parents __vftable 之间的多重继承转换似乎已损坏

multiple inheritance casting between parents __vftable seems corrupted

我有以下问题, 让我们假设用户使用 RightParent 项目, 我还需要添加功能,我在 LeftParent 中有,

一些左右 parent 功能是纯虚拟的

传递给用户的实际项目继承自 rightParent(原始合同)和 leftParent(我在那里添加的合同)

它可能看起来像这样(在 VS 2010 上可编译且相同)

左 parent - 添加 contract/functionality(即我拥有 class)

#pragma once
struct CParent_Left
{
   CParent_Left(){}
   virtual void Foo() const =0;
   virtual ~CParent_Left(){}
};

正确 parent - 原始合同(实际上这是 Qt Object - 即我不拥有 class)

#pragma once
struct CParent_Right
{
   CParent_Right(){}
   virtual void Bar() const =0;
   virtual ~CParent_Right(){}
};

项目(存在多个项目,并且在一个程序中使用的所有项目都继承自 parents)

#pragma once

#include "CParent_Left.h"
#include "CParent_Right.h"

class CItem : public CParent_Left, public CParent_Right
{
   public:
      CItem(){}
      virtual void Foo() const override {}
      virtual void Bar() const override {}
      virtual ~CItem(){}
};

用户(这个通常继承自 Qt QObject),自己(和 Qt 私有内部)将 item 视为 rightParent,此处实现的添加功能有时需要将 item 视为 leftParent

#pragma once

#include "CParent_Left.h"
struct CParent_Right;

class CUser
{
   CParent_Right * data; // normally obtained from parent Qt class

   public:
      CUser(CParent_Right * data) : data(data){}

      void CallFoo()
      {
         ((CParent_Left*)data)->Foo(); // calls Bar()
      }

      virtual ~CUser(){}
};

示例主要

#include "CItem.h"
#include "CUser.h"

void main(int argc, char *argv[])
{
   auto item(new CItem());
   item->Foo(); // calls foo

   auto user(new CUser(item));
   user->CallFoo();
}

问题是, 当我拿着物品,从左边或右边调用方法时 parent,它们被正确调用,

当我将项目保存为(指向)rightParent(因为它可以从 Qt 小部件获得)时,我知道它是从两者继承的某些 CItem(CItem1,CITem2...)(所以 CItem 是 CLeft_Parent 并且 CItem 是 CRight_Parent),当我尝试将其转换为 leftParent 时,对 leftParent 方法的调用无法正常工作(在调试器中,跳转到 vftable 的地方就好像它已损坏一样)

有什么想法吗?

多重继承真是伟大而强大的东西。

但是,在使用转换时需要格外小心。不幸的是,你所做的是未定义的行为。

这里出了什么问题?

  • 首先,您通过向构造函数传递一个 CItem 指针来创建一个 CUser
  • 由于构造函数仅为 CParent_Right 定义,编译器会将 CItem 转换为 CParent_Right
  • 从这一刻起,传递给构造函数的指针就是指向CParent_Right子对象的指针。您不能再假设它是 CItem;并非所有 CParent_Right 子对象都必须 CItems !
  • 因此,转换 ((CParent_Left*)data)->Foo(); 不会像您期望的那样产生指向 CParent_Left 的指针。它只获取 CParent_Right 的当前地址并将其用作 CParent_Left。这是未定义的行为。对于您的编译器,它只获取导致这种不匹配的 vtable 中第一个函数的地址。

如何更正?

如果您确定构造函数的 CParent_Right 实际上是一个 CItem,您可以向下转换为该 CItem。这是合法的,并且做得很好。然后你可以将 CItem 上传到 CParent_Left 并且它会像你预期的那样工作:

    // ((CParent_Left*)data)->Foo(); // calls Bar() ===>  OUCH !!!!
    static_cast<CParent_Left*>(static_cast<CItem*>(data))->Foo(); // YES !!

请注意,我在这里使用了 static_cast,因为我确定我的对象类型,而且我可以使用这种构造,即使是非多态 类。但是,如果有疑问,dynamic_cast 将是更安全的选择。

对象布局和投射

一张小图来说明可能的铸件: