函数返回对象时拷贝构造函数的作用及赋值运算符重载疑问
问题解答
1. 为什么拷贝构造函数被调用了还是出现double free?
核心原因是你的拷贝构造函数做了浅拷贝——只是简单复制了动态内存的指针,没有为新对象分配独立的堆内存。
举个错误的拷贝构造示例:
samp(const samp& other) { this->ptr = other.ptr; // 仅复制指针,两个对象共享同一块堆内存 }
当函数返回局部对象时,拷贝构造会创建临时对象,这个临时对象和函数内的局部对象指向同一块内存:
- 函数执行完毕,局部对象先销毁,调用析构函数释放了这块内存;
- 后续临时对象销毁时,又会调用析构函数尝试释放已被释放的内存,直接触发double free。
哪怕拷贝构造被调用,浅拷贝的逻辑根本没解决多对象共享内存的问题,自然会报错。
2. 重载赋值运算符和拷贝构造代码相似,为啥能解决问题?
首先要明确你的操作场景:你应该是把返回的对象赋值给了一个已经存在的对象(比如a = func();),而非用返回值直接初始化对象(比如samp a = func();)。
- 之前的问题里,禁用拷贝省略后,返回临时对象时调用了拷贝构造,但后续赋值环节用的是默认赋值运算符——同样是浅拷贝,会导致目标对象的指针和临时对象的指针指向同一块内存,临时对象销毁后目标对象的指针变成野指针,要么触发double free要么出现垃圾值。
- 你重载的赋值运算符应该做了正确的深拷贝:先释放当前对象已有的动态内存,再为新内容分配独立的堆内存,甚至处理了自赋值的情况。比如:
samp& operator=(const samp& other) { if (this != &other) { // 避免自赋值导致的内存提前释放 delete this->ptr; this->ptr = new int(*other.ptr); // 分配新内存,复制内容 } return *this; }
这样赋值后的对象拥有独立的堆内存,不会和临时对象共享,自然解决了问题。
3. 返回带动态内存的对象是否必须重载赋值运算符?
不是必须,但你必须正确处理拷贝控制:
- 遵循C的三法则(C11之前):如果类需要自定义析构函数(因为有动态内存要释放),那么必须同时自定义拷贝构造函数和拷贝赋值运算符,确保所有拷贝操作都是深拷贝;
- 或者遵循五法则(C++11及以后):除上述三个,还要自定义移动构造函数和移动赋值运算符,提升性能;
- 更简单的方式是用智能指针(比如
std::unique_ptr、std::shared_ptr)管理动态内存,智能指针会自动处理内存的分配、释放和拷贝/移动逻辑,无需手动写拷贝控制函数,从根源上避免double free和内存泄漏。
内容的提问来源于stack exchange,提问作者Preetom Saha Arko
相关产品推荐
相关产品推荐

