You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

算术运算符重载返回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; // 这行代码会编译失败!完美阻止了无意义赋值
}

为什么这样有效?

  1. 阻止a + b = c:operator+返回的是右值临时对象,而我们给operator=加了&引用限定,意味着只有左值(比如a这种具名对象)才能调用赋值运算符,右值临时对象无法调用,所以b + c = a会直接编译报错,达到了条款3的目的。
  2. 触发移动赋值:operator+返回非const的右值,当我们写a = b + c时,赋值运算符的参数rhs是值传递,编译器会自动用移动构造来创建rhs(因为b+c是右值),然后通过swap完成赋值,本质上等价于移动赋值的效果,而且代码更简洁健壮。

补充说明

《Effective C++》的条款3是C98时代的产物,当时没有移动语义和引用限定符,所以返回const对象是唯一可行的方案。但在C11及以后的现代C++里,引用限定符是更优的选择——既满足了禁止临时对象赋值的需求,又能充分利用移动语义提升性能,不用再牺牲移动赋值的效率啦。

内容的提问来源于stack exchange,提问作者taskinoor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:49:05