我对 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 关键字将数据从一种指针或引用类型转换为另一种,执行运行时检查以确保转换的有效性
我们可以用 C 编写相当于 dynamic_cast
的 C++ 以便我更好地理解事物吗?
dynamic_cast<>
在幕后的工作原理(或 C++ 的工作原理),一本好书(对于技术性很强的东西也很容易阅读)是 Lippman 的“Inside the C++ Object Model” . Stroustrup 的“Design and Evolution of C++”和“The C++ Programming Language”书籍也是很好的资源,但 Lippman 的书专注于 C++ 如何在“幕后”工作。
B* b2 = dynamic_cast<B*> (ap) // 'b'
行中的注释是什么意思? b2 is pointer to b
还是什么?
以下是有关指针的 static_cast<>
和 dynamic_cast<>
的简要说明。这只是一个101级的纲要,并没有涵盖所有的错综复杂。
static_cast< 类型* >(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< 类型* >(ptr)
这再次尝试获取 ptr
中的指针并将其安全地转换为 Type*
类型的指针。但是这个转换是在运行时执行的,而不是编译时。因为这是一个运行时强制转换,所以在与多态类结合使用时尤其有用。事实上,在某些情况下,类必须是多态的,才能使强制转换合法。
强制转换可以沿两个方向之一进行:从基础到派生 (B2D) 或从派生到基础 (D2B)。看看 D2B 转换如何在运行时工作很简单。 ptr
是从 Type
派生的,或者不是。在 D2B dynamic_cast 的情况下,规则很简单。您可以尝试将任何内容强制转换为其他任何内容,如果 ptr
实际上是从 Type
派生的,您将从 dynamic_cast
返回一个 Type*
指针。否则,您将得到一个 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;
显然不是类型安全的。你怎么能解决这个问题?一种方法是将类似 bool AreYouABar() const = 0;
的函数添加到基类并从 Bar
返回 true
并从 Foo
返回 false
。但还有另一种方法:使用 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 必须是标准所称的多态类型。为了成为多态类型,您的类必须至少有一个 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;
}
将虚函数添加到 base 中,例如 virtual 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(并绕过系统),否则不可能直接在 C++ 用户级代码中实现 dynamic_cast
。 dynamic_cast
与 C++ 实现的 RTTI 系统密切相关。
但是,为了帮助您更多地了解 RTTI(以及 dynamic_cast
),您应该阅读 <typeinfo>
标头和 typeid
运算符。这将返回与您手头的对象对应的类型信息,您可以从这些类型信息对象中查询各种(有限的)事物。
dynamic_cast
的文章非常少。 :-P 自己玩,直到你掌握它的窍门。 :-)
不仅仅是 C 中的代码,我认为一个英文定义就足够了:
给定一个 Base 类,其中有一个派生类 Derived,当且仅当指向的实际对象实际上是 Derived 对象时,dynamic_cast
才会将 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
实例的情况下成功。
在类型检查方面,以下内容与您从 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;
}
首先,为了用 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 );
中没有类,因此不可能用该语言编写 dynamic_cast。 C 结构没有方法(因此,它们没有虚拟方法),因此其中没有“动态”的东西。
不,不容易。编译器为每个类分配一个唯一标识,该信息被每个对象实例引用,这就是在运行时检查以确定动态转换是否合法的内容。您可以使用此信息和运算符创建一个标准基类,以对该基类进行运行时检查,然后任何派生类都会通知基类它在类层次结构中的位置,并且这些类的任何实例都可以通过以下方式进行运行时强制转换你的操作。
编辑
这是一个演示一种技术的实现。我没有声称编译器使用了类似的东西,但我认为它展示了这些概念:
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;
}
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;
}
如需更多信息,请点击此处click
例如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";
}
dynamic_cast 使用 RTTI。它会减慢您的应用程序,您可以使用访问者设计模式的修改来实现无需 RTTI 的向下转换http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html
Base* base = new Base;
,则dynamic_cast<Foo*>(base)
将是NULL
。base
不是Foo
。Base
指针可以指向Foo
,但它仍然是Foo
,因此可以进行动态转换。如果您使用Base* base = new Base
,则base
是Base
,而不是Foo
,因此您不能将其动态转换为Foo
。bar->BarIt();
的每次调用都会打印baring It...
,即使对于Foo
类也是如此。尽管现在的答案已经过时了,但它可能会对为什么做出很好的评论。即,类定义中BarIt
和FooIt
的巧合重叠 vtable 条目。