代码之家  ›  专栏  ›  技术社区  ›  Stephan Lechner

使用多重继承和中间变量将动态强制转换回同一对象类型失败

  •  3
  • Stephan Lechner  · 技术社区  · 6 年前

    假设一个层次结构有两个不相关的多态类 PCH GME ,一个子类 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_pch公司 pch 是的。它还表明,变量中这两个指针的工作关系与它不工作的关系不同(即,取决于是否写入 GME_PCE : public GME, public PCH GME_PCE : public PCH, public GME , GME_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
    
    4 回复  |  直到 6 年前
        1
  •  4
  •   M.M    6 年前
    GME_PCH *gme_pch = new GME_PCH();
    
    GME *gme = gme_pch;
    PCH *pch = static_cast<PCH*>(static_cast<void*>(gme));
    

    是错误的当 GME_PCH 是从两个 GME 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型 是有效值,而 二氧化碳 不是。

        2
  •  2
  •   M.M    6 年前

    当你转换 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样式的cast的行为类似于 静态铸造 如果这是有效的,否则它的行为就像 重新解释演员 .

    守则 (PCH *)gme_pch static_cast<PCH *>(gme_pch) ,但是密码 (PCH *)gme reinterpret_cast<PCH *>(gme) 是的。

    到达 PCH公司 从一个 GME公司 你需要使用 动态铸造 ,它能够测试 GME公司 实际上是 GME_PCH公司 或者不。如果不是,则转换将产生空指针。

        3
  •  2
  •   Yakk - Adam Nevraumont    6 年前

    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* 你应该一直往后退 完全相同的类型 就像你投的一样。

    在某些情况下,重新解释cast(或通过void错误地跳闸)是可行的;但是它们是脆弱的,并且涉及到标准中相对深奥的文本。

    只需始终将void ptr返回到其原始类型,而不要重新解释其他类型的cast或C风格的cast指针。

        4
  •  1
  •   user7860670    6 年前

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

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

    酒吧布局:

    pvtable bar*将指向此处

    int foo*将指向此处

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