C++中对象的构造和析构顺序遵循明确的规则,不同场景下的顺序存在差异,理解这些规则是正确管理对象资源的基础。

基础单个对象的构造与析构顺序
对于没有继承、没有成员对象的单个普通类对象,构造和析构的顺序是最基础的场景。构造时先执行构造函数体内的逻辑,析构时先执行析构函数体内的逻辑,整体顺序相反。
我们可以通过以下代码观察单个对象的构造析构过程:
#include <iostream>
using namespace std;
class BaseObj {
public:
BaseObj() {
cout << "BaseObj 构造函数执行" << endl;
}
~BaseObj() {
cout << "BaseObj 析构函数执行" << endl;
}
};
int main() {
BaseObj obj;
return 0;
}
运行上述代码,输出结果为:
BaseObj 构造函数执行 BaseObj 析构函数执行
组合对象的构造与析构顺序
当一个类包含其他类的对象作为成员时,就形成了组合关系。组合对象的构造顺序是先构造所有成员对象,再执行当前类的构造函数体;析构顺序则完全相反,先执行当前类的析构函数体,再析构所有成员对象,且成员对象的析构顺序和声明顺序相反。
示例代码如下:
#include <iostream>
using namespace std;
class MemberA {
public:
MemberA() {
cout << "MemberA 构造函数执行" << endl;
}
~MemberA() {
cout << "MemberA 析构函数执行" << endl;
}
};
class MemberB {
public:
MemberB() {
cout << "MemberB 构造函数执行" << endl;
}
~MemberB() {
cout << "MemberB 析构函数执行" << endl;
}
};
class ComboObj {
private:
// 成员声明顺序为MemberB在前,MemberA在后
MemberB b;
MemberA a;
public:
ComboObj() {
cout << "ComboObj 构造函数执行" << endl;
}
~ComboObj() {
cout << "ComboObj 析构函数执行" << endl;
}
};
int main() {
ComboObj obj;
return 0;
}
运行输出结果为:
MemberB 构造函数执行 MemberA 构造函数执行 ComboObj 构造函数执行 ComboObj 析构函数执行 MemberA 析构函数执行 MemberB 析构函数执行
可以看到成员对象的构造顺序和声明顺序一致,析构顺序和声明顺序相反,和初始化列表中的顺序无关。
继承关系下的对象构造与析构顺序
存在继承关系时,构造顺序为先构造基类对象,再构造派生类对象;析构顺序相反,先析构派生类对象,再析构基类对象。如果有多层继承,则按照继承层级从最顶层基类到最底层派生类依次构造,析构时反向进行。
以下是单继承的示例代码:
#include <iostream>
using namespace std;
class Parent {
public:
Parent() {
cout << "Parent 构造函数执行" << endl;
}
~Parent() {
cout << "Parent 析构函数执行" << endl;
}
};
class Child : public Parent {
public:
Child() {
cout << "Child 构造函数执行" << endl;
}
~Child() {
cout << "Child 析构函数执行" << endl;
}
};
int main() {
Child obj;
return 0;
}
运行输出结果为:
Parent 构造函数执行 Child 构造函数执行 Child 析构函数执行 Parent 析构函数执行
不同作用域对象的构造与析构顺序
对象的作用域也会影响构造和析构的时机,常见的作用域包括全局、静态局部、普通局部:
- 全局对象:在程序启动前构造,程序结束时析构
- 静态局部对象:第一次执行到定义语句时构造,程序结束时析构
- 普通局部对象:进入作用域时构造,离开作用域时析构
示例代码验证不同作用域的顺序:
#include <iostream>
using namespace std;
class GlobalObj {
public:
GlobalObj() {
cout << "全局对象 构造" << endl;
}
~GlobalObj() {
cout << "全局对象 析构" << endl;
}
};
GlobalObj g_obj; // 全局对象
void testFunc() {
static StaticLocalObj s_obj; // 静态局部对象
LocalObj l_obj; // 普通局部对象
}
class StaticLocalObj {
public:
StaticLocalObj() {
cout << "静态局部对象 构造" << endl;
}
~StaticLocalObj() {
cout << "静态局部对象 析构" << endl;
}
};
class LocalObj {
public:
LocalObj() {
cout << "普通局部对象 构造" << endl;
}
~LocalObj() {
cout << "普通局部对象 析构" << endl;
}
};
int main() {
cout << "进入main函数" << endl;
testFunc();
cout << "离开main函数前" << endl;
return 0;
}
运行输出结果为:
全局对象 构造 进入main函数 静态局部对象 构造 普通局部对象 构造 普通局部对象 析构 离开main函数前 静态局部对象 析构 全局对象 析构
数组对象的构造与析构顺序
对象数组的构造顺序是按照下标从0到n-1依次构造,析构顺序则相反,从n-1到0依次析构。
示例代码如下:
#include <iostream>
using namespace std;
class ArrayObj {
public:
ArrayObj(int id) {
this->id = id;
cout << "ArrayObj " << id << " 构造" << endl;
}
~ArrayObj() {
cout << "ArrayObj " << id << " 析构" << endl;
}
private:
int id;
};
int main() {
ArrayObj arr[3] = {ArrayObj(1), ArrayObj(2), ArrayObj(3)};
return 0;
}
运行输出结果为:
ArrayObj 1 构造 ArrayObj 2 构造 ArrayObj 3 构造 ArrayObj 3 析构 ArrayObj 2 析构 ArrayObj 1 析构
常见注意事项
- 不要在构造函数的初始化列表中改变成员对象的构造顺序,构造顺序只和成员声明的顺序有关
- 析构函数不要抛出异常,否则可能导致后续对象析构失败,引发未定义行为
- 如果基类没有虚析构函数,通过基类指针删除派生类对象时,派生类的析构函数不会执行,可能导致资源泄漏
- 全局对象和静态对象的构造顺序在不同编译单元之间没有明确的顺序保证,不要依赖跨编译单元的全局对象构造顺序