c++类的构造析构顺序
1 前言
程序的正确运行依赖于对变量生命周期的管理,但是类的构造和析构顺序有时非常隐蔽,控制不好可能会引发不可预知的错误,所以本文探讨一下c++类的构造和析构顺序。
2 构造析构顺序的影响因素
构造析构顺序主要受类与类之间的关系和类的作用域的影响。
- 类与类之间的关系
- 继承关系
- 单继承
- 多继承
- 菱形继承
- 包含关系/成员变量,比如A是B的成员变量
- 声明的先后顺序
- 继承关系
- 类的作用域
- 全局变量
- 静态变量
- 局部变量
结合以上因素,通过设计实验来验证构造析构的顺序,回答以下几个问题:
- 父类和子类的构造析构顺序
- 父类和成员变量的声明顺序对构造析构顺序的影响
- 作用域与构造析构顺序的关系
3 实验
3.1 实验设计
/**
* 测试构造函数和析构函数的顺序,包含以下6种情况
* 1. 单继承
* 2. 多继承
* 3. 菱形继承
* 4. 包含成员变量
* 5. 声明顺序和构造/析构顺序
* 6. 既有继承又有成员变量
* */
#include <iostream>
#include <memory>
class Base {
public:
Base() {
std::cout << "Base: constructor" << std::endl;
}
virtual ~Base() {
std::cout << "~Base: destructor" << std::endl;
}
};
class A : public Base {
public:
A() {
std::cout << "A: constructor" << std::endl;
}
virtual ~A() {
std::cout << "~A: destructor" << std::endl;
}
};
class B : public Base {
public:
B() {
std::cout << "B: constructor" << std::endl;
}
virtual ~B() {
std::cout << "~B: destructor" << std::endl;
}
};
class AB : public A, public B {
public:
AB() {
std::cout << "AB: constructor" << std::endl;
}
virtual ~AB() {
std::cout << "~AB: destructor" << std::endl;
}
};
class BA : public B, public A {
public:
BA() {
std::cout << "BA: constructor" << std::endl;
}
virtual ~BA() {
std::cout << "~BA: destructor" << std::endl;
}
};
class MemberA {
public:
MemberA() {
std::cout << "MemberA: constructor" << std::endl;
}
virtual ~MemberA() {
std::cout << "~MemberA: destructor" << std::endl;
}
};
class MemberB {
public:
MemberB() {
std::cout << "MemberB: constructor" << std::endl;
}
virtual ~MemberB() {
std::cout << "~MemberB: destructor" << std::endl;
}
};
class HasMemberAB {
public:
HasMemberAB()
: mb_(new MemberB()),
ma_(new MemberA()) {
std::cout << "HasMemberAB: constructor" << std::endl;
}
virtual ~HasMemberAB() {
std::cout << "~HasMemberAB: destructor" << std::endl;
}
private:
std::shared_ptr<MemberA> ma_;
std::shared_ptr<MemberB> mb_;
};
class HasMemberBA {
public:
HasMemberBA()
: ma_(new MemberA()),
mb_(new MemberB()) {
std::cout << "HasMemberBA: constructor" << std::endl;
}
virtual ~HasMemberBA() {
std::cout << "~HasMemberBA: destructor" << std::endl;
}
private:
std::shared_ptr<MemberB> mb_;
std::shared_ptr<MemberA> ma_;
};
class DerivedAndMember : public A, public B {
public:
DerivedAndMember() {
std::cout << "DerivedAndMember: constructor" << std::endl;
}
virtual ~DerivedAndMember() {
std::cout << "~DerivedAndMember: destructor" << std::endl;
}
private:
MemberA ma_;
MemberB mb_;
};
void Test01_SingleDerived() {
std::cout << "测试01:单继承 {" << std::endl;
{
A a;
}
std::cout << "}\n\n" << std::endl;
}
void Test02_MultiDerived() {
std::cout << "测试02:多继承 {" << std::endl;
{
std::cout << "case 1: 先继承A,再继承B" << std::endl;
AB ab;
}
{
std::cout << "case 2: 先继承B,再继承A" << std::endl;
BA ba;
}
std::cout << "}\n\n" << std::endl;
}
void Test03_HasMember() {
std::cout << "测试03:包含成员变量 {" << std::endl;
{
std::cout << "case 1: 先声明A,再声明B" << std::endl;
HasMemberAB hasMemberAB;
}
{
std::cout << "case 2: 先声明B,再声明A" << std::endl;
HasMemberBA hasMemberBA;
}
std::cout << "}\n\n" << std::endl;
}
void Test04_DerivedAndMember() {
std::cout << "测试04:既有多继承,又有成员变量 {" << std::endl;
{
DerivedAndMember dm;
}
std::cout << "}\n\n" << std::endl;
}
int main() {
Test01_SingleDerived();
Test02_MultiDerived();
Test03_HasMember();
Test04_DerivedAndMember();
return 0;
}
3.2 实验结果
// output
测试01:单继承 {
Base: constructor
A: constructor
~A: destructor
~Base: destructor
}
/**
* 结论:
* 单继承情况下,
* - 构造:先构造父类,再构造子类
* - 析构:先析构子类,再析构父类
*/
// output
测试02:多继承 {
case 1: 先继承A,再继承B
Base: constructor
A: constructor
Base: constructor
B: constructor
AB: constructor
~AB: destructor
~B: destructor
~Base: destructor
~A: destructor
~Base: destructor
case 2: 先继承B,再继承A
Base: constructor
B: constructor
Base: constructor
A: constructor
BA: constructor
~BA: destructor
~A: destructor
~Base: destructor
~B: destructor
~Base: destructor
}
/**
* 结论:
* 多继承情况下,
* - 构造:父类的构造顺序和声明顺序一致(先声明先构造)
* - 析构:父类的析构顺序和声明顺序相反(先声明后析构,或者说先构造的后析构)
*/
// output
测试03:包含成员变量 {
case 1: 先声明A,再声明B
MemberA: constructor
MemberB: constructor
HasMemberAB: constructor
~HasMemberAB: destructor
~MemberB: destructor
~MemberA: destructor
case 2: 先声明B,再声明A
MemberB: constructor
MemberA: constructor
HasMemberBA: constructor
~HasMemberBA: destructor
~MemberA: destructor
~MemberB: destructor
}
/**
* 结论:
* 类中包含成员的情况下
* - 构造:先构造成员变量,再构造自身
* 类中成员变量的构造顺序是:先声明先构造,和构造函数中的初始化列表的顺序无关
* - 析构:先运行类的析构函数,在析构成员,成员的析构顺序是:先声明后析构;
*/
// output
测试04:既有多继承,又有成员变量 {
Base: constructor
A: constructor
Base: constructor
B: constructor
MemberA: constructor
MemberB: constructor
DerivedAndMember: constructor
~DerivedAndMember: destructor
~MemberB: destructor
~MemberA: destructor
~B: destructor
~Base: destructor
~A: destructor
~Base: destructor
}
}
/**
* 结论:
* 既有继承,又有成员变量的情况下
* - 构造:先构造父类,再构造成员类,最后构造自身(子类)
* - 析构:先析构本身(子类),再析构成员,最后再析构父类
*/
4 结论
通过以上4个实验,可以得出最终结论:
- 构造顺序
- a. 先父类,再成员变量(类),最后自身(父类->成员类->自身)
- 在多继承多成员变量的情况下,都是先声明先构造
- 析构顺序
- 先自身,再成员变量,最后父类(自身->成员类->父类)
- 在多继承多成员的情况下,都是先声明后析构
- 或者说,析构顺序和构造顺序相反,先构造的后析构
- 作用域
- 在不考虑引用计数的情况下,类出了作用域就会被析构