为何拷贝交换惯用法的赋值运算符需swap?拷贝构造与赋值是否必要?
关于拷贝交换惯用法与移动语义的疑问解答
你提到的这个赋值运算符实现:
dumb_array& operator=(dumb_array other) // (1) { swap(*this, other); // (2) return *this; }
我完全懂你的困惑——明明移动构造或者拷贝构造已经把资源拿到手了,为啥还要多做一步swap?咱们一步步把这个问题掰明白:
首先,拷贝交换惯用法的核心价值
这玩意儿能流行起来,核心是解决了两个大问题:
- 异常安全的强保证:如果直接在赋值运算符里手动释放旧资源、分配新资源,万一中间步骤(比如内存分配)抛出异常,当前对象的状态会被破坏(旧资源已经释放,新资源没拿到),变成半失效的状态。但用拷贝交换的话,所有的资源创建/转移都在函数参数
other的构造阶段完成——这一步如果失败,当前对象*this根本没被碰过。而swap操作是不会抛出异常的(只要你的swap实现正确),这样就能保证:要么赋值完全成功,要么当前对象保持原状态,完美符合异常安全的强要求。 - 代码极简复用:不用分别写拷贝赋值运算符和移动赋值运算符,这一个
operator=就能同时处理左值和右值的情况——传左值时自动调用拷贝构造创建other,传右值时自动调用移动构造创建other,后续统一用swap完成资源转移,省了大量重复代码。
为啥移动构造后还要swap?这不是重复操作!
你可以换个视角看这个过程:
- 当用右值赋值时,移动构造是把右值的资源转移到了临时对象
other里,而不是直接转移到*this。 - swap的作用,就是把
*this的旧资源和other手里的新资源(刚从右值移过来的)做交换——这一步才是把目标资源真正转移到当前对象。 - 函数结束时,
other会被自动析构,它手里握着的*this的旧资源也会被一并清理掉,相当于帮我们自动完成了旧资源的释放。
举个具体的例子(假设dumb_array里有个动态分配的指针data):
- 右值赋值时,
other通过移动构造,把右值的data指针抢过来(右值的data变成nullptr)。 - swap之后,
*this->data变成了other刚拿到的新指针,other->data变成了*this原来的旧指针。 other析构时,自动delete旧指针,完成旧资源的清理。
如果不用swap,你得手动写:先delete this->data,再把右值的data移过来——但这一步如果移动过程中出问题(虽然移动构造一般不抛异常,但规则上允许),*this已经释放了旧资源,直接就变成无效状态了。而swap的方式,旧资源一直握在other手里,直到它销毁才释放,全程安全可控。
拷贝构造函数和赋值运算符的关系
在拷贝交换的模式下,赋值运算符是**依赖拷贝构造(或移动构造)**的——因为参数other是通过值传递进来的,值传递会自动触发拷贝/移动构造来创建这个临时对象。所以:
- 你必须正确实现拷贝构造函数(如果你的类需要管理动态资源的话),否则
other的拷贝会出问题。 - 移动构造函数是C++11及以后的可选优化,实现它能让右值赋值的性能更好(避免不必要的拷贝),但即使不写,赋值运算符也能通过拷贝构造处理右值(只是性能差一点)。
另外补充一句:这里的赋值运算符用值传递参数是关键,别改成引用——改成引用的话,就没法自动触发拷贝/移动构造了,拷贝交换的逻辑就不成立了。
内容的提问来源于stack exchange,提问作者strikeforcefan2013
相关产品推荐
相关产品推荐

