当我们创建派生类的对象时, 为什么构造函数以从上到下的方式调用(首先是基础构造函数,然后是派生构造函数),并且是从底部到顶部的析构函数(第一个派生构造函数,然后是基础构造函数)
答案 0 :(得分:5)
要解释新手,请考虑您正在建造一座新建筑。
你建造地下室,一楼,二楼。 在摧毁时,你会摧毁二楼,一楼和地下室。
同样,对象的构造/破坏发生在C ++中。
希望这有助于理解。
答案 1 :(得分:1)
内存分配独立于调用构造函数和析构函数。构造派生对象时,将分配整个类型的内存。
然后,调用它的构造函数(不是基础构造函数)。但是,此构造函数首先调用基类构造函数。默认情况下,将调用base的默认构造函数,但您可以指定如何在派生构造函数中调用基本构造函数。如果不存在这样的并且您没有指定如何构造基类,那么这是一个编译错误。
class Base {
int x;
public:
Base() : x(42) {}
};
class Derived : public Base {
int y;
public:
Derived() : Base(), y(1337) {}
// is the same as: Derived() {}
};
这里,Base()
是不可能的(它不提供默认构造函数):
class Base {
int x;
public:
Base(int x) : x(x) {}
};
class Derived : public Base {
int y;
public:
Derived() : Base(), y(1337) {} // <-- error!
Derived() : Base(42), y(1337) {} // <-- ok!
};
正如您所看到的,从技术上讲,派生的构造函数是第一个被调用的构造函数。但是因为它在一开始就调用了基本构造函数,所以它实际上是另一种方式:构造了基类,然后派生类“添加”它自己的东西。
破坏只是反过来了:派生类的一个实例需要在基础被破坏之前清理它添加到基类的东西。
将此视为建造房屋:您必须首先建造基地,然后添加故事,最后是屋顶。在破坏时,首先移除屋顶,然后移除故事,最后摆脱基地。
答案 2 :(得分:0)
这是另一条规则的结果:在进入派生构造函数的主体之前调用所有基础构造函数,并在离开派生析构函数的主体后调用所有基础析构函数。
考虑这个(过度简化的)代码:
class BaseMember
{
public:
bool Pred();
};
class DerivedMember
{
public:
void DoFirst(bool);
void DoLast(bool);
};
class Base
{
public:
Base() {baseMember=new BaseMember;}
~Base(){delete baseMember;}
protected:
BaseMember *baseMember;
};
class Derived: public Base
{
public:
Derived()
{
derivedMember=new DerivedMember;
derivedMember->DoFirst(baseMember->Pred());
}
~Derived()
{
derivedMember->DoLast(baseMember->Pred());
delete derivedMember;
}
protected:
DerivedMember *derivedMember;
};
我们可以编写类似的东西,因为我们依赖于Base完全构造(所有Base的基础构造函数都已完成,所有Base成员构造函数也已完成,Base的构造函数体已完成),然后我们才进入Derived构造函数体。通过这种方式,我们可以使用知道它们构建的所有基础成员。当我们进入Derived析构函数的主体时,我们知道没有任何东西被破坏了,所以 我们可以使用所有基地的成员知道他们仍然存在,并将在以后被破坏。
构造函数/析构函数调用层次结构是此逻辑的结果。如果我们在输入Derived()的主体之前调用Base构造函数,并且Base本身是从BaseOfBase派生的,那么Base构造函数将在进入Base()的主体之前调用BaseOfBase()构造函数。如果我们在离开~Derived()析构函数体之后调用~Base()析构函数,那么~BaseOfBase()析构函数将在~Base()析构函数完成后调用。
在C ++中,多重继承(和虚拟继承)会进一步复杂化调用层次结构,但同样的逻辑也适用于这种情况。