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 将是更安全的选择。
对象布局和投射
一张小图来说明可能的铸件:
我有以下问题, 让我们假设用户使用 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 将是更安全的选择。
对象布局和投射
一张小图来说明可能的铸件: