C++二元算术运算符重载:传值优化与实现选择疑问
C++二元算术运算符重载的传值与传引用优化问题
我查阅了cppreference网站上关于运算符重载的「二元算术运算符」页面,对一段代码中为编译器优化鼓励传值而非传引用的注释感到困惑。按我的认知,尽可能传引用更优。
示例代码
第一个示例
class A { public: A& operator+=(A const& rhs) { /*...*/ return *this; } friend A operator+(A lhs, A const& rhs) { lhs += rhs; return lhs; } };
该代码说明operator+中按值传递lhs有助于优化链式加法,但有观点称传值会阻止「具名返回值优化(NRVO)」。
第二个示例
inline A operator+(A const& lhs, X const& rhs) { A a = lhs; a += rhs; return a; }
相关讨论指出,若operator+基于operator+=实现,第一个示例的优化逻辑成立。
问题
- 若
operator+不基于operator+=实现,第一个示例的优化是否仍成立?
- 若
- 若第二个示例支持具名返回值优化,哪种实现的计算效率更优?
- 还有其他选择某一种实现的理由吗?
回答
问题1
不成立。第一个示例的核心优化是利用传值时的拷贝/移动构造复用临时对象:链式调用a + b + c时,a + b返回的临时对象可直接作为下一个operator+的lhs实参,触发移动构造而非拷贝,修改后直接返回。但如果operator+不依赖operator+=,意味着你要在函数内重新构造新对象,此时传值的lhs会产生多余的拷贝开销——本来可以直接用传引用的lhs构造新对象,现在多了一次传值拷贝,且返回的新对象无法复用lhs的存储,优化自然失效。
问题2
多数场景下第一个示例的实现更优:
- 第一个示例中,
lhs是传值进来的,修改后直接返回,编译器可触发返回值优化(RVO)——返回的是函数参数本身,编译器能直接复用实参和返回值的内存,省去一次移动/拷贝。如果调用者传入临时对象,传值时会直接触发移动构造,连初始拷贝都能省。 - 第二个示例即便有NRVO,也必须先拷贝
lhs到局部对象a,再修改返回。虽然NRVO能省去返回时的拷贝,但第一步的拷贝无法避免。
比如链式调用a + b + c:
- 第一个示例:
a + b的临时对象作为下一个operator+的lhs触发移动构造,返回时触发RVO,全程最多1次拷贝+1次移动,甚至可能被优化到0次。 - 第二个示例:每次调用都要拷贝
lhs到a,即使有NRVO,也至少有2次拷贝操作。
问题3
还有这些场景可以选择对应实现:
选第一个示例(传值lhs)的理由:
- 代码简洁:直接复用
operator+=的逻辑,减少重复代码,不用手动写拷贝构造和修改逻辑。 - 自动适配移动语义:传入临时对象时,编译器会自动用移动构造代替拷贝构造,无需额外写重载版本(比如
operator+(A&& lhs, const A& rhs))。 - 规避悬空引用风险:传值不会出现引用失效的问题,避免返回局部对象引用这类低级错误。
选第二个示例(传引用lhs)的理由:
- 当
operator+逻辑无法复用operator+=时,比如需要构造完全不同的对象,传引用可避免不必要的传值拷贝。 - 对于禁用移动构造的类型,传值开销过大,此时传引用+NRVO是更稳妥的选择。
内容的提问来源于stack exchange,提问作者Pompan
相关产品推荐
相关产品推荐

