C++运算符重载:传值与const引用参数的选择疑问
这问题问到点子上了——虽然最终都要生成临时变量,但两种传参方式在性能、语义、适配性上有不小的差别,咱们逐个掰扯清楚:
1. 避免额外拷贝,对复杂类型至关重要
先看第一个左值重载:
template <typename T> T operator+(T a, const T& b) { a += b; return a; }
当你传入一个左值(比如std::vector<int> x)时,参数a是通过拷贝构造初始化的——这是第一次拷贝。然后a += b修改这个副本,最后return a:因为a是函数参数(左值),标准不保证编译器能对它做返回值优化(NRVO),所以大概率会触发第二次拷贝,把a拷贝到返回值的内存里。
再看第二个重载:
template <typename T> T operator+(const T& a, const T& b) { T tmp {a}; tmp += b; return tmp; }
这里a是const左值引用,没有拷贝开销;只在创建tmp的时候做一次拷贝。而且tmp是函数内的局部变量,编译器几乎一定会做NRVO(命名返回值优化)——直接在返回值的目标内存(比如调用者的auto z = x + y;里的z的内存)构造tmp,连return tmp的拷贝/移动都省了!
对于int这种简单类型,拷贝成本可以忽略,但对于std::vector、std::string或者自定义的大对象,一次拷贝的开销可能非常大,这种差异会直接影响性能。
2. 语义更清晰明确
用const T& a传参,明确告诉阅读代码的人和编译器:我们不会修改传入的原对象,只是读取它的值来生成结果。而传值的方式T a,虽然也不会修改原对象,但语义上是"拿到一个对象的副本",容易让人疑惑:为什么要提前拷贝?是不是有什么特殊操作?这种语义上的模糊在团队协作中可能会带来误解。
3. 与右值重载的设计逻辑一致
看你给出的右值重载:
template <typename T> T operator+(T&& a, const T& b) { a += b; return std::move(a); } template <typename T> T operator+(const T& a, T&& b) { b += a; return std::move(b); }
这些重载都是用引用(右值引用/const左值引用)来接收参数,核心思路是尽可能延迟拷贝/移动,只在必要的时候才获取对象的所有权。左值重载用const引用,和这个设计思路完全统一——都是先引用原对象,再在需要修改的时候创建临时变量(或移动右值)。
如果用传值的左值重载,不仅打破了这种一致性,还会在传入右值时出现问题:如果没有匹配的右值重载,传值版本会把右值拷贝到a,而不是移动,平白多了一次拷贝开销。
例外:极小值类型的特殊情况
当然,对于int、char这种大小远小于指针的类型,传值和const引用的性能差异可以忽略——甚至传值可能更快,因为不需要解引用指针。但模板是通用的,要适配所有类型,所以优先选择对大多数场景更优的const引用方式。
内容的提问来源于stack exchange,提问作者Desmond Rhodes

