当我们创建派生类的对象时,为什么构造函数是以自上而下的方式调用(首先是基构造函数,然后是派生构造函数)并且是自下而上调用的析构函数(先是派生构造函数,然后是基构造函数) )
3 回答
为了向新手解释,假设您正在建造一座新建筑。
你建造地下室,一楼,二楼。摧毁时,你摧毁 2 楼、1 楼和地下室。
同样,对象的构造/销毁发生在 C++ 中。
希望这有助于理解。
Memory allocation is independent of calling constructors and destructors. When constructing a derived object, memory for the whole type will be allocated.
Then, its constructor is called (not the base constructor). However, this constructor first calls the base class constructor. Per default, the base's default constructor gets called, but you can specify how to call the base constructor within the derived constructor. If no such exists and you don't specify how to construct the base class, it's a compile error.
class Base {
int x;
public:
Base() : x(42) {}
};
class Derived : public Base {
int y;
public:
Derived() : Base(), y(1337) {}
// is the same as: Derived() {}
};
Here, Base()
isn't possible (it doesn't provide a default constructor):
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!
};
So as you can see, technically the derived constructor is the first one being called. But since it calls the base constructor at the very beginning, it's effectively the other way around: The base is constructed, then the derived class "adds" its own stuff.
The destruction is simply the other way around: An instance of the derived class needs to clean up the stuff it adds to the base class before the base can be destructed.
Think of this simply as building a house: You have to build the base first, then add the stories, finally the roof. When destructing, first remove the roof, then the stories and finally get rid of the base.
这是另一个规则的结果:在进入派生构造函数的主体之前调用所有基构造函数,并且在离开派生析构函数的主体之后调用所有基析构函数。
考虑这个(过于简单的)代码:
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++ 中,多重继承(和虚拟继承)将进一步复杂调用层次结构,但同样的逻辑也适用于这种情况。