移动赋值运算符引发段错误,求拷贝/移动构造与赋值无泄漏方案
问题分析与解决方案
代码中的核心错误
- 空悬指针访问触发段错误
main函数里,obj被std::move传递给moj后,obj的p已被置为nullptr。后续又将std::move(obj)赋值给loj,此时loj的p会被设置为nullptr,调用loj.printData()时直接访问nullptr的下标,触发Segmentation fault。 - 析构函数的未定义行为
当对象的p为nullptr(比如被移动后的源对象),析构函数中循环执行delete [] p[i]会尝试访问nullptr的成员,这属于未定义行为,会被valgrind检测为错误,甚至导致崩溃。
修复后的完整代码
#include <iostream> using namespace std; class MY{ int **p; // 提取内存分配/释放的辅助函数,避免重复代码 void allocateMemory() { p = new int*[4]; for(int i=0;i<4;i++) p[i] = new int[5]; } void freeMemory() { if (p == nullptr) return; for(int i=0;i<4;i++) delete [] p[i]; delete [] p; p = nullptr; // 释放后置空,避免野指针 } public: MY(){ cout << "Default Constructor" << endl; allocateMemory(); } // Copy Constructor MY(const MY& obj){ cout << "Copy Constructor" << endl; allocateMemory(); for(int i=0;i<4;i++){ for(int j=0;j<5;j++) p[i][j] = obj.p[i][j]; } } // Copy Assignment Operator MY& operator = (const MY & obj){ cout << "Copy Assignment Operator" << endl; if(this == &obj) return *this; freeMemory(); allocateMemory(); for(int i=0;i<4;i++){ for(int j=0;j<5;j++) p[i][j] = obj.p[i][j]; } return *this; } // Move Constructor MY(MY && obj) noexcept { // 添加noexcept,符合标准库移动语义要求 cout << "Move Constructor" << endl; p = obj.p; obj.p = nullptr; } // Move Assignment Operator MY& operator = (MY && obj) noexcept { // 添加noexcept cout << "Move Assignment Operator" << endl; if(this == &obj) return *this; freeMemory(); p = obj.p; obj.p = nullptr; return *this; } ~MY(){ cout << "Destructor " << endl; freeMemory(); } void setData(){ if (p == nullptr) return; // 避免空指针访问 for(int i=0;i<4;i++){ for(int j=0;j<5;j++) p[i][j] = i*j; } } void printData(){ if (p == nullptr) { cout << "Object has no valid data (moved from)" << endl; return; } for(int i=0;i<4;i++){ for(int j=0;j<5;j++) cout << p[i][j] << " "; cout << endl; } } }; int main() { MY obj; obj.setData(); obj.printData(); MY moj(std::move(obj)); moj.printData(); // 注意:被移动后的obj处于有效但空的状态,不要再使用其数据成员 MY loj; // 改为移动仍持有有效数据的moj loj = std::move(moj); loj.printData(); // 此时moj也处于空状态,调用printData会提示无有效数据 moj.printData(); }
关键修复点说明
- 提取辅助函数:将内存分配(
allocateMemory)和释放(freeMemory)逻辑抽离,避免代码重复,同时统一处理空指针判断。 - 析构函数安全处理:
freeMemory中先判断p是否为nullptr,避免访问空指针的下标,彻底解决valgrind检测到的错误。 - 添加
noexcept:移动构造和移动赋值运算符添加noexcept,符合C++标准对移动语义的要求(标准库容器在移动时会依赖noexcept判断是否使用移动而非拷贝)。 - 成员函数空指针防护:
setData和printData中添加p的非空判断,避免空指针访问导致的崩溃。 - 修正main函数的错误用法:不再对已经被移动过的
obj进行二次移动,改为移动持有有效数据的对象,同时演示被移动后的对象的行为。
内存泄漏验证
修复后的代码满足三/五法则,所有内存分配都对应释放:
- 拷贝构造/赋值会深拷贝内存,析构时释放
- 移动构造/赋值会转移内存所有权,源对象置空,不会重复释放
- 析构函数安全处理空指针,不会出现内存泄漏或重复释放的问题
用valgrind检测时,不会再出现内存泄漏或非法访问的错误。
内容的提问来源于stack exchange,提问作者PANKAJ LADE
相关产品推荐
相关产品推荐

