C++向vector添加带动态分配属性的对象触发double free错误问题
问题根本原因
第一个代码报错的核心原因
- 你的
Matrix类违反了C++的三五法则:当你自定义了析构函数来释放堆内存时,必须同步自定义拷贝构造函数、拷贝赋值运算符,或者移动构造函数、移动赋值运算符,否则编译器生成的默认版本只会做浅拷贝。 std::vector在容量不足需要扩容时,会申请更大的内存空间,将原有元素复制/移动到新空间后,销毁旧空间里的所有元素。- 结合你的运行日志完整流程如下:
- 第一次
emplace_back(1),vector容量从0扩到1,构造第一个Matrix对象,地址0x5642c4882eb0,data指向堆内存A - 第二次
emplace_back(2),vector容量从1扩到2,编译器生成的默认拷贝构造函数把旧地址的0x5642c4882eb0对象浅拷贝到新空间,新对象的data也指向堆内存A - 旧空间的
0x5642c4882eb0对象被析构,释放了堆内存A - 程序结束时新空间里的对象生命周期结束,析构函数会再次释放已经被释放的堆内存A,触发double free错误。
- 第一次
指针版本的问题
- 指针版本不会出现double free的原因是:vector存储的只是指针本身,扩容的时候只拷贝指针值,不会触发
Matrix对象的拷贝/析构,所有Matrix对象的内存都是你手动new和delete的,不会出现重复释放的问题。 - 你当前写的指针版本不存在内存泄漏:循环里
new了2个对象,后续循环delete了2个对象,所有堆内存都被正确回收。但这种写法的风险点在于:如果中间逻辑抛出异常,很容易出现delete没执行的情况,导致内存泄漏,而且手动管理内存确实繁琐。
最优修复方案
不需要用指针,你只要给Matrix类补充移动构造函数即可,满足vector扩容时的移动语义,避免浅拷贝:
struct Matrix { int * data = nullptr; int sz = 0; Matrix(int sz) : sz(sz) { std::cout << this << " constructed" << std::endl; data = new int[sz]; for (int i = 0; i < sz; i++) { data[i] = sz; } } // 补充移动构造函数 Matrix(Matrix&& other) noexcept { std::cout << this << " moved from " << &other << std::endl; data = other.data; sz = other.sz; // 把原对象的指针置空,避免析构时释放共享的内存 other.data = nullptr; other.sz = 0; } // 可选:禁止拷贝构造和拷贝赋值,避免误操作出现浅拷贝 Matrix(const Matrix&) = delete; Matrix& operator=(const Matrix&) = delete; ~Matrix() { std::cout << this << " destructed" << std::endl; // 空指针delete是安全的 delete[] data; } };
补充移动构造后,vector扩容时会直接把旧对象的内存所有权转移到新对象,旧对象析构时只会释放空指针,不会出现double free的问题。
内容的提问来源于stack exchange,提问作者Squaloto
相关产品推荐
相关产品推荐

