三条规则:如果类管理资源(例如内存),则必须在显式实现以下方法之一时实现其余方法:
C++11:五条规则 – 增加了移动操作:
违反此规则会导致资源所有权错误,例如双重删除或内存泄漏。
class Buffer { char* data; public: Buffer(size_t sz) : data(new char[sz]) {} ~Buffer() { delete[] data; } Buffer(const Buffer& other) : data(new char[strlen(other.data)+1]) { strcpy(data, other.data); } Buffer& operator=(const Buffer& other) { if (&other != this) { delete[] data; data = new char[strlen(other.data)+1]; strcpy(data, other.data); } return *this; } // C++11+ 中的移动语义: Buffer(Buffer&& other) noexcept : data(other.data) { other.data = nullptr; } Buffer& operator=(Buffer&& other) noexcept { if (&other != this) { delete[] data; data = other.data; other.data = nullptr; } return *this; } };
如果类管理指针,单单实现析构函数是否足够?
不够。没有拷贝和移动操作,复制时会发生双重内存删除。例如:
Buffer a(10); Buffer b = a; // b 和 a 将删除同一个指针!
故事
在电信数据聚合平台上,所有类只实现了析构函数。重构结构后发生了大规模双重释放错误:对象复制会产生随机行为。
故事
在一款手机游戏项目中,忘记为缓冲区容器类实现移动构造函数。移动时对象被复制,导致数据额外复制和性能损失。
故事
在数据结构序列化库中,从函数返回对象时返回了临时对象,而拷贝构造函数进行了浅拷贝指针。导致了许多内存泄漏,直到几个月后才显现出来。