171

dynamic_castC++のキーワードにかなり混乱しています。

struct A {
    virtual void f() { }
};
struct B : public A { };
struct C { };

void f () {
    A a;
    B b;

    A* ap = &b;
    B* b1 = dynamic_cast<B*> (&a);  // NULL, because 'a' is not a 'B'
    B* b2 = dynamic_cast<B*> (ap);  // 'b'
    C* c = dynamic_cast<C*> (ap);   // NULL.

    A& ar = dynamic_cast<A&> (*ap); // Ok.
    B& br = dynamic_cast<B&> (*ap); // Ok.
    C& cr = dynamic_cast<C&> (*ap); // std::bad_cast
}

定義は次のように述べています。

キーワードは、dynamic_castあるポインターまたは参照型から別の型にデータをキャストし、ランタイム チェックを実行してキャストの有効性を確認します。

dynamic_cast物事をよりよく理解できるように、C++ に相当するものを C で書くことはできますか?

4

10 に答える 10

322

ここではstatic_cast<>dynamic_cast<>特にポインターに関連する概要を示します。これは 101 レベルの要約に過ぎず、すべての複雑さを網羅しているわけではありません。

static_cast< Type* >(ptr)

これはポインタを受け取り、ptrそれを type のポインタに安全にキャストしようとしますType*。このキャストはコンパイル時に行われます。型が関連している場合にのみキャストを実行します。型が関連していない場合は、コンパイラ エラーが発生します。例えば:

class B {};
class D : public B {};
class X {};

int main()
{
  D* d = new D;
  B* b = static_cast<B*>(d); // this works
  X* x = static_cast<X*>(d); // ERROR - Won't compile
  return 0;
}

dynamic_cast< Type* >(ptr)

ptrこれは再びポインターを取り込もうとし、安全に type のポインターにキャストしようとしますType*。ただし、このキャストはコンパイル時ではなく実行時に実行されます。これはランタイム キャストであるため、特にポリモーフィック クラスと組み合わせると便利です。実際、場合によっては、キャストが合法的であるためにクラスがポリモーフィックである必要があります。

キャストは、ベースから派生 (B2D) または派生からベース (D2B) の 2 つの方向のいずれかに進むことができます。D2B キャストが実行時にどのように機能するかを確認するのは簡単です。ptrから派生したTypeか、そうでないかのどちらかです。D2B dynamic_cast<> の場合、ルールは単純です。何かを他の何かにキャストしようとすることができptr、実際に から派生したものである場合はTypeType*からポインターが返されdynamic_castます。そうしないと、NULL ポインターが返されます。

しかし、B2D キャストはもう少し複雑です。次のコードを検討してください。

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void DoIt() = 0;    // pure virtual
    virtual ~Base() {};
};

class Foo : public Base
{
public:
    virtual void DoIt() { cout << "Foo"; }; 
    void FooIt() { cout << "Fooing It..."; }
};

class Bar : public Base
{
public :
    virtual void DoIt() { cout << "Bar"; }
    void BarIt() { cout << "baring It..."; }
};

Base* CreateRandom()
{
    if( (rand()%2) == 0 )
        return new Foo;
    else
        return new Bar;
}


int main()
{
    for( int n = 0; n < 10; ++n )
    {
        Base* base = CreateRandom();

            base->DoIt();

        Bar* bar = (Bar*)base;
        bar->BarIt();
    }
  return 0;
}

main()返されるオブジェクトの種類がわからないCreateRandom()ため、C スタイルのキャストBar* bar = (Bar*)base;は明らかに型安全ではありません。どうすればこれを修正できますか? 1 つの方法は、bool のような関数を基本クラスに追加して、fromおよびfrom からAreYouABar() const = 0;返すことです。しかし、別の方法があります: 使用:trueBarfalseFoodynamic_cast<>

int main()
{
    for( int n = 0; n < 10; ++n )
    {
        Base* base = CreateRandom();

        base->DoIt();

        Bar* bar = dynamic_cast<Bar*>(base);
        Foo* foo = dynamic_cast<Foo*>(base);
        if( bar )
            bar->BarIt();
        if( foo )
            foo->FooIt();
    }
  return 0;

}

