24-3 派生类的构造顺序
在上节关于24.2 节 C++基本继承的课程中,你了解到类可以从其他类继承成员和函数。本节我们将深入探讨派生类实例化时构造函数的执行顺序。
首先,让我们引入几个新类来阐明关键概念。
class Base
{
public:
int m_id {};
Base(int id=0)
: m_id { id }
{
}
int getId() const { return m_id; }
};
class Derived: public Base
{
public:
double m_cost {};
Derived(double cost=0.0)
: m_cost { cost }
{
}
double getCost() const { return m_cost; }
};
在此示例中,派生类 Derived 继承自基类 Base。

由于派生类继承了基类的函数和变量,你可能会认为基类的成员被复制到了派生类中。但事实并非如此。相反,我们可以将派生类视为由两部分组成的类:一部分是派生类本身,另一部分则是基类。

你已经看到许多实例,展示了当我们实例化一个普通(非派生)类时会发生什么:
int main()
{
Base base;
return 0;
}
基类是一个非派生类,因为它不继承自任何其他类。C++ 为基类分配内存后,会调用基类的默认构造函数进行初始化。
现在让我们看看实例化派生类时会发生什么:
int main()
{
Derived derived;
return 0;
}
如果你亲自尝试这个操作,你不会发现它与之前实例化非派生类 Base 的示例有何不同。但在幕后,事情的发生方式略有不同。如前所述,派生类本质上由两部分构成:基类部分和派生类部分。C++构造派生对象时采用分阶段方式:首先构造继承树顶端的基类,随后按顺序构造每个子类,直至最后构造继承树底端的派生类。
因此当实例化派生类时,首先构造其基类部分(使用基类的默认构造函数)。基类部分完成后,再构造派生类部分(使用派生类的默认构造函数)。此时已无更多派生类,构造过程即告完成。
这个过程其实很容易用图示说明。
#include <iostream>
class Base
{
public:
int m_id {};
Base(int id=0)
: m_id { id }
{
std::cout << "Base\n";
}
int getId() const { return m_id; }
};
class Derived: public Base
{
public:
double m_cost {};
Derived(double cost=0.0)
: m_cost { cost }
{
std::cout << "Derived\n";
}
double getCost() const { return m_cost; }
};
int main()
{
std::cout << "Instantiating Base\n";
Base base;
std::cout << "Instantiating Derived\n";
Derived derived;
return 0;
}
该程序产生以下结果:

如你所见,在构造派生类时,其基类部分会优先完成构造。这符合逻辑:子类无法脱离父类而存在。这种做法也更安全:子类常需调用父类的变量和函数,而父类对子类一无所知。先实例化父类可确保派生类创建时,父类的变量已完成初始化并可供使用。
继承链的构造顺序
有时类会从其他类派生,而这些类本身又从其他类派生。例如:
#include <iostream>
class A
{
public:
A()
{
std::cout << "A\n";
}
};
class B: public A
{
public:
B()
{
std::cout << "B\n";
}
};
class C: public B
{
public:
C()
{
std::cout << "C\n";
}
};
class D: public C
{
public:
D()
{
std::cout << "D\n";
}
};
请记住,C++ 总是先构造“最顶层”或“最基础”的类。随后它会按顺序遍历继承树,依次构造每个派生类。
以下是一个简短程序,演示了沿继承链创建的顺序。
int main()
{
std::cout << "Constructing A: \n";
A a;
std::cout << "Constructing B: \n";
B b;
std::cout << "Constructing C: \n";
C c;
std::cout << "Constructing D: \n";
D d;
}
此代码输出以下内容:

结论
C++分阶段构造派生类,从最基类(位于继承树顶端)开始,直至最子类(位于继承树底端)。每个类构造时,都会调用该类对应的构造函数来初始化该部分。
请注意,本节示例类均采用了基类的默认构造函数(为简化起见)。在下一课中,我们将深入探讨构造函数在派生类构造过程中的作用(包括如何明确指定派生类应使用的基类构造函数)。

浙公网安备 33010602011771号