给出以下代码:
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
。
编译器如何批准?为了使其正常工作,它添加了什么?
还有,删除对象时如何正确释放内存?
答案 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
引用了对象类的所有虚函数的正确覆盖。这里我说的是动态类(即:对象的真实类),而不是静态类(指针的类)。在这种情况下:
pb
为Derived
,因为它指向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)
,当然实际价值可能不同。)
如果您正在使用虚拟继承(这可以保证公共基类只包含一次,例如,如果A
和B
都继承自CommonBase
),那么简单的指针转换变得更加复杂,编译器执行vtable查找以找到它应该用于指针转换的实际偏移量。
如果您使用的是Visual Studio,则可以在此行上创建断点并按Alt + 8查看反汇编,这将显示指针转换后的“魔术”。