多重继承和指针实现

时间:2013-04-24 07:28:24

标签: c++

给出以下代码:

namespace Example1 {

class A {
public:
    A() {}
    virtual ~A() {}
private:
    float data_A;
};

class B {
public:
    B() {}
    virtual ~B() {}
protected:
    float data_B;
};

class Derived : public A, public B {
public:
    Derived() {}
    virtual ~Derived() {}
protected:
    float data_Derived;
};

}

int main (void)
{
using namespace Example1;
B* pb = new Derived;
delete pb;
}

pb现在应该指向B对象的Derived部分。 但派生对象也派生自A,意味着它有A子对象..而A子对象应该是“第一”,因为Derived类是第一个继承自A

编译器如何批准?为了使其正常工作,它添加了什么?

还有,删除对象时如何正确释放内存?

6 个答案:

答案 0 :(得分:7)

简短的回答是:通过魔法。

中等答案是:你不用担心。标准说这是有效的,并且由编译器决定如何使其工作。

答案很长:由于这取决于您的编译器,请阅读编译器的文档!许多C ++编译器实现了Itanium C ++ ABI,所以这是一个开始。作为多态继承的一部分,每个类通常都有一个所谓的 vtable ,它存储了一堆函数指针,但它也存储了RTTI信息并加入了虚拟破坏和内存释放逻辑。想一想:delete pb;不仅要以正确的顺序调用正确的析构函数,还必须将正确的指针传递给释放函数。所有这些信息都包含在类层次结构的各种vtable中。

答案 1 :(得分:0)

§10.1/2

  

[注意:除非另有说明,否则推导顺序并不重要   通过构造函数(12.6.2)初始化的语义,清理   (12.4)和存储布局(9.2,11.1)。 - 后注]

因此

class Derived : public A, public B {
                ^^^^^^^^  ^^^^^^^^
                  first    second

然后调用派生构造函数。此外,析构函数将以相反的顺序调用。

Construction
A()
B()
D()

Destruction
~D()
~B()
~A()

答案 2 :(得分:0)

无关紧要,您在派生类声明中首先键入哪个类。 “A应该是第一个”是不正确的,因为它们同样是基类。唯一的区别是,首先调用哪个构造函数/析构函数(按顺序/颠倒顺序将它们声明为基类)。

您可以参考why multiple inheritance is a bad idea

也许适合你的是单继承,B类派生自A和C派生自B.

答案 3 :(得分:0)

来自法国网站的来源:C++ FAQ

  

Les constructeurssontasoplésdansl'ordre suivant:

le constructeur des classes de base héritées virtuellement en profondeur croissante et de gauche à droite ;
le constructeur des classes de base héritées non virtuellement en profondeur croissante et de gauche à droite ;
le constructeur des membres dans l'ordre de leur déclaration ;
le constructeur de la classe.
     

施工人员电话的顺序:

base class constructors from virtual inheritance (BFS style and from left to rigth);
base class constructors from non virtual inheritance (BFS style and from left to rigth);
instances constructor in the order of their declaration;
Class constructor

我猜测析构函数的调用顺序与此顺序相反。

答案 4 :(得分:0)

由于你的析构函数在这里(正确)是虚拟的,编译器完全没有问题。它只是调用正确的析构函数(在这种情况下为~Derived()),一切正常。

该标准不强制任何实现,但在许多计算机中使用的流行的是虚拟表。属于具有至少一个虚函数的类的所有对象(如此处所示)具有指向与其类对应的虚拟表(vptr)的指针(vtbl)。此vtbl引用了对象类的所有虚函数的正确覆盖。这里我说的是动态类(即:对象的真实类),而不是静态类(指针的类)。在这种情况下:

  • 动态类型pbDerived,因为它指向Derived实例
  • pb的静态类型为B,因为它被声明为B*

正如您所看到的,编译器在这种情况下并不关心静态类型。它将指令delete pb解释为此(伪代码):

pb->vptr->destructor();
operator delete(pb); // Not exactly, but this is immaterial in this case

出于我们的目的,您可以忽略第二行。运行时将遍历此链并找到要调用的正确析构函数(Derived())。 Derived()会依次以正确的顺序呼叫~A()~B()

答案 5 :(得分:0)

一般的答案是它有效,但实际的实现是依赖于编译器的,你不应该依赖于细节(但要记住这一点仍然是一件好事,因此在处理时你不会做出错误的假设指针)。

当使用多重继承时,简单的行(如B* pb = new Derived)会隐式更改指针的实际值。在这种特殊情况下,由于编译器知道它必须将Derived指针转换为B*,因此它确切地知道需要多少更改指针(例如sizeof(A),当然实际价值可能不同。)

如果您正在使用虚拟继承(这可以保证公共基类只包含一次,例如,如果AB都继承自CommonBase),那么简单的指针转换变得更加复杂,编译器执行vtable查找以找到它应该用于指针转换的实际偏移量。

如果您使用的是Visual Studio,则可以在此行上创建断点并按Alt + 8查看反汇编,这将显示指针转换后的“魔术”。