构造和析构
在C++中,构造函数和析构函数是类的特殊成员函数,分别用于对象的初始化和资源清理,它们由编译器自动调用,是面向对象编程中管理对象生命周期的核心机制。
this指针
编译器对类型编译的过程中分为三个部分
- **类型解析:**编译器首先对代码中的类型声明进行解析,识别类型的定义、成员结构(如类的成员变量和成员函数)、继承关系等信息。
- 函数识别:在语义分析阶段,编译器基于类型解析的结果,对程序中的函数返回类型、函数名、参数类型进行识别。
- **成员方法改写:**无论成员方法是公有私有,他会对非静态成员方法加入
常性this指针。
1 | class Pointer { |

构造函数(Constructor)
构造函数是创建对象时自动调用的成员函数,主要作用是初始化对象的成员变量(如给成员变量赋初值、分配动态内存等)。
核心特点
- 名字与类名完全相同:例如类名为
Person,构造函数名也必须是Person。 - 无返回值:不能写
void或其他返回类型(连return语句都不能带返回值)。 - 可重载:可以定义多个参数不同的构造函数(参数个数或类型不同),满足不同的初始化需求。
- 自动调用:创建对象时(如
Person p;或new Person();)由编译器自动调用,无需手动调用。 - 一次调用:生存期内构造函数只能被调用一次,但可以重载构造函数。
默认构造函数
如果用户没有定义任何构造函数,编译器会自动生成一个默认构造函数(无参数,函数体为空)。
但如果用户定义了构造函数(无论是否有参数),编译器不会再生成默认构造函数。
1 |
|
1 | 无参构造函数被调用 |
析构函数(Destructor)
析构函数是对象生命周期结束时自动调用的成员函数,主要作用是清理对象占用的资源(如释放动态内存、关闭文件句柄等)。
核心特点
- 名字为
~类名:例如类名为Person,析构函数名是~Person。 - 无返回值:同构造函数,不能写返回类型。
- 无参数:因此不能重载(一个类只能有一个析构函数)。
- 自动调用:对象销毁时(如局部对象离开作用域、
delete动态对象时)由编译器自动调用。
默认析构函数
如果用户没有定义析构函数,编译器会自动生成默认析构函数(函数体为空)。但如果类中使用了动态内存(如new分配的内存),必须手动定义析构函数释放资源,否则会导致内存泄漏。
1 |
|
输出:
1 | 构造函数:分配了5个int的内存 |
构造与析构的调用顺序
- 构造函数:创建对象时,先调用基类的构造函数,再调用成员对象的构造函数,最后调用自身的构造函数。
- 析构函数:对象销毁时,调用顺序与构造相反:先调用自身的析构函数,再调用成员对象的析构函数,最后调用基类的析构函数。
关键区别
| 特性 | 构造函数 | 析构函数 |
|---|---|---|
| 作用 | 初始化对象(分配资源) | 清理对象(释放资源) |
| 名字 | 与类名相同 | ~类名 |
| 参数 | 可以有(可重载) | 无(不可重载) |
| 调用时机 | 对象创建时 | 对象销毁时 |
| 默认版本 | 无自定义时自动生成 | 无自定义时自动生成 |
移动构造函数
移动构造函数是 C++11 引入的重要特性,其核心目的是通过 “资源转移” 替代 “资源复制”,解决临时对象(或即将销毁的对象)在传递过程中的性能开销问题。与拷贝构造函数不同,移动构造函数不复制对象的底层资源,而是 “窃取” 源对象的资源所有权,从而避免不必要的内存分配与数据拷贝。
C++中不存在缺省的移动构造,C++11 及以后标准中存在默认移动构造函数,但它的生成有严格条件
- 类中没有显式定义拷贝构造函数、拷贝赋值运算符(
operator=)、移动赋值运算符(移动版本operator=)或析构函数; - 类中没有显式定义移动构造函数(若显式定义,默认版本则不会生成)。
编译器会在满足以下条件时,才自动生成默认移动构造函数。
默认移动构造函数的行为是 “逐成员移动”:对对象的每个非静态成员,调用其对应的移动构造函数(若成员是类类型且有移动构造);对于基本类型(如int、指针等),则直接按位复制(类似浅拷贝,但后续通过 “置空源对象” 避免资源冲突)。
尽管存在默认移动构造函数,但多数场景下(尤其是包含动态资源的类),需要显式定义移动构造函数
默认移动构造可能被抑制
若类中显式定义了拷贝构造函数、拷贝赋值运算符或析构函数(常见于需要手动管理动态内存的场景),编译器会主动抑制默认移动构造函数的生成。此时若使用
std::move试图触发移动语义,编译器会退而求其次调用拷贝构造函数(若存在),导致本可避免的深拷贝开销。例如,一个包含动态数组的类若定义了析构函数(用于释放内存),默认移动构造不会生成,
std::move会触发拷贝而非移动:1
2
3
4
5
6
7
8
9
10
11
12class MyClass {
private:
int* data;
int size;
public:
MyClass(int s) : size(s), data(new int[s]) {}
~MyClass() { delete[] data; } // 显式定义析构函数,抑制默认移动构造
MyClass(const MyClass& other); // 显式定义拷贝构造,进一步抑制
};
MyClass a(10);
MyClass b = std::move(a); // 此处调用拷贝构造,而非移动构造(因默认移动被抑制)默认移动构造的 “浅移动” 不安全
即使默认移动构造被生成,对于包含指针(或动态资源)的类,其 “逐成员移动” 本质是 “复制指针地址”(类似浅拷贝),但不会自动将源对象的指针置空。这会导致源对象与目标对象的指针指向同一块内存,当源对象析构时,目标对象的指针会成为野指针,引发内存访问错误。
例如,默认移动构造对指针的处理:
1
2
3// 编译器生成的默认移动构造(伪代码)
MyClass::MyClass(MyClass&& other)
: size(other.size), data(other.data) {} // 仅复制指针地址,未置空源对象此时若源对象
other析构(释放data指向的内存),目标对象的data会变为野指针。
显式定义移动构造函数时,需遵循以下规则:
参数必须是右值引用语法为MyClass(MyClass&& other),右值引用(&&)专门用于匹配右值(如临时对象、std::move转换的左值),确保仅对 “即将销毁的对象” 进行资源窃取。
核心操作:转移资源并置空源对象移动构造的核心是 “窃取” 源对象的资源(如动态内存、文件句柄等),并将源对象的资源指针置空,避免源对象析构时释放已转移的资源。
**通常标记为noexcept**移动构造函数应尽可能标记为noexcept(不抛出异常),这是因为标准容器(如std::vector)在扩容时,若元素的移动构造函数是noexcept,会使用移动而非拷贝来转移元素,大幅提升性能;若可能抛异常,容器会退化为拷贝以保证异常安全。
示例
1 | class MyClass { |





