dynamic_cast et static_cast en C ++
-
20-09-2019 - |
Question
Je suis tout à fait confondu avec le mot-clé dynamic_cast
en C ++.
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
}
la définition dit:
Le mot-clé
dynamic_cast
jette une donnée d'un pointeur ou une référence type à un autre, d'effectuer un contrôle d'exécution pour assurer la validité de la distribution
Peut-on écrire un équivalent de dynamic_cast
de C ++ en C pour que je puisse mieux comprendre les choses?
La solution
Voici un aperçu sur static_cast<>
et dynamic_cast<>
particulièrement en ce qui a trait aux pointeurs. Ceci est juste une diminution des effectifs de 101 niveau, il ne couvre pas toutes les subtilités.
static_cast (PTR)
Cela prend le pointeur dans ptr
et tente de jeter en toute sécurité à un pointeur de type Type*
. Cette fonte se fait au moment de la compilation. Il ne s'effectuer la conversion des types de type sont liés. Si les types ne sont pas liés, vous obtiendrez une erreur de compilation. Par exemple:
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 (PTR)
essaie à nouveau de prendre le pointeur dans ptr
et en toute sécurité à jeter un pointeur de type Type*
. Mais cette distribution est exécuté lors de l'exécution, pas le temps de compilation. Parce que c'est un casting d'exécution, il est particulièrement utile lorsqu'il est combiné avec des classes polymorphes. En fait, dans les cas certian les classes doivent être polymorphes pour que la distribution soit légal.
moulages peuvent aller dans une des deux directions: à partir de la base de dérivé (B2D) ou de dérivé de base (D2B). Il est assez simple de voir comment D2B moulages travailleraient à l'exécution. Soit ptr
a été dérivé de Type
ou il n'a pas été. Dans le cas de D2B dynamic_cast <> s, les règles sont simples. Vous pouvez essayer de jeter quoi que ce soit à toute autre chose, et si ptr
était en fait dérivé de Type
, vous obtiendrez un pointeur Type*
retour de dynamic_cast
. Sinon, vous obtiendrez un pointeur NULL.
Mais moulages B2D sont un peu plus compliqué. Considérez le code suivant:
#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()
ne peut pas dire quel genre de CreateRandom()
objet sera de retour, de sorte que le Bar* bar = (Bar*)base;
coulé style C est décidément pas de type sécurisé. Comment pouvez-vous résoudre ce problème? Un moyen serait d'ajouter une fonction comme bool AreYouABar() const = 0;
à la classe de base et de retour true
de Bar
et false
de Foo
. Mais il y a une autre façon: l'utilisation 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;
}
Les moulages exécutent à l'exécution, et le travail en interrogeant l'objet (pas besoin de vous soucier de la façon dont pour l'instant), en lui demandant si elle le type que nous recherchons. Si elle est, dynamic_cast<Type*>
renvoie un pointeur; sinon il retourne NULL.
Pour que cette base de dérivés coulée au travail en utilisant dynamic_cast<>
, Base, Foo et Bar doivent être ce que les appels standard types polymorphes . Pour être un type polymorphes, votre classe doit avoir au moins une fonction virtual
. Si vos classes ne sont pas les types polymorphes, la base à l'utilisation de dérivés dynamic_cast
ne compilera pas. Exemple:
class Base {};
class Der : public Base {};
int main()
{
Base* base = new Der;
Der* der = dynamic_cast<Der*>(base); // ERROR - Won't compile
return 0;
}
Ajout d'une fonction virtuelle à la base, tel qu'un dtor virtuel, fera à la fois de base et Der types polymorphes:
class Base
{
public:
virtual ~Base(){};
};
class Der : public Base {};
int main()
{
Base* base = new Der;
Der* der = dynamic_cast<Der*>(base); // OK
return 0;
}
Autres conseils
Sauf si vous implémentez votre propre RTTI (et sans passer par le système un) roulées à la main, il est impossible de mettre en œuvre dynamic_cast
directement en C ++ code de niveau utilisateur. dynamic_cast
est très lié au système RTTI de mise en œuvre du C.
Mais, pour vous aider à comprendre RTTI (et donc dynamic_cast
) plus, vous devriez lire sur l'en-tête de <typeinfo>
, et l'opérateur typeid
. Cela renvoie les informations de type correspondant à l'objet que vous avez à portée de main, et vous pouvez demander divers (limité) les choses de ces objets d'information de type.
Plus de code en C, je pense qu'une définition anglaise pourrait être suffisant:
Compte tenu d'une base de classe dont il est une classe dérivée dérivée, dynamic_cast
convertit un pointeur de base à un pointeur dérivé si et seulement si l'objet réel pointé est en fait un objet dérivé.
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
}
Dans l'exemple, l'appel à des objets différents test
lie à une référence à Base
. En interne, la référence est downcasted à une référence à Derived
de manière typesafe:. Le baissés ne réussira que dans les cas où l'objet référencé est en effet une instance de Derived
Ce qui suit est pas vraiment à ce que vous obtenez de dynamic_cast
de C ++ en termes de vérification de type, mais peut-être vous aidera à comprendre son but un peu mieux:
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;
}
A dynamic_cast
effectue une vérification de type en utilisant RTTI . Si elle échoue, elle va vous lancer une exception (si vous avez donné une référence) ou NULL si vous avez donné un pointeur.
Tout d'abord, pour décrire casting dynamique en termes C, nous devons représenter les classes en C. Les classes avec des fonctions virtuelles utilisent un « VTABLE » de pointeurs vers les fonctions virtuelles. Les commentaires sont C ++. Ne hésitez pas à reformater et corriger des erreurs de compilation ...
// 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])();
Ensuite, un casting dynamique est quelque chose comme:
// A * ptr = new B();
A * ptr = (A*) newB();
// B * aB = dynamic_cast<B>(ptr);
B * aB = ( ptr->vtable == Bvtable ? (B*) aB : (B*) 0 );
Il n'y a pas de classes en C, il est donc impossible d'écrire à dynamic_cast dans cette langue. structures C ne sont pas des méthodes (par conséquent, ils ne sont pas des méthodes virtuelles), donc il n'y a rien dans ce « dynamique ».
Non, pas facilement. Le compilateur attribue une identité unique à chaque classe, cette information est référencée par chaque instance d'objet, et qui est ce qui est inspecté lors de l'exécution pour déterminer si une distribution dynamique est légal. Vous pouvez créer une classe de base standard avec ces informations et les opérateurs pour faire l'inspection d'exécution sur cette classe de base, toute classe dérivée informerait la classe de base de sa place dans la hiérarchie des classes et toutes les instances de ces classes serait runtime-coulable via vos opérations.
modifier
Voici une implémentation qui démontre une technique. Je ne prétends pas que le compilateur utilise quelque chose comme ça, mais je pense que cela démontre les concepts:
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;
}
dynamic_cast utilise RTTI. Il peut ralentir votre application, vous pouvez utiliser la modification du modèle de conception des visiteurs pour atteindre downcasting sans RTTI http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html
static_cast< Type* >(ptr)
static_cast en C ++ peut être utilisé dans des scénarios où tous les types coulée peut être vérifiée au moment de la compilation .
dynamic_cast< Type* >(ptr)
dynamic_cast en C ++ peut être utilisé pour effectuer SÛR type bas casting . dynamic_cast est exécuté polymorphisme de temps. L'opérateur dynamic_cast, qui convertit en toute sécurité à partir d'un pointeur (ou de référence) à un type de base à un pointeur (ou de référence) à un type dérivé.
par exemple 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;
}
Pour plus d'informations cliquez ici
par exemple 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";
}