C ++のdynamic_castおよびstatic_cast


155

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 ++ を書くことはできますか?


1
dynamic_cast<>舞台裏でどのように機能するか(またはC ++ がどの程度機能するか)の良いアイデアを得たい場合は、Lippmanの「Inside the C ++ Object Model」という優れた本(これも非常に技術的に読むのは簡単です)です。また、Stroustrupの「C ++の設計と進化」および「C ++プログラミング言語」の本は優れたリソースですが、リップマンの本は、C ++が「舞台裏」でどのように機能するかを扱っています。
マイケルバー

行のコメントはどういうB* b2 = dynamic_cast<B*> (ap) // 'b'意味ですか?b2 is pointer to bまたは何?
LRDPRDX 2017

@BogdanSikachそれは何の質問ですか?これは単に、APは現在、Bクラスのタイプであることを意味

回答:


282

ここではstatic_cast<>dynamic_cast<>具体的には、それらがポインタに関連する場合の概要を示します。これは単なる101レベルのランダウンであり、すべての複雑さをカバーしているわけではありません。

static_cast <Type *>(ptr)

これはポインタを受け取り、ptrそれをタイプのポインタに安全にキャストしようとします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)のいずれかの方向に進むことができます。実行時にD2Bキャストがどのように機能するかを確認するのは簡単です。ptr由来Typeかそうでないかのどちらかです。D2B dynamic_cast <> sの場合、ルールは単純です。何かを他のものにキャストしようとすることができptr、実際にから派生したType場合、Type*からポインターが返され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などの関数をAreYouABar() const = 0;基本クラスに追加し、truefrom Barfalsefrom を返すことFooです。しかし、別の方法がありますdynamic_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と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;
}

9
コンパイラはなぜそもそもそれについて文句を言うのですか?また、ベースのみに仮想dctorのみを提供するのではありませんか?
リカ

5
あなたがしなければことに留意すべきであるBase* base = new Base;dynamic_cast<Foo*>(base)となりますNULL
Yay295

2
@ Coderx7 dynamic_castには、ポリモーフィックなクラス(つまり、少なくとも1つの仮想メソッドを持つクラス)でのみ使用可能なランタイムタイプ情報(RTTI)が必要です。
Elvorfirilmathredia 2017

@ Yay295なぜdynamic_cast<Foo*>(base)aの場合null Base* base = new Base;ですか?
MuneshSingh 2017年

3
@muneshでbaseはないのでFooBaseポインタが指すことができFoo、それはまだだFooダイナミックなキャストが動作しますので、。行う場合Base* base = new BasebaseはでBaseはなくであるFooため、動的ににキャストすることはできませんFoo
Yay295 2017年

20

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

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


ウィキペディアを紹介しdynamic_castますが、RTTIに関する記事は非常に扱いにくいです。:-P慣れるまで自分で遊んでください。:-)
Chris Jester-Young

10

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

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

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


2
上記で共有された例は、クラスが多態性のみである場合、つまり、少なくとも基本クラスに少なくとも仮想メソッドがある場合、仮定に基づいて機能することを明確にした方がいいと思います。
irsis

1
クラスはポリモーフィック型ではないため、これは失敗します。
username_4567

4

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

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;
}

3

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


2

まず、Cで動的キャストを説明するには、Cでクラスを表す必要があります。仮想関数を持つクラスは、仮想関数へのポインターの「VTABLE」を使用します。コメントはC ++です。自由に再フォーマットしてコンパイルエラーを修正してください...

// class A { public: int data; virtual int GetData(){return data;} };
typedef struct A { void**vtable; int data;} A;
int AGetData(A*this){ return this->data; }
void * Avtable[] = { (void*)AGetData };
A * newA() { A*res = malloc(sizeof(A)); res->vtable = Avtable; return res; }

// class B : public class A { public: int moredata; virtual int GetData(){return data+1;} }
typedef struct B { void**vtable; int data; int moredata; } B;
int BGetData(B*this){ return this->data + 1; }
void * Bvtable[] = { (void*)BGetData };
B * newB() { B*res = malloc(sizeof(B)); res->vtable = Bvtable; return res; }

// int temp = ptr->GetData();
int temp = ((int(*)())ptr->vtable[0])();

次に、動的キャストは次のようなものです。

// A * ptr = new B();
A * ptr = (A*) newB();
// B * aB = dynamic_cast<B>(ptr);
B * aB = ( ptr->vtable == Bvtable ? (B*) aB : (B*) 0 );

1
最初の質問は、「CでC ++のdynamic_castに相当するものを書けるか」というものでした。
デビッドレイナ

1

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


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;
}


0

static_cast< Type* >(ptr)

C ++のstatic_castは、コンパイル時にすべての型キャストを検証できるシナリオで使用できます

dynamic_cast< Type* >(ptr)

C ++のdynamic_castを使用して、タイプセーフなダウンキャストを実行できます。dynamic_castは、実行時のポリモーフィズムです。dynamic_cast演算子。基本型へのポインター(または参照)から派生型へのポインター(または参照)に安全に変換します。

例1:

#include <iostream>
using namespace std;

class A
{
public:
    virtual void f(){cout << "A::f()" << endl;}
};

class B : public A
{
public:
    void f(){cout << "B::f()" << endl;}
};

int main()
{
    A a;
    B b;
    a.f();        // A::f()
    b.f();        // B::f()

    A *pA = &a;   
    B *pB = &b;   
    pA->f();      // A::f()
    pB->f();      // B::f()

    pA = &b;
    // pB = &a;      // not allowed
    pB = dynamic_cast<B*>(&a); // allowed but it returns NULL

    return 0;
}

詳細については、ここをクリックしてください

例2:

#include <iostream>

using namespace std;

class A {
public:
    virtual void print()const {cout << " A\n";}
};

class B {
public:
    virtual void print()const {cout << " B\n";}
};

class C: public A, public B {
public:
    void print()const {cout << " C\n";}
};


int main()
{

    A* a = new A;
    B* b = new B;
    C* c = new C;

    a -> print(); b -> print(); c -> print();
    b = dynamic_cast< B*>(a);  //fails
    if (b)  
       b -> print();  
    else 
       cout << "no B\n";
    a = c;
    a -> print(); //C prints
    b = dynamic_cast< B*>(a);  //succeeds
    if (b)
       b -> print();  
    else 
       cout << "no B\n";
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.