回到同一对象类型的dynamic_cast失败,具有多个继承和中间变量

时间:2018-07-22 22:03:12

标签: c++ c++11 multiple-inheritance dynamic-cast reinterpret-cast

假设一个层次结构具有两个不相关的多态类PCHGME,一个子类PCH_GME : public GME, public PCH和一个类型为gme_pch的对象PCH_GME*

为什么gme_pch的以下强制转换序列会“中断”强制转换为对象的原始类型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等于{有效变体中的{1}}和非有效变体中的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();   
}

4 个答案:

答案 0 :(得分:4)

GME_PCH *gme_pch = new GME_PCH();

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

是错误的。不要使用它。

通过尝试以下操作,您会发现为什么这是错误的:

PCH

您会注意到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; pch1是不同的。 pch2是有效值,而pch1不是有效值。

答案 1 :(得分:2)

当使用隐式转换,GME_PCH *PCH *static_cast转换为dynamic_cast时,结果指向对象的PCH子对象。 GME_PCH个对象。

但是,当您使用GME_PCH *PCH *转换为reinterpret_cast时,结果将使地址保持不变:它指向GME_PCH对象的内存位置,通常是GME子对象所在的位置(编译器通常将多态对象以内存中的第一个基类放在第一位)。

enter image description here

您的非工作尝试均等效于reinterpret_cast<PCH *>(gme_pch)。它们失败是因为您最终得到的类型为PCH *的指针没有指向PCH对象。


如果这是有效的,则C样式强制转换的行为类似于static_cast,否则类似于reinterpret_cast

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

要从PCH进入GME,您需要使用dynamic_cast,它可以测试GME是否实际上是{{1}的一部分} 或不。如果不是,则强制类型转换将产生空指针。

答案 2 :(得分:2)

PCH *pch = (PCH*)gme;

使用C样式转换停止。这行代码没有任何合理的意义;它将gme的位重新解释为指向一件事的指针,并说“如果这些位引用了另一种类型该怎么办”。

但是GME和PCH子对象的地址不相同,因此获得的指针是垃圾。然后其他一切都会失败。

该行也可以写为PCH *pch = reinterpret_cast<PCH*>(gme); C样式强制转换在任何情况下都是合理或危险的。

PCH *pch = static_cast<PCH*>(static_cast<void*>(gme));违反了另一条规则;投射到void*时,应始终将还原为与您投射时相同的类型

在某些情况下,重新解释强制转换(或通过void进行的错误跳转)会起作用;但是它们很脆弱,并且在标准中包含了比较深奥的文字。

总是总是将void ptr返回其确切的原始类型,并且永远不要重新解释对其他类型的强制转换或C样式强制转换指针。

答案 3 :(得分:1)

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

struct foo
{
    int whatever;
};

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

栏布局:

     
    

pvtable←栏*将指向此处

         

int←foo *将指​​向此处

  

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