算术运算符重载返回const值:如何兼顾禁止非法赋值与移动赋值?
这个问题其实是C98时代的最佳实践遇到C11移动语义后的小冲突,我来帮你拆解一下并给出现代C++的解决方案~
首先得搞清楚为什么返回const Foo会导致移动赋值失效:
在C++11里,移动赋值运算符的参数通常是Foo&&(非const右值引用),而const Foo类型的临时对象是const右值,它无法绑定到non-const的右值引用上,所以编译器只能退而求其次调用拷贝赋值运算符,这就是你遇到的问题根源。
而《Effective C++》条款3建议返回const对象,是因为在C98没有移动语义的时代,这是唯一能阻止a + b = c这种无意义赋值的手段,但放到现代C里就有点过时啦。
那怎么兼顾两个需求:既禁止a + b = c,又让a = b + c触发移动赋值?答案是用引用限定符(ref-qualifiers)——这是C++11新增的特性,专门用来区分成员函数是被左值还是右值调用的。
具体实现方案
我们可以给赋值运算符加上&引用限定,让它只能被左值调用,同时让operator+返回非const的Foo对象,这样就能两全其美:
#include <iostream> #include <utility> class Foo { public: Foo() = default; Foo(const Foo&) { std::cout << "拷贝构造\n"; } Foo(Foo&&) noexcept { std::cout << "移动构造\n"; } // 用&限定:只有左值才能调用这个赋值运算符 Foo& operator=(Foo rhs) & { // 拷贝并交换惯用法,自动处理移动/拷贝逻辑 swap(*this, rhs); return *this; } // operator+返回非const的临时对象,支持移动语义 Foo operator+(const Foo&) const { return Foo(); } // 自定义swap成员,配合拷贝并交换逻辑 void swap(Foo& other) noexcept { // 如果有成员变量,在这里交换它们 } }; // 全局swap函数,可选但推荐实现 void swap(Foo& lhs, Foo& rhs) noexcept { lhs.swap(rhs); } int main() { Foo a, b, c; a = b + c; // 这里会输出"移动构造",触发了移动语义 // b + c = a; // 这行代码会编译失败!完美阻止了无意义赋值 }
为什么这样有效?
- 阻止
a + b = c:operator+返回的是右值临时对象,而我们给operator=加了&引用限定,意味着只有左值(比如a这种具名对象)才能调用赋值运算符,右值临时对象无法调用,所以b + c = a会直接编译报错,达到了条款3的目的。 - 触发移动赋值:
operator+返回非const的右值,当我们写a = b + c时,赋值运算符的参数rhs是值传递,编译器会自动用移动构造来创建rhs(因为b+c是右值),然后通过swap完成赋值,本质上等价于移动赋值的效果,而且代码更简洁健壮。
补充说明
《Effective C++》的条款3是C98时代的产物,当时没有移动语义和引用限定符,所以返回const对象是唯一可行的方案。但在C11及以后的现代C++里,引用限定符是更优的选择——既满足了禁止临时对象赋值的需求,又能充分利用移动语义提升性能,不用再牺牲移动赋值的效率啦。
内容的提问来源于stack exchange,提问作者taskinoor
相关产品推荐
相关产品推荐

