CPP学习笔记—特殊成员函数
1. 引言:对象生命周期与 RAII
C++ 中,对象有明确的生命周期:它被创建,然后在使用结束后被销毁。构造函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数这六个函数(通常被称为特殊成员函数)就是用来管理这个生命周期的。它们控制着对象的:
- 创建 (Construction)
- 销毁 (Destruction)
- 拷贝 (Copying)
- 移动 (Moving)
它们是实现 C++ 核心设计哲学 RAII (Resource Acquisition Is Initialization) 的基石。RAII 意味着在对象的构造函数中获取资源(如内存、文件句柄、锁),并在其析构函数中释放资源,从而将资源的生命周期与对象的生命周期绑定在一起,避免资源泄漏。
2. 核心示例:一个简单的 Buffer 类
我们将使用一个简单的 Buffer 类来贯穿整个讲解。这个类在堆上分配了一块整数数组,因此它需要手动管理内存资源。
1 |
|
3. 构造函数 (Constructor)
- 目的:初始化一个新创建的对象,为其分配所需资源,并建立一个有效的初始状态。
- 特征:函数名与类名相同,没有返回类型。
- 何时调用:当一个新对象被创建时。
1 | // 在 Buffer 类中 |
- 重点:使用 成员初始化列表 (Member Initializer List)(即冒号
:后面的部分)来初始化成员。这比在构造函数体{}内赋值更高效,对于const成员或引用成员来说是必须的。
4. 析构函数 (Destructor)
- 目的:在对象生命周期结束时 清理 对象,释放其占有的资源。
- 特征:函数名前面有一个波浪号
~,没有返回类型,也没有参数。 - 何时调用:当对象离开其作用域、
delete一个指向对象的指针时,或程序结束时(对于全局/静态对象)。
1 | // 在 Buffer 类中 |
- 重点:如果你的类被用作基类,并且你希望通过基类指针
delete派生类对象,那么析构函数 必须 声明为virtual(virtual ~Buffer()),以防止资源泄漏。
5. 拷贝构造函数 (Copy Constructor)
- 目的:使用一个 已存在的同类对象 来 创建一个新的对象。这是“拷贝”语义的来源。
- 特征:参数是此类的一个
const引用。const是因为我们不应该修改源对象,引用&是为了避免无限递归的拷贝。 - 何时调用:
Buffer b2 = b1;或Buffer b2(b1);(初始化)- 函数按值传递对象:
void func(Buffer b); - 函数按值返回对象:
Buffer func();
1 | // 在 Buffer 类中 |
- 重点:如果你不提供,编译器会生成一个默认的拷贝构造函数,它执行 浅拷贝(只复制指针
_ptr的值,不复制其指向的数据),这会导致两个对象指向同一块内存,从而引发“重复释放”的严重错误。因此,对于管理资源的类,必须 实现深拷贝。
6. 拷贝赋值运算符 (Copy Assignment Operator)
- 目的:将一个 已存在的对象 的值赋给另一个 已存在的对象。
- 特征:通常返回一个指向当前对象的引用 (
*this) 以支持链式赋值 (a = b = c;)。 - 何时调用:
b2 = b1;(赋值,此时 b1 和 b2 都已经存在)。
1 | // 在 Buffer 类中 |
- 重点:自赋值检查 (
if (this == &other)) 是防止在b1 = b1;这种情况下,提前delete掉自己的资源导致后续无法拷贝。
7. “三之法则” (The Rule of Three)
这是一个经典的 C++ 设计准则:
如果你需要显式声明析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,那么你几乎肯定需要将这三个都声明。
我们的 Buffer 类就是完美范例:因为它需要自定义析构函数来释放内存,所以它也必须自定义拷贝操作来实现深拷贝。
8. 移动构造函数 (Move Constructor) - C++11
- 目的:从一个临时对象(右值,如函数返回值)“窃取”或“转移” 资源来构造一个新对象。这比深拷贝高效得多,因为它避免了内存的重新分配和数据的复制。
- 特征:参数是一个非
const的右值引用 (&&)。 - 何时调用:当用一个右值初始化对象时,例如
Buffer b(create_buffer());或Buffer b(std::move(another_buffer));
1 | // 在 Buffer 类中 |
- 重点:移动构造函数应该是
noexcept的,这向编译器承诺它不会抛出异常,使得标准库容器(如std::vector)在需要重新分配内存时可以安全地使用移动而非拷贝,从而获得巨大性能提升。
9. 移动赋值运算符 (Move Assignment Operator) - C++11
- 目的:将一个临时对象(右值)的资源 “窃取” 给一个已存在的对象。
- 特征:参数是右值引用
&&,返回*this。 - 何时调用:当将一个右值赋给一个已存在的对象时,例如
b = create_buffer();或b = std::move(another_buffer);
1 | // 在 Buffer 类中 |
10. “五之法则” (The Rule of Five) 与 “零之法则” (The Rule of Zero)
五之法则 (Rule of Five):C++11 后的扩展。如果一个类需要自定义析构、拷贝或移动操作中的任何一个,那么它可能需要全部五个。
零之法则 (Rule of Zero) - 现代 C++ 最佳实践:
你的类应该只负责业务逻辑,而将资源管理委托给专门的 RAII 类(如std::vector,std::string,std::unique_ptr,std::shared_ptr)。
如果遵循此法则,你的类通常 不需要 手动编写任何特殊成员函数,编译器自动生成的版本会做正确的事。使用零之法则重写
Buffer类:1
2
3
4
5
6
7
8
9
class ModernBuffer {
private:
std::vector<int> _data; // std::vector 内部已经正确实现了所有五个特殊函数!
public:
ModernBuffer(size_t size) : _data(size) {} // 简洁
// 不需要手动写析构、拷贝、移动函数!
};
11. 总结
| 函数名称 | 签名示例 | 目的 | 触发示例 (b1, b2 已存在) |
|---|---|---|---|
| 构造函数 | Buffer(size_t size); |
创建并初始化新对象 | Buffer b1(10); |
| 析构函数 | ~Buffer(); |
销毁对象,释放资源 | (自动调用) } 或 delete ptr; |
| 拷贝构造函数 | Buffer(const Buffer& other); |
用 other 创建一个 新对象 |
Buffer b3 = b1; |
| 拷贝赋值运算符 | Buffer& operator=(const Buffer& other); |
将 other 赋给一个 已存在对象 |
b2 = b1; |
| 移动构造函数 (C++11) | Buffer(Buffer&& other) noexcept; |
从临时对象 other 窃取资源 创建新对象 |
Buffer b3 = create_buffer(); |
| 移动赋值运算符 (C++11) | Buffer& operator=(Buffer&& other) noexcept; |
从临时对象 other 窃取资源 给已存在对象 |
b2 = std::move(b1); |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 星海流光!
评论









