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

时间:2015-06-27 15:12:02

标签: c++ inheritance virtual multiple-inheritance

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

左右父功能中的一些是纯虚拟

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

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

左父母 - 添加合同/功能(即我拥有该课程)

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

正确的父母 - 原始合同(实际上这是Qt对象 - 即我不拥有该课程)

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

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

#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私人内部)上将项目视为rightParent,此处实现的添加功能有时需要将项目视为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();
}

问题是, 当我持有该项目,并从左侧或右侧父项调用方法时,它们被正确调用,

当我把item指定为(指向)rightParent(因为它可以从Qt小部件获得)时,我知道它是从两者继承的一些CItem(CItem1,CITem2 ...)(所以CItem是CLeft_Parent也是CItem是CRight_Parent),当我尝试将它强制转换为leftParent时,对leftParent方法的调用无法正常工作(在调试器中,跳转到vftable的某处就好像它被破坏了一样)

任何想法?

1 个答案:

答案 0 :(得分:1)

多重继承是非常强大的东西。

然而,使用铸造时需要特别小心。不幸的是,你所做的是未定义的行为。

这里出了什么问题?

  • 首先,通过向构造函数传递CUser指针来创建CItem
  • 由于构造函数仅为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将是一个更安全的替代方案。

对象布局和投射

用于说明可能的铸件的小图片:

enter image description here