私はC++のdynamic_cast
キーワードと非常に混同しています。
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
キーワードは、データを1つのポインターまたは参照型から別の型にキャストし、実行時チェックを実行してキャストの有効性を確認します
物事をより良く理解できるように、C++のdynamic_cast
に相当するものをCで書くことはできますか?
static_cast<>
とdynamic_cast<>
の詳細は、特にポインターに関連するものです。これは単なる101レベルの要約であり、すべての複雑さを網羅しているわけではありません。
これは、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;
}
これは再びptr
のポインターを取得し、Type*
型のポインターに安全にキャストしようとします。ただし、このキャストはコンパイル時ではなく実行時に実行されます。これはランタイムキャストであるため、特にポリモーフィッククラスと組み合わせる場合に役立ちます。実際、certianの場合、キャストを有効にするために、クラスmustはポリモーフィックでなければなりません。
キャストは、ベースから派生(B2D)または派生からベース(D2B)のいずれかの方向に進むことができます。 D2Bキャストが実行時にどのように機能するかを見るのは簡単です。 ptr
はType
から派生したか、派生していません。 D2B dynamic_cast <>の場合、ルールは単純です。他のものに何でもキャストしようとすることができ、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;
などの関数を基本クラスに追加し、true
からBar
およびfalse
から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;
}
独自の手動ロールRTTIを実装する(およびシステムRTTIをバイパスする)場合を除き、C++ユーザーレベルコードにdynamic_cast
を直接実装することはできません。 dynamic_cast
は、C++実装のRTTIシステムに密接に関係しています。
しかし、RTTI(したがってdynamic_cast
)をさらに理解するには、<typeinfo>
ヘッダーとtypeid
演算子を確認する必要があります。これにより、手元にあるオブジェクトに対応する型情報が返され、これらの型情報オブジェクトからさまざまな(制限された)ものを照会できます。
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
への参照にバインドされます。内部的には、参照はdowncasted型安全な方法でDerived
への参照になります。ダウンキャストは、参照されるオブジェクトが実際にDerived
のインスタンスである場合にのみ成功します。
以下は、型チェックに関して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;
}
dynamic_cast
は、 RTTI を使用して型チェックを実行します。失敗した場合、例外(参照を与えた場合)をスローするか、ポインターを与えた場合はNULLをスローします。
まず、動的なキャストを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 );
いいえ、簡単ではありません。コンパイラはすべてのクラスに一意の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;
}
Cにはクラスがないため、その言語でdynamic_castを記述することはできません。 C構造体にはメソッドがないため(結果として、仮想メソッドがないため)、「動的」なものは何もありません。
dynamic_castはRTTIを使用します。アプリケーションの速度が低下する可能性があります。ビジターのデザインパターンの変更を使用して、RTTIなしでダウンキャストを実現できます http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html
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;
}
詳細については クリック こちら
eg 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";
}