虚拟继承使应用程序崩溃

ato*_*erz 4 c++ crash virtual-inheritance visual-studio-2008 visual-studio

以下代码崩溃(访问冲突错误),因为我使用了虚拟继承。
AFAIK 虚拟继承通过强制使用类的单个实例来解决 Diamond 问题。在这种情况下,该类Derived仅继承了一个实例,IObject因此应该没有问题,但它会崩溃。

class IObject
{
public:
    virtual int getType()=0;
};
class Base : public IObject
{
protected:
    int val;
public:
    Base() { val = 1; }
    virtual int getType();
};
int Base::getType() { return val; }

class Derived : public virtual Base //If I remove the virtual keyword here problem is solved.
{
public:
    Derived() { val = 2; }
};

int getVal( void* ptr ) 
{
    return ((IObject*)ptr)->getType();
}

int main()
{
    void* ptr = new Derived();
    cout << getVal(ptr) << endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Mat*_* M. 5

问题在于强制转换链不正确:Derived* -> void* -> IObject*是由于混合 C 和 C++ 概念而导致的未定义行为。更具体地说,周围的规则void*是从 C 继承的,没有对对象和层次结构进行任何调整。

因此,解决方案是确保任何循环void*都是一个T -> void* -> T循环:始终通过相同的类型。因此,根据您的情况,您需要Derived* -> IObject* -> void* -> IObject*.


要理解为什么virtual继承会导致问题,您必须了解它如何具体表示的细节这是实现定义的)。让我们看一下可能的内存表示的示例(大致基于 Itanium ABI)。

线性非虚拟层次结构的实现就像通过组合一样:

struct Base { int a; };
struct Derived: Base { int b; };
struct SuperDerived: Derived { int c; };

+---+---+
| a | b |
+---+---+
^~~~~~~~~ Derived
    ^~~~~ Derived specific
^~~~~         Base

+---+---+---+
| a | b | c |
+---+---+---+
^~~~~~~~~~~~~ SuperDerived
        ^~~~~ SuperDerived specific
^~~~~~~~~     Derived
^~~~~         Base
Run Code Online (Sandbox Code Playgroud)

在这种情况下,&derived == &base并且&superderived == &derived一般来说(注意:如果一个层没有虚拟表而下一层有,那么这就会从屋顶上掉下来)。

具有多个基础的层次结构

struct Base1 { int a; };
struct Base2 { int b; };
struct Derived: Base1, Base2 { int c; };

+---+---+---+
| a | b | c |
+---+---+---+
^~~~~~~~~~~~~ Derived
        ^~~~~ Derived specific
    ^~~~~     Base2
^~~~~         Base1
Run Code Online (Sandbox Code Playgroud)

在这种情况下,&derived == &base1但是&derived != &base2,我们已经注意到基类不一定与其派生类具有相同的地址。

最后,让我们添加虚拟继承:

struct Object { int a; };
struct Base1: virtual Object { int b; };
struct Base2: virtual Object { int c; };
struct Derived: Base1, Base2 { int d; };

+---+---+
| b | a |
+---+---+
^~~~~~~~~ Complete Base1
^~~~~     Base1 specific
    ^~~~~ Object

+---+---+
| c | a |
+---+---+
^~~~~~~~~ Complete Base2
^~~~~     Base2 specific
    ^~~~~ Object

+---+---+---+---+
| b | c | d | a |
+---+---+---+---+
^~~~~~~~~~~~~~~~~ Complete Derived
        ^~~~~     Derived specific
^~~~~             Incomplete Base1
    ^~~~~         Incomplete Base2
            ^~~~~ Object
Run Code Online (Sandbox Code Playgroud)

这里的挑战是虚拟基地的单个实例应该在所有潜在基地之间共享。由于只有完整的对象知道将涉及哪些碱基,因此一个简单的选择是让完整的对象负责虚拟碱基的放置(它放置在尾部),并让虚拟表提供导航机制,在运行时,从Object派生类。

但是,请注意,在我们的设计中&base1 != &object&base2 != &object&derived != &object因为object被放置在尾部。

这就是为什么使用C++ 机器执行转换很重要, C++ 机器知道如何静态或动态(取决于情况)计算从一个基数到另一个基数时所需的指针调整。

注意:C++ 机器知道计算是静态的还是动态的,例如static_cast<Base1*>(&object)编译时错误,adynamic_cast在这里是必要的。