[英]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)
,当然实际值可能不同)。
如果您正在使用虚拟继承(这可以保证公共基类只包含一次,例如,如果A
和B
继承自CommonBase
),则简单的指针转换变得更加复杂,并且编译器执行vtable查找以查找它应该用于指针转换的实际偏移量。
如果您使用的是Visual Studio,则可以在此行上创建断点并按Alt + 8查看反汇编,这将显示指针转换后的“魔术”。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.