派生类的构造顺序
本节阅读量:
在上一课关于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++会分阶段构造派生类:从最基本的类开始(位于继承树顶部),最终结束于当前子类(位于继承树底部)。在构造每个类时,会调用该类的适当构造函数来初始化对象的对应部分。您会注意到,本节中的示例类都使用了基类默认构造函数(为了简单起见)。在下一课中,我们将更仔细地研究构造函数在构造派生类过程中的作用(包括如何显式选择派生类要使用的基类构造函数)。