キャストは実行時に実行され、オブジェクトを照会して (今のところ方法について心配する必要はありません)、探している型であるかどうかを尋ねます。そうであればdynamic_cast<Type*>、ポインタを返します。それ以外の場合は NULL を返します。

このベースから派生へのキャストが を使用して機能するためにはdynamic_cast<>、Base、Foo、および Bar が、標準でポリモーフィック タイプと呼ばれるものでなければなりません。ポリモーフィック型であるためには、クラスに少なくとも 1 つのvirtual関数が必要です。クラスがポリモーフィック型でない場合、ベースから派生への使用はdynamic_castコンパイルされません。例:

class Base {};
class Der : public Base {};


int main()
{
    Base* base = new Der;
    Der* der = dynamic_cast<Der*>(base); // ERROR - Won't compile

    return 0;
}

仮想 dtor などの仮想関数を base に追加すると、Base と Der の両方のポリモーフィック型が作成されます。

class Base 
{
public:
    virtual ~Base(){};
};
class Der : public Base {};


int main()
{
    Base* base = new Der;
    Der* der = dynamic_cast<Der*>(base); // OK

    return 0;
}
于 2010-02-12T18:29:34.720 に答える
20

dynamic_cast独自の手巻き RTTI を実装する (およびシステムの RTTI をバイパスする) 場合を除き、C++ ユーザーレベル コードで直接実装することはできません。dynamic_castC++ 実装の RTTI システムと密接に結びついています。

ただし、RTTI (および ) をさらに理解するためには、ヘッダーと演算子dynamic_castを読む必要があります。これにより、手元にあるオブジェクトに対応する型情報が返され、これらの型情報オブジェクトからさまざまな (限定された) ものを照会できます。<typeinfo>typeid

于 2010-02-12T16:15:12.017 に答える
14

C のコードよりも、英語の定義で十分だと思います。

派生クラス Derived があるクラス Base を指定すると、dynamic_cast指している実際のオブジェクトが実際に派生オブジェクトである場合にのみ、Base ポインターが派生ポインターに変換されます。

class Base { virtual ~Base() {} };
class Derived : public Base {};
class Derived2 : public Base {};
class ReDerived : public Derived {};

void test( Base & base )
{
   dynamic_cast<Derived&>(base);
}

int main() {
   Base b;
   Derived d;
   Derived2 d2;
   ReDerived rd;

   test( b );   // throw: b is not a Derived object
   test( d );   // ok
   test( d2 );  // throw: d2 is not a Derived object
   test( rd );  // ok: rd is a ReDerived, and thus a derived object
}

この例では、 への呼び出しにより、testさまざまなオブジェクトが への参照にバインドされBaseます。内部的に、参照はタイプセーフな方法でへの参照にダウンキャストDerivedされます。ダウンキャストは、参照されるオブジェクトが実際に のインスタンスである場合にのみ成功しますDerived

于 2010-02-12T16:45:44.893 に答える
4

以下は、型チェックの点でC ++から得られるものに実際には近いものではありませんがdynamic_cast、その目的を少しよく理解するのに役立つかもしれません。

struct Animal // Would be a base class in C++
{
    enum Type { Dog, Cat };
    Type type;
};

Animal * make_dog()
{
   Animal * dog = new Animal;
   dog->type = Animal::Dog;
   return dog;
}
Animal * make_cat()
{
   Animal * cat = new Animal;
   cat->type = Animal::Cat;
   return cat;
}

Animal * dyn_cast(AnimalType type, Animal * animal)
{
    if(animal->type == type)
        return animal;
    return 0;
}

void bark(Animal * dog)
{
    assert(dog->type == Animal::Dog);

    // make "dog" bark
}

