13. Copy Control¶
Copy control 不止管理 copy, 还管理 move 和 destroy。
Copy control 专门指负责对象资源管理与生命周期的五个特殊成员函数:copy constructor, move constructor, copy-assignment operator, move-assignment operator, 和 destructor。
The Copy Constructor¶
什么是 Copy Constructor?¶
Copy constructor 是一种构造函数,用同类型的另一个对象来初始化新对象。
copy constructor 的第一个参数必须是对同类对象的引用:
什么时候会用到 copy initialization?¶
-
用一个对象初始化另一个对象
-
形参不是引用,会触发 copy initialization
-
返回一个对象时(不是引用或指针),也需要用 copy constructor 构造返回值对象
-
大多数 STL 容器操作插入已有对象时
-
创建临时对象来满足参数类型
什么时候会隐式生成 copy constructor?¶
如果类的所有成员都可拷贝,编译器自动生成一个 memberwise 的拷贝构造函数,它的行为是:按类中成员出现的顺序,成员逐个拷贝,每个非 static 成员用它自己的拷贝规则。
即使编译器可能跳过 copy/move constructor,copy/move constructor 本身仍必须合法(存在并且可访问)。
什么时候不会自动生成?¶
如果某个成员不可拷贝,整个类的拷贝构造函数会被禁止。
explicit 构造函数禁止隐式转换。
Exercise 13.5: Implement the copy constructor
The Copy-Assignment Operator¶
Copy-assignment operator 是对象赋值时调用的成员函数,默认按成员逐个赋值,通常返回左操作数的引用以支持链式赋值。
如果没有自己定义,编译器会合成一个 synthesized copy-assignment operator,逐个成员赋值,类似 copy constructor,但作用对象是已存在对象。
The Destructor¶
构造函数分配或初始化资源,析构函数负责销毁对象释放资源。析构函数没有参数,也没有返回值。
- 没写析构函数 → 编译器合成默认析构函数。
- 析构顺序:先执行函数体,然后按与初始化相反顺序销毁成员。
析构函数什么时候被调用?¶
很多普通类型(局部变量、STL 对象、智能指针)内部自己管理内存(RAII),比如:
但是对于手动管理资源的类,如果不写析构函数,这些资源不会自动释放,就会泄漏:
class HasPtr {
std::string *ps;
public:
HasPtr(const std::string &s = "") : ps(new std::string(s)) {}
~HasPtr() { delete ps; } // 必须写析构函数释放内存
};
注意¶
-
引用离开作用域不会触发析构函数。 举例:
- r 离开作用域时,只是引用变量被销毁(其实引用本身只是别名,没有内存需要释放)
- s 的析构函数会在作用域结束时自动调用
-
指针变量(除了智能指针以外)离开作用域,只销毁指针本身(即存储地址的内存),指针指向的对象的析构函数不会自动调用。
Exercise 13.3
#include <iostream>
struct X {
X() { std::cout << "X()" << std::endl; }
X(const X&) { std::cout << "X(const X&)" << std::endl; }
X& operator=(const X&) {
std::cout << "operator=" << std::endl;
return *this;
}
~X() { std::cout << "~X()" << std::endl; }
};
int main() {
X a; // X()
X b = a; // X(const X&)
X c;
c = b; // operator=
X *p = new X; // X()
delete p; // ~X()
X arr[2]; // X() X()
}
The Rule of Three/Five¶
1. Classes That Need Destructors Need Copy and Assignment¶
这段代码有什么问题?
class HasPtr {
public:
HasPtr(const std::string &s = std::string())
: ps(new std::string(s)), i(0) { }
~HasPtr() { delete ps; } // WRONG: 需要 copy constructor 和 copy-assignment operator
private:
std::string *ps;
int i;
};
没有自定义的 copy constructer 和 copy assignment operator.
编译器会生成 synthesized versions. 这样会有什么问题?
编译器默认生成的拷贝构造函数做的事情等价于:
注意:ps 只是拷贝了指针的值,并没有拷贝 std::string 的内容。如果你这样使用:
- 现在
a.ps和b.ps都指向同一块内存。 - 当
a被销毁时,delete ps会释放这块内存。 - 当
b被销毁时,又会delete ps→ 导致重复释放(double free)。
Rule of Three 第一条:"Classes That Need Destructors Need Copy and Assignment"。
如果类自己管理资源(如 new/delete),必须自己写 copy constructor(实现深拷贝)和 copy assignment operator。
2. Classes That Need Copy Need Assignment, and Vice Versa¶
如果一个类需要拷贝构造函数,它几乎也一定需要拷贝赋值运算符。反之亦然。
但是需要拷贝构造或拷贝赋值,不一定需要析构函数。
3. Deleted 函数¶
编译器会为你自动生成 copy-control 成员,但如果类中某个成员无法被拷贝/赋值/销毁,编译器就会把对应的函数标记为 = deleted,来避免产生危险对象。
Deleted 的原因通常是:
- 成员不可访问(private destructor)
- 成员有 deleted 函数(deleted copy ctor / deleted operator=)
- const 或 reference 成员导致默认赋值不可实现
总结:如果类需要用户定义的析构函数、拷贝构造函数或拷贝赋值运算符,几乎肯定需要三者都定义。