我们知道
dynamic_cast<void*>
会强制转换一个指向最派生对象的指针;但是如果底层对象不是最派生的呢?例如:
class BaseClass { public: virtual void dummy() { std::cout << "Base\n"; } };
class DerivedClass : public BaseClass {
int a{};
public:
void dummy() { std::cout << "Derived\n"; }
};
class MostDerivedClass : public DerivedClass {
int b{};
public:
void dummy() { std::cout << "Most\n"; }
};
BaseClass* basePtr_d = new DerivedClass, *basePtr_md = new MostDerivedClass;
DerivedClass* derivedPtr =
dynamic_cast<DerivedClass*>(basePtr_d); // right
MostDerivedClass* mostDerivedPtr =
dynamic_cast<MostDerivedClass*>(basePtr_md); // right
MostDerivedClass* mostDerivedPtr2 =
static_cast<MostDerivedClass*>(dynamic_cast<void*>(basePtr_md)); // right
DerivedClass* derivedPtr2 =
static_cast<DerivedClass*>(dynamic_cast<void*>(basePtr_d)); // What happens??
最后一个案例会怎样?
DerivedClass* derivedPtr2 =
static_cast<DerivedClass*>(dynamic_cast<void*>(basePtr_d)); // What happens??
这相当于 (1) 向下转换(动态),然后是 (2) 向上转换(静态),后者是定义明确的非问题:
void* p1 = dynamic_cast<void*>(basePtr_d); // 1
MostDerivedClass* p2 = reinterpret_cast<MostDerivedClass*>(p1); // †
DerivedClass* p3 = static_cast<DerivedClass*>(p2); // 2
很容易忽略重要的区别:指针类型和指针值,后者有时被称为指针的动态类型。
当
B
派生自 A
时,类型 A*
的指针和类型 B*
的指针都指向同一个类型 B
的对象可能具有不同的 values。对 dynamic_cast<void*>
的限制涵盖了这种特殊性,并保证了最派生类型的返回值,即使返回指针的静态类型当然是 void*
.
__
† 为了使等效性起作用,需要进行显式指针转换。这是安全的,因为在
dynamic_cast<new-type>(expression)
中,如果 expression 是指向多态类型的指针,而 new-type 是指向 void 的指针,则结果是指向由 expression 指向或引用的最派生对象的指针。见dynamic_cast #4
。
static_cast
中的所有void*
所做的就是更改类型而不更改指针的值。它与reinterpret_cast
相同。
在这种情况下,一切都应该没问题,因为 MostDerivedClass
对象与其 DerivedClass
子对象具有相同的地址。
如果不是这种情况,
derivedPtr2
将是一个无效指针。从技术上讲,这可能仍然是 UB,但我不确定。
说你的MostDerivedClass
实际上是这样的:
struct X { int i; };
class MostDerivedClass : public X, public DerivedClass {
int b{};
public:
void dummy() { std::cout << "Most\n"; }
};
现在做一个
static_cast
从MostDerivedClass*
到DerivedClass*
改变指针的值,可能通过添加四个字节的偏移量。
现在这条线
DerivedClass* derivedPtr2 =
static_cast<DerivedClass*>(dynamic_cast<void*>(basePtr_d)); // What happens??
静态地将一个空指针(指向
MostDerivedClass
对象或X
子对象)投射到DerivedClass*
,这本质上是一个reinterpret_cast
。所以你最终得到一个 DerivedClass
类型的指针,它实际上指向 X
-子对象,而不是 DerivedClass
子对象。
所以你最终得到的 derivedPtr2
指针是无效的。