C++:返回含指针的对象时如何避免析构引发的内存崩溃
问题分析与解决
代码示例
#include<iostream> class A { public: int* a; char name; A(char _n = 'N') { a = 0; name = _n; } A(int _a, int _b, char _n = 'N') { name = _n; a = new int[2]{ _a, _b }; } ~A() { std::cout << "deleting object..." << name << "\n"; delete[] a; } // void operator=(A b) { std::cout << "cleanup begin\n"; delete[] a; a = new int[] {b.a[0], b.a[1]}; } // A Copy() { if (a == 0) return *new A(); A* _r = new A(a[0], a[1], name + 1); return *_r; } }; // int main() { A d{0, 1, 'T'}; { A Z(0, 1); Z = Z.Copy(); } std::cout << "check\n"; return 0; }
核心问题
执行Z = Z.Copy()触发delete_scalar断点,本质是代码存在两处致命问题:
Copy()函数的内存错误:用new创建对象后返回解引用值,不仅会造成内存泄漏(new的对象永远无法释放),还会因为默认拷贝构造的浅拷贝,让临时对象和动态分配的原对象共享同一块内存。临时对象销毁时释放内存,后续Z的指针访问该内存会引发非法操作。- 赋值运算符的浅拷贝与重复释放:赋值运算符参数采用值传递,会通过默认拷贝构造创建参数
b,b和传入的临时对象共享a指针。赋值结束后b销毁时释放内存,Z析构时再次释放同一块内存,触发重复释放断点。同时代码未处理自赋值场景,若出现Z=Z会先释放自身内存,再访问已释放的指针。
解决步骤
1. 修复Copy()函数
无需动态分配对象,直接返回栈对象即可,编译器会通过返回值优化(RVO)避免冗余拷贝:
A Copy() { if (a == nullptr) return A(); return A(a[0], a[1], name + 1); }
2. 实现类的「三法则」
因类持有动态内存,必须自定义拷贝构造、拷贝赋值运算符,配合已有的析构函数,彻底解决浅拷贝问题:
- 自定义拷贝构造函数:
A(const A& other) { name = other.name; if (other.a == nullptr) { a = nullptr; } else { a = new int[2]{other.a[0], other.a[1]}; } }
- 修复赋值运算符:
推荐用「拷贝交换法」,简洁且自动处理自赋值和异常安全:
// 先实现交换函数 void swap(A& other) noexcept { std::swap(a, other.a); std::swap(name, other.name); } // 拷贝赋值运算符(值传递参数,利用拷贝构造创建临时对象) A& operator=(A other) { swap(other); // 交换后原资源被临时对象带走,临时对象销毁时自动释放 return *this; }
或采用传统写法:
A& operator=(const A& other) { if (this == &other) return *this; // 处理自赋值 delete[] a; // 释放当前资源 // 拷贝新资源 if (other.a == nullptr) { a = nullptr; } else { a = new int[2]{other.a[0], other.a[1]}; } name = other.name; return *this; }
3. 支持复杂运算赋值
修复后,类即可安全支持x = (x - y) * z + y这类操作——只要你的Vector3和Matrix4运算函数返回合法的对象实例,赋值运算符就能正确处理动态内存的拷贝与释放。
额外建议
- 优先使用
std::unique_ptr<int[]>等智能指针替代裸指针,自动管理内存,避免手动delete的错误。 - 遵循C的「零法则」:若类不需要自定义析构、拷贝构造、拷贝赋值,则不手动实现;若需要其中一个,通常三个都需要(C11后还需考虑移动构造和移动赋值,即「五法则」)。
内容的提问来源于stack exchange,提问作者nikodem0808
相关产品推荐
相关产品推荐