int main()
{
    Animal * animal;
    if(rand() % 2)
        animal = make_dog();
    else
        animal = make_cat();

    // At this point we have no idea what kind of animal we have
    // so we use dyn_cast to see if it's a dog

    if(dyn_cast(Animal::Dog, animal))
    {
        bark(animal); // we are sure the call is safe
    }

    delete animal;
}
于 2010-02-12T16:21:54.113 に答える
3

AはRTTIdynamic_castを使用して型チェックを実行します。失敗した場合は、例外 (参照を指定した場合) またはポインターを指定した場合は NULL がスローされます。

于 2010-02-12T16:17:40.777 に答える
2

C にはクラスがないので、その言語で dynamic_cast を書くことは不可能です。C 構造体にはメソッドがありません (その結果、仮想メソッドがありません)。そのため、「動的」なものはありません。

于 2010-02-12T16:19:05.940 に答える
1

dynamic_cast は RTTI を使用します。アプリケーションの速度が低下する可能性があります。ビジター デザイン パターンの変更を使用して、RTTI なしでダウンキャストを実現できますhttp://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html

于 2016-02-11T00:32:31.920 に答える
1

いいえ、簡単ではありません。コンパイラはすべてのクラスに一意の ID を割り当てます。その情報はすべてのオブジェクト インスタンスによって参照され、動的キャストが有効かどうかを判断するために実行時に検査されます。この情報と演算子を使用して標準の基本クラスを作成し、その基本クラスで実行時検査を行うことができます。その後、派生クラスは基本クラスにクラス階層内の場所を通知し、それらのクラスのインスタンスは実行時にキャスト可能になります。あなたの操作。

編集

1 つの手法を示す実装を次に示します。コンパイラがこのようなものを使用していると主張しているわけではありませんが、概念を示していると思います。

class SafeCastableBase
{
public:
    typedef long TypeID;
    static TypeID s_nextTypeID;
    static TypeID GetNextTypeID()
    {
        return s_nextTypeID++;
    }
    static TypeID GetTypeID()
    {
        return 0;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return false; }
        return true;
    }
    template <class Target>
    static Target *SafeCast(SafeCastableBase *pSource)
    {
        if (pSource->CanCastTo(Target::GetTypeID()))
        {
            return (Target*)pSource;
        }
        return NULL;
    }
};
SafeCastableBase::TypeID SafeCastableBase::s_nextTypeID = 1;

class TypeIDInitializer
{
public:
    TypeIDInitializer(SafeCastableBase::TypeID *pTypeID)
    {
        *pTypeID = SafeCastableBase::GetNextTypeID();
    }
};

class ChildCastable : public SafeCastableBase
{
public:
    static TypeID s_typeID;
    static TypeID GetTypeID()
    {
        return s_typeID;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
        return true;
    }
};
SafeCastableBase::TypeID ChildCastable::s_typeID;

TypeIDInitializer ChildCastableInitializer(&ChildCastable::s_typeID);

class PeerChildCastable : public SafeCastableBase
{
public:
    static TypeID s_typeID;
    static TypeID GetTypeID()
    {
        return s_typeID;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
        return true;
    }
};
SafeCastableBase::TypeID PeerChildCastable::s_typeID;

TypeIDInitializer PeerChildCastableInitializer(&PeerChildCastable::s_typeID);

int _tmain(int argc, _TCHAR* argv[])
{
    ChildCastable *pChild = new ChildCastable();
    SafeCastableBase *pBase = new SafeCastableBase();
    PeerChildCastable *pPeerChild = new PeerChildCastable();
    ChildCastable *pSameChild = SafeCastableBase::SafeCast<ChildCastable>(pChild);
    SafeCastableBase *pBaseToChild = SafeCastableBase::SafeCast<SafeCastableBase>(pChild);
    ChildCastable *pNullDownCast = SafeCastableBase::SafeCast<ChildCastable>(pBase);
    SafeCastableBase *pBaseToPeerChild = SafeCastableBase::SafeCast<SafeCastableBase>(pPeerChild);
    ChildCastable *pNullCrossCast = SafeCastableBase::SafeCast<ChildCastable>(pPeerChild);
    return 0;
}
于 2010-02-12T16:15:21.613 に答える