繁体   English   中英

多重继承和指针实现

[英]Multiple inheritance and pointer implementation

给出以下代码:

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现在应该指向Derived对象的B部分。 但派生对象也派生自A ,意味着它有A子对象..并且A子对象应该是“第一”,因为Derived类首先从A继承。

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

而且,删除对象时如何正确释放内存?

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

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

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

§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()

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

您可以参考为什么多继承是一个坏主意

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

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

Les constructeurssontopindlé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 

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

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

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

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

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM