派生类的构造顺序
本节阅读量:
在上一课关于C++中的基本继承的内容中,学习了类可以从其他类继承成员和函数。在本课中,我们将更仔细地看一看在实例化派生类时发生的构造顺序。
首先,让我们介绍一些新的类,这些类将帮助我们说明一些重要的观点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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类派生的。
Derived继承
由于Derived从Base继承函数和变量,因此您可以假设Base的成员被复制到Derived。然而,这不是真的。相反,我们可以将“Derived”视为一个由两部分组成的类:一部分“Derived”,一部分“Base”。
Derived结构
之前已经有大量例子,说明当我们实例化一个普通(非派生)类时会发生什么:
1
2
3
4
5
6
|
int main()
{
Base base;
return 0;
}
|
Base是一个非派生类,因为它不从任何其他类继承成员。C++为Base分配内存,然后调用Base的默认构造函数来进行初始化。
现在让我们看看实例化派生类时会发生什么:
1
2
3
4
5
6
|
int main()
{
Derived derived;
return 0;
}
|
实际运行时,您不会注意到与前面实例化非派生类Base的示例有任何不同。但在幕后,情况略有不同。如上所述,Derived实际上是两个部分:Base部分和Derived部分。当C++构造派生对象时,它分阶段进行。首先,构造最基本的基类(在继承树的顶部)。然后按顺序构造每个子类,直到最后构造当前的子类(位于继承树的底部)。
因此,当我们实例化Derived的实例时,首先构造Base部分(使用Base默认构造函数)。一旦Base部分完成,就构建Derived部分(使用Derived默认构造函数)。此时,没有更底层的派生类,因此工作结束。
这个过程实际上很容易说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
#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;
}
|
此程序产生以下结果:
1
2
3
4
5
|
Instantiating Base
Base
Instantiating Derived
Base
Derived
|
当构造Derived时,首先构造Derived的Base部分。这是有意义的:从逻辑上讲,没有父级,子级就不能存在。这也是做事情的安全方法:子类通常使用来自父类的变量和函数,但父类不知道子类的任何信息。首先实例化父类可以确保在创建派生类并准备使用它们时,这些变量已经初始化。
继承链的构造顺序
有时类是从其他类派生的,这些类本身也是从其他类派生的。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
#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++总是首先构造“第一个”或“最基本”类。然后,它按顺序遍历继承树,并构造每个连续的派生类。
下面是一个简短的程序,它说明了整个继承链的创建顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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;
}
|
此代码打印以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Constructing A:
A
Constructing B:
A
B
Constructing C:
A
B
C
Constructing D:
A
B
C
D
|
结论
C++分阶段构造派生类,从最基本的类开始(位于继承树顶部),最终结束与当前的子类(在继承树的底部)。在构造每个类时,调用该类的适当构造函数来初始化类的该部分。您将注意到,本节中的示例类都使用了基类默认构造函数(为了简单起见)。在下一课中,我们将更仔细地研究构造函数在构造派生类过程中的作用(包括如何显式选择希望派生类使用的基类构造函数)。