C++析构函数删除指针引发野指针问题及解决方案
解决浅拷贝引发的悬垂指针问题
你遇到的是典型的浅拷贝导致的悬垂指针/双重释放问题——类A默认的拷贝构造函数只会逐值复制成员变量,使得原对象和拷贝对象的token指向同一块堆内存。当拷贝对象析构时释放了这块内存,原对象的指针就变成了无效的悬垂指针。下面针对两种场景分别给出解决方案:
场景1:无法修改类A的代码(来自第三方库)
如果不能改动类A的实现,我们只能在调用层面规避拷贝风险,或者手动管理内存来解决问题:
- 方案1:用智能指针共享对象所有权
使用std::shared_ptr<A>管理对象,多个智能指针会共享同一个对象的所有权,只有当最后一个智能指针被销毁时才会触发析构,避免提前释放内存。示例代码:void someFunction() { std::shared_ptr<A> a = std::make_shared<A>(); cout << a->token; // 优先传智能指针,避免拷贝 anotherFunction(a); cout << a->token; } // 同时修改函数参数为:void anotherFunction(std::shared_ptr<A> copyOfA) - 方案2:手动复制内存,保留原指针的有效空间
如果必须传值拷贝,可在拷贝前复制原token的内存,待拷贝对象析构后重新为原对象分配有效内存(此方式较繁琐,需注意内存泄漏风险):void someFunction() { A a; cout << a.token; // 提前复制原token内容 char* tempToken = new char[strlen(a.token) + 1]; strcpy(tempToken, a.token); anotherFunction(a); // 拷贝对象析构会释放原token内存 // 重置原对象的token delete[] a.token; a.token = tempToken; cout << a.token; } - 方案3:强制使用引用传递
你提到过引用传递可避免问题,若无法修改类A,尽量将anotherFunction的参数改为const A& copyOfA或A& copyOfA,这样不会触发拷贝构造,也就不会有析构释放内存的问题。
场景2:可以修改类A的代码
此时我们需要为类A实现深拷贝的拷贝构造函数和赋值运算符重载,确保每次拷贝对象时,都会为token分配独立的内存,而非共享原指针:
实现深拷贝的核心代码
class A { char *token; public: A() { token = GetRandomToken(); // GetRandomToken返回new分配的char数组 } // 拷贝构造函数:深拷贝 A(const A& other) { size_t len = strlen(other.token) + 1; token = new char[len]; strcpy(token, other.token); } // 赋值运算符重载:遵循拷贝交换惯用法,更安全可靠 A& operator=(A other) { std::swap(token, other.token); return *this; } ~A() { if(token != NULL) { delete[] token; token = NULL; } } };
这样当anotherFunction接收A copyOfA时,会调用自定义的拷贝构造函数,为copyOfA的token分配独立内存,原对象a的token不会被拷贝对象的析构函数影响,也就不会出现悬垂指针了。
额外优化:用std::string替代裸指针
如果允许,把char* token换成std::string token会更省心——std::string本身已实现深拷贝,无需手动管理内存,从根源上避免这类问题:
class A { std::string token; public: A() { char* temp = GetRandomToken(); token = temp; delete[] temp; // 释放GetRandomToken分配的临时内存 } // 无需手动编写拷贝构造和赋值运算符,编译器生成的即可正常工作 ~A() { // std::string会自动处理内存释放,无需手动操作 } };
总结
如果必须拷贝对象:
- 场景1下,要么用智能指针共享所有权,要么手动复制内存;
- 场景2下,实现深拷贝的拷贝构造和赋值运算符,或者直接用
std::string替代裸指针,彻底规避内存管理风险。
内容的提问来源于stack exchange,提问作者Vivek MVK
相关产品推荐
相关产品推荐

