2013-04-24 76 views
3

考虑下面的代码:多重继承和指针实现

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继承。

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

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

+1

您已经创建了一个Derived对象,您还创建了一个指向B的指针,并将Derived的地址指定给它。这些是独立的事实。在运行时,对象的类型不会因为将其地址指定给基本类型的指针而丢失。你可以从B *中dynamic_cast到Derived *,它会成功。 “怎么?”,你问。那么,这是“如何完成”。如果编译器无法保证这一点,它将不符合C++标准。 – 2013-04-24 07:37:08

回答

7

简短的回答是:靠魔法。

中等答案是:这不适合你担心。该标准说这是有效的,编译器需要找出一种方法来使其工作。

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

+2

克拉克第三定律:*任何足够先进的技术与魔法无法区分* – 2013-04-24 08:27:26

+0

非常感谢:) – Rouki 2013-04-24 09:30:54

+0

@MatthieuM .:“大公司禁止的任何C++功能与魔法无法区分”? :-) – 2013-04-24 13:18:14

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

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

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

也许这可能是非常适合你的是单继承,B类从A派生和C从派生B.从法国网站

0

来源:C++ FAQ

莱斯constructeurs SONTappelés丹斯骑士勋章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 

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

0

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

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

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

正如你可以看到,在这种情况下,编译器不关心静态类型。它解释指令delete pb,因为这(伪):

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

你可以无视我们的目的,第二行。运行时将通过此链并找到正确的析构函数调用(Derived())。 Derived()将依次以正确顺序呼叫~A()~B()

+1

正确的术语是“* pb'的静态类型是'B','* pb'的动态类型是'Derived''。 – 2013-04-24 08:15:58

+0

@KerrekSB你说得对,我正在纠正它。 – Gorpik 2013-04-24 08:22:14

0

一般的答案是,它的工作原理,但实际执行是编译器相关的,你不应该依赖于细节(但它仍然是要记住这样处理,当你不做出不正确的假设是好事与指针)。

当使用多重继承,简单的线如B* pb = new Derived隐含改变指针的实际值。在这种特殊情况下,由于编译器知道它有一个Derived指针转换为B*,它通过它需要多少改变指针确切地知道(例如sizeof(A),当然实际值可能是不同的)。

如果使用虚拟继承(这是保证一个共同的基类仅包含一次,例如,如果两个ABCommonBase继承),然后简单的指针转换变得更加复杂,并且编译器一个虚函数表查找来查找它应该用于指针转换的实际偏移量。

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