dynamic_cast 由于多重继承和中间变量,返回相同的对象类型失败

dynamic_cast back to same object type fails with multiple inheritance and intermediate variable

假设一个层次结构包含两个不相关的多态 类 PCHGME、一个子类 PCH_GME : public GME, public PCH 和一个 gme_pch 类型的对象 [=19] =].

为什么以下 gme_pch "break" 的转换序列会转换回对象的原始类型 GME_PCH*:

GME_PCH *gme_pch = new GME_PCH();    
GME *gme = gme_pch;
PCH *pch = (PCH*)gme;
GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);
// same_as_gme_pch is NULL

而以下代码不会破坏转换:

GME_PCH *gme_pch = new GME_PCH();    
PCH *pch = gme_pch;    
GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);
// address of same_as_gme_pch == gme_pch

问题:难道每个指针总是指向同一个对象,那么最后转换回原始类型的结果不应该总是相同的吗?

编辑:根据答案,我添加了gme_pchpch地址的输出。它表明这两个指针在起作用的变体中的关系不同于那些不起作用的变体(即取决于一个人是写 GME_PCE : public GME, public PCH 还是 GME_PCE : public PCH, public GMEgme_pch 等于 pch 在工作变体中和 gme_pch 在非工作变体中不相等,反之亦然)。


为了更容易尝试,请参阅以下代码演示上述转换序列的变体;有些工作,有些不工作:

class PCH {  // PrecachingHint
public:
    virtual std::string getHint() const = 0;
};

class GME {  // GenericModelElement
public:
    virtual std::string getKey() const = 0;
};

class GME_PCH : public GME, public PCH {
public:
    virtual std::string getHint() const { return "some hint"; }
    virtual std::string getKey() const { return "some key"; }
};

void castThatWorks() {

    GME_PCH *gme_pch = new GME_PCH();

    PCH *pch = gme_pch;

    GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);

    std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}

void castThatWorks2() {

    GME_PCH *gme_pch = new GME_PCH();

    GME *gme = gme_pch;
    PCH *pch = dynamic_cast<PCH*>(gme);

    GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);

    std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}

void castThatDoesntWork() {

    GME_PCH *gme_pch = new GME_PCH();

    GME *gme = gme_pch;  // note: void* gme = gme_pch breaks the subsequent dynamic cast, too.
    PCH *pch = (PCH*)gme;

    GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);

    std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}

void castThatDoesntWork2() {

    GME_PCH *gme_pch = new GME_PCH();

    GME *gme = gme_pch;
    PCH *pch =  reinterpret_cast<PCH*>(gme);

    GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);

    std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}

void castThatDoesntWork3() {

    GME_PCH *gme_pch = new GME_PCH();

    GME *gme = gme_pch;
    PCH *pch = static_cast<PCH*>(static_cast<void*>(gme));

    GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);

    std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}

int main() {
    castThatWorks();
    castThatWorks2();
    castThatDoesntWork();
    castThatDoesntWork2();
    castThatDoesntWork3();   
}

输出:

cast worked.gmepch:0x100600030; pch:0x100600038
cast worked.gmepch:0x100600040; pch:0x100600048
cast did not work.gmepch:0x100600260; pch:0x100600260
cast did not work.gmepch:0x100202c30; pch:0x100202c30
cast did not work.gmepch:0x100600270; pch:0x100600270
GME_PCH *gme_pch = new GME_PCH();

GME *gme = gme_pch;
PCH *pch = static_cast<PCH*>(static_cast<void*>(gme));
GME_PCHGMEPCH 多重继承时,

是错误的。不要使用它。

通过尝试以下操作,您会发现错误的原因:

GME_PCH *gme_pch = new GME_PCH();

GME *gme = gme_pch;
PCH *pch1 = gme_pch;  // Implicit conversion. Does the right offsetting of pointer
PCH *pch2 = static_cast<PCH*>(static_cast<void*>(gme)); // Wrong.

std::cout << "pch1: " << pch1 << ", pch2: " << pch2 << std::endl;

您会注意到 pch1pch2 是不同的。 pch1 是有效值,而 pch2 不是。

使用继承时,即使没有多重继承,也需要将指针值从指向派生 class 的指针调整为可能不同的指向基 class 的指针。例如,如果您在派生 class 中添加虚方法,指向基 class 的指针通常会根据指向 vtable 的指针的大小进行调整:

struct foo
{
    int whatever;
};

struct bar: public foo
{
    virtual void what();
};

bar layout:

pvtable ← bar * will point here

int ← foo * will point here

此外,在虚拟继承的情况下,将无法找出正确的指针调整,因此基 class 指针的实际值显式存储在每个派生的 class 实例中。

当您使用隐式转换、static_castdynamic_castGME_PCH * 转换为 PCH * 时,结果指向 PCH 子对象GME_PCH 对象。

然而,当您使用 reinterpret_castGME_PCH * 转换为 PCH * 时,结果保持地址不变:它仍然指向 GME_PCH 对象的内存位置,这通常是 GME 子对象所在的位置(编译器通常在内存中将第一个基数 class 排在第一位)。

您的无效尝试都等同于 reinterpret_cast<PCH *>(gme_pch)。它们失败了,因为你最终得到了一个 PCH * 类型的指针,它没有指向 PCH 对象。


如果有效,C 风格转换的行为类似于 static_cast,否则它的行为类似于 reinterpret_cast

代码(PCH *)gme_pchstatic_cast<PCH *>(gme_pch),但代码(PCH *)gmereinterpret_cast<PCH *>(gme)

要从 GME 到达 PCH,您需要使用 dynamic_cast,它能够测试 GME 是否实际上是 [= 的一部分15=] 与否。如果不是,转换将产生空指针。

PCH *pch = (PCH*)gme;

停止使用 C 风格转换。这行代码没有做任何合理的事情;它将 gme 的位重新解释为指向一件事的指针并表示 "what if those bits referred to another type".

但是GME和PCH子对象的地址相同,所以得到的指针是垃圾。那么其他一切都失败了。

该行也可以写成 PCH *pch = reinterpret_cast<PCH*>(gme); C 风格的强制转换可能是合理的,也可能是危险的。

PCH *pch = static_cast<PCH*>(static_cast<void*>(gme)); 违反了不同的规则;投射到 void* 时,你应该始终将 投射回完全相同的类型 作为你投射的类型。

在某些情况下,重新解释转换(或通过 void 错误触发)是有效的;但它们很脆弱,并且在标准中涉及相对深奥的文本。

总是 return 指向其确切原始类型的 void ptr,从不重新解释指向其他类型的强制转换或 C 样式强制转换指针。