运算符重载:修改临时对象还是创建新对象?
临时对象优化与operator+/-=效率的疑问解答
先还原问题中的代码场景:
class A { public: A(const A&); A(A &&); ~A(); A &operator += (const A &); A operator + (const A &); private: class B; B *b; }; A factory(); void sink(const A &); void foo(const A &x) { sink(factory() += x); // 高亮行 }
一、用operator+=替代operator+的优化是否合理?
这个优化是合理的。核心逻辑差异在于:
factory() += x:factory()返回的临时右值对象会被直接修改(比如更新B指向的内部资源),全程仅存在这一个临时对象,最终由sink的const引用绑定,仅触发一次析构。- 常规实现的
factory() + x:operator+会先拷贝构造一个新临时对象(以factory()的返回值为模板),再对新对象执行+=操作后返回。即使有移动语义,也会多一次临时对象的构造(或移动构造)与析构,当A的内部资源(比如B*指向的堆内存)较大时,额外开销会很明显。
你通过Godbolt观察到的汇编差异,正是这种逻辑差异的直接体现。
二、两种写法的利弊对比
operator+=写法
- 优势:无额外临时对象开销,对于资源密集型的A类,能显著减少内存分配、复制的次数,效率更高。
- 劣势:语义违背直觉,
factory() += x看起来像是修改了factory返回的临时对象,不符合+操作“不修改原操作数”的常规认知,可读性差;后续如果需要链式操作(比如factory() += x += y),逻辑会更混乱。
operator+写法
- 优势:语义清晰,符合数学运算的直觉,代码可读性、可维护性更高;链式操作(
factory() + x + y)逻辑自然,符合用户预期。 - 劣势:常规实现下会多一次临时对象的构造/析构,带来额外性能开销,当A的复制成本很高时,这个开销不可忽略。
三、如何保留operator+写法同时达到同等效率?
可以通过优化operator+的实现,消除额外临时对象的开销,主要有两种方案:
1. 为operator+提供右值重载版本
利用右值引用直接复用factory()返回的临时对象资源:
// 非成员函数,覆盖左值+左值、右值+左值的情况 A operator+(const A& lhs, const A& rhs) { A temp(lhs); temp += rhs; return temp; } A operator+(A&& lhs, const A& rhs) { lhs += rhs; return std::move(lhs); }
当调用factory() + x时,factory()返回的右值会匹配第二个重载:直接在该右值对象上执行+=,再通过移动构造返回(A的移动构造是浅拷贝B*,几乎无开销)。结合编译器的返回值优化(RVO),最终的执行逻辑和factory() += x完全一致,无额外临时对象。
2. 利用C++17的保证复制消除(Guaranteed Copy Elision)
C++17标准强制要求编译器消除某些场景下的复制/移动操作,我们可以调整operator+的参数传递方式:
A operator+(A lhs, const A& rhs) { lhs += rhs; return lhs; }
当传入右值(factory()的返回值)时,lhs会通过移动构造初始化(而非拷贝),执行+=后返回lhs,此时C++17的保证复制消除会直接把lhs的资源转移到最终的临时对象中,全程仅存在一次临时对象,效率和operator+=写法持平。
另外,还可以结合完美转发实现更通用的operator+,适配左值、右值等多种场景:
template<typename T, typename U> std::enable_if_t<std::is_same_v<std::decay_t<T>, A>, A> operator+(T&& lhs, const U& rhs) { std::forward<T>(lhs) += rhs; return std::forward<T>(lhs); }
这些优化的核心前提是:A的移动构造函数是高效的(比如仅浅拷贝指针、无额外资源分配)。如果移动构造本身开销较大,需要进一步调整类的内部实现。
内容的提问来源于stack exchange,提问作者phlipsy
相关产品推荐
相关产品推荐

