C++含动态分配内存数据成员的类对象如何安全拷贝(以Matrix类为例)
动态内存类的安全拷贝实现方案
问题根因
内存访问异常的本质是浅拷贝引发的资源非法访问:
- 持有
double**类型堆上二维矩阵资源的Matrix类,若依赖编译器默认生成的拷贝逻辑完成对象赋值,默认逻辑只会复制指针本身的地址,不会为目标对象开辟独立的堆内存空间。 - 当
multiply方法返回临时Matrix对象时,该临时对象在当前表达式执行结束后会被析构,直接释放它持有的矩阵堆内存;此时被赋值的prod对象内部指针和临时对象指向同一块已经被回收的内存,后续调用output访问内存时就会触发非法访问错误。
这类问题的核心解决思路是:为持有动态资源的类实现深拷贝逻辑,保证每个对象持有完全独立的内存资源,拷贝过程中完整复制所有堆上的矩阵数据。
具体实现步骤
严格遵循C++类设计的三法则:只要类需要自定义析构函数(用于释放动态分配的内存),就必须同时自定义拷贝构造函数、拷贝赋值运算符,从根源上避免浅拷贝问题。
1. 明确类的核心私有成员
常规Matrix类的私有成员一般包含:
rows_:矩阵行数cols_:矩阵列数data_:指向二维矩阵堆内存的double**类型指针
2. 实现深拷贝逻辑
拷贝操作的核心是:为新对象开辟独立的堆内存,逐元素复制原矩阵的所有数据,绝不直接复制指针地址。
(1)自定义拷贝构造函数
实现逻辑:按照原矩阵的维度为新对象开辟独立的二维堆内存,逐元素复制矩阵数据。
参考实现代码:
Matrix::Matrix(const Matrix& other) { rows_ = other.rows_; cols_ = other.cols_; // 开辟行指针数组的堆内存 data_ = new double*[rows_]; // 逐行开辟元素存储的连续堆内存 for (int i = 0; i < rows_; i++) { data_[i] = new double[cols_]; } // 逐元素深拷贝矩阵数据 for (int i = 0; i < rows_; i++) { for (int j = 0; j < cols_; j++) { data_[i][j] = other.data_[i][j]; } } }
(2)自定义拷贝赋值运算符
实现逻辑需要规避三个常见问题:
- 先做自赋值检查:如果是对象给自己赋值,直接返回,避免不必要的操作
- 先释放对象自身已经持有的旧堆内存,避免内存泄漏
- 按照和拷贝构造一致的逻辑,开辟新内存完成数据深拷贝
参考实现代码:
Matrix& Matrix::operator=(const Matrix& other) { // 自赋值判断 if (this == &other) { return *this; } // 释放当前对象持有的旧堆内存 for (int i = 0; i < rows_; i++) { delete[] data_[i]; } delete[] data_; data_ = nullptr; // 重新开辟内存、深拷贝数据源 rows_ = other.rows_; cols_ = other.cols_; data_ = new double*[rows_]; for (int i = 0; i < rows_; i++) { data_[i] = new double[cols_]; } for (int i = 0; i < rows_; i++) { for (int j = 0; j < cols_; j++) { data_[i][j] = other.data_[i][j]; } } return *this; }
3. 可选性能优化
如果使用C++11及以上版本的编译器,可以补充实现移动构造函数、移动赋值运算符(即满足五法则):
- 移动逻辑直接“窃取”临时对象的堆内存指针,将临时对象的指针置空,避免临时对象析构时释放已经转移的内存
- 不需要逐元素复制堆数据,相比深拷贝可以大幅提升临时对象返回、赋值的性能
4. 常见实现误区规避
- 禁止直接复制指针值,这是浅拷贝问题的核心来源
- 堆内存开辟完成后必须逐元素复制数据,否则两个对象仍会共享同一块内存,引发重复释放、数据篡改问题
- 二维内存释放顺序必须是:先逐行释放每一行的
double数组,再释放行指针数组data_,顺序颠倒会造成内存泄漏
内容的提问来源于stack exchange,提问作者chrispin2510
相关产品推荐
相关产品推荐

