为何在C++赋值运算符内为this赋值复用拷贝构造会触发编译错误?
C++ IntList 赋值运算符编译错误问题解答
问题场景
在实现自定义链表类IntList的过程中,编写的拷贝构造、赋值运算符代码如下,已知类的析构函数可以正确释放所有链表节点:
// 拷贝构造函数 IntList::IntList(const IntList& source){ Node* currentNode = source.first; first = nullptr; while(currentNode){ append(currentNode->info); currentNode = currentNode->next; } } // 赋值运算符要求:从源链表拷贝内容到当前对象,清除/替换当前对象已有的节点 IntList& IntList::operator=(const IntList& source){ this -> ~IntList(); this = new IntList(source); return *this; }
编译时触发如下报错:
intlist.cpp:26:30: error: lvalue required as left operand of assignment
报错定位到代码行this = new IntList(source);。核心疑惑点是:既然this指向调用赋值运算符的当前实体对象,为什么不能直接给它赋值,又该怎么正确复用拷贝构造的逻辑实现赋值运算符。
编译错误的核心原因
你对this指针的属性存在两个认知偏差,这也是触发报错的直接原因:
this本身是不可被赋值的常指针
编译器在类非静态成员函数中自动生成的this指针,类型为IntList* const——也就是说指针本身的指向是固定的,永远只能指向当前正在调用成员函数的那个对象的内存地址,你无法修改this指向其他内存块。
所以你写this = new IntList(source)本质是试图修改this本身的指向,这在C++语法中是完全不允许的,编译器自然会抛出“左值要求作为赋值左操作数”的错误:因为不可修改的常指针属于右值,不能放在赋值号左边当被赋值的目标。- 显式调用
this->~IntList()本身就属于未定义行为
析构函数的执行意味着对象的生命周期已经结束,对应的内存空间已经被标记为可回收。你在赋值运算符的执行流程中间手动调用析构函数,相当于提前把当前对象的内存标记为已回收,哪怕后续想往这块内存写数据,也属于访问已回收内存的非法操作。
退一步说,就算不遇到编译错误,这种写法运行时也会出现崩溃、内存损坏等随机问题。
正确复用拷贝构造实现赋值运算符的方案
最简洁、安全、符合C++惯例的实现方式是拷贝交换惯用法,完全不需要重复写链表遍历拷贝的逻辑,也不需要手动管理析构流程:
- 第一步:为
IntList实现一个无异常抛出的交换友元,只需要交换两个链表的头指针即可,不需要拷贝任何节点:// 写在类定义内部的友元声明 friend void swap(IntList& firstList, IntList& secondList) noexcept { // 只交换两个链表的头指针,O(1)时间完成交换 std::swap(firstList.first, secondList.first); } - 第二步:重写赋值运算符,核心逻辑非常简洁:
IntList& IntList::operator=(const IntList& source){ // 用拷贝构造创建一个和source内容完全一致的临时对象,直接复用已有的拷贝构造逻辑 IntList temp(source); // 交换临时对象和当前对象的内部指针 swap(*this, temp); // 函数执行结束时,temp会自动调用析构函数,把原来当前对象的所有旧节点全部释放 return *this; }
这种实现的优势
- 零代码冗余:完全复用已经写好的拷贝构造逻辑,不需要重复实现链表遍历拷贝的代码
- 无未定义行为:没有手动调用析构的流程,所有对象的生命周期都由编译器自动管理
- 异常安全:如果拷贝构造过程中因为内存分配失败抛出异常,当前
this指向的对象内容不会被修改,不会出现半拷贝的残缺状态 - 天然兼容自赋值场景:哪怕写
a = a,也会创建一个和a内容完全一样的临时对象,交换后临时对象释放,不会出现释放自身内存导致的野指针问题
内容的提问来源于stack exchange,提问作者Justin Kasowski
相关产品推荐
相关产品推荐

