【发布时间】:2013-04-22 19:13:12
【问题描述】:
当我们创建派生类的对象时, 为什么构造函数以自上而下的方式调用(首先是基构造函数,然后是派生构造函数)并且是自下而上调用的析构函数(首先是派生构造函数,然后是基构造函数)
【问题讨论】:
-
如果类
Derived继承类Base和类Base有一个名为BaseProperty的属性......然后Derived尝试在其构造函数中设置该属性,您会发生什么?
当我们创建派生类的对象时, 为什么构造函数以自上而下的方式调用(首先是基构造函数,然后是派生构造函数)并且是自下而上调用的析构函数(首先是派生构造函数,然后是基构造函数)
【问题讨论】:
Derived 继承类Base 和类Base 有一个名为BaseProperty 的属性......然后Derived 尝试在其构造函数中设置该属性,您会发生什么?
为了向新手解释,假设您正在建造一座新建筑。
你建造地下室,一楼,二楼。 摧毁时,你摧毁 2 楼、1 楼和地下室。
同样的方式,对象的构造/销毁发生在 C++ 中。
希望这有助于理解。
【讨论】:
内存分配独立于调用构造函数和析构函数。构造派生对象时,会为整个类型分配内存。
然后,它的构造函数被调用(不是基础构造函数)。但是,此构造函数首先调用基类构造函数。默认情况下,会调用基类的默认构造函数,但您可以指定如何在派生构造函数中调用基类构造函数。如果没有这样的存在并且你没有指定如何构造基类,这是一个编译错误。
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!
};
如您所见,从技术上讲,派生构造函数是第一个被调用的构造函数。但由于它在一开始就调用基类构造函数,因此实际上是相反的:基类被构造出来,然后派生类“添加”它自己的东西。
破坏只是相反的方式:派生类的实例需要清理它添加到基类的东西,然后才能破坏基类。
把这简单地想象成建造一座房子:你必须先建造基地,然后添加楼层,最后是屋顶。破坏时,先拆除屋顶,再拆除楼层,最后拆除底座。
【讨论】:
这是另一个规则的结果:在进入派生构造函数体之前调用所有基构造函数,并且在离开派生析构函数体之后调用所有基析构函数。
考虑这个(过于简单的)代码:
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;
};
我们可以写这样的东西,因为在我们进入 Derived 构造函数体之前,我们依赖于 Base 的完全构造(所有 Base 的构造函数都已完成,所有 Base 成员构造函数也已完成,Base 的构造函数体也已完成)。这样,我们可以在知道它们已构建的情况下使用所有基础的成员。当我们进入 Derived 析构函数的主体时,我们知道还没有任何东西被破坏,所以 我们可以使用所有基地的成员,知道它们仍然存在并且稍后将被破坏。
构造函数/析构函数调用层次结构是这种逻辑的结果。如果我们在进入 Derived() 的主体之前调用 Base 的构造函数,而 Base 本身是从 BaseOfBase 派生的,那么 Base 的构造函数会在进入 Base() 的主体之前调用 BaseOfBase() 的构造函数。如果我们在离开 ~Derived() 析构函数体后调用 ~Base() 析构函数,那么 ~BaseOfBase() 析构函数将在 ~Base() 析构函数完成后被调用。
在 C++ 中,多重继承(和虚拟继承)会使调用层次结构更加复杂,但同样的逻辑也适用于这种情况。
【讨论】: