复制构造函数 和 复制赋值操作符 是处理拥有资源(例如动态内存)的对象所必需的。默认情况下,编译器会生成字段-字段的复制,这对原始指针来说是不安全的:
class Buffer { public: Buffer(size_t size) { data = new int[size]; this->size = size; } ~Buffer() { delete[] data; } // 正确实现复制构造函数 Buffer(const Buffer& other) : size(other.size) { data = new int[size]; std::copy(other.data, other.data + size, data); } // 正确的赋值操作符 Buffer& operator=(const Buffer& other) { if (this != &other) { delete[] data; size = other.size; data = new int[size]; std::copy(other.data, other.data + size, data); } return *this; } private: int* data; size_t size; };
否则,在复制两个对象时,一个释放了内存,另一个则留有悬空指针(double free 或 use after free)。
如果明确声明了复制构造函数但没有赋值操作符,会发生什么?它何时需要,何时不需要?
回答: 如果只声明了复制构造函数但未声明赋值操作符,编译器会生成默认的赋值操作符(逐位复制),这在对象包含动态资源时是危险的,即会对同一个指针进行两次销毁。
在管理内存时,必须实现两者:复制构造函数和赋值操作符,以避免复制错误/内存泄漏。
故事
在媒体服务器中,在复制缓冲区对象时使用了复制构造函数,但忘记了赋值操作符。当一个缓冲区赋值给另一个时,对象销毁时发生了双重内存释放。错误在压力测试中显现。
故事
在运输物流项目中,默认的赋值操作符复制了一个指向坐标数组的指针的结构。在删除一个对象后,第二个对象访问已释放的内存,导致分段错误。
故事
在某个图形资源项目中,在流之间传递对象时忘记实现复制构造函数。按值传递导致了浅拷贝,并且在另一个流中更改对象后,出现数据损坏和程序崩溃。