C++中函数返回参数时能否避免不必要的拷贝
最优实现方案与标准依据
你写的Reversed2其实就是最优实现,在现代C++标准和主流编译器下完全可以做到右值场景零额外拷贝,具体规则和细节如下:
两种初始实现的开销对比
首先明确前提:你的MyType满足可平凡复制、可平凡析构,移动操作与拷贝操作开销完全一致,没有移动语义收益。
Reversed1(const MyType& value):无论传入左值还是右值,都必须执行一次拷贝将引用指向的对象复制到局部变量result,哪怕传入的是即将销毁的临时右值,这次拷贝也无法省略——因为const左值引用绑定的对象不允许直接原地修改,必须复制出可写的副本才能执行反转操作。Reversed2(MyType value):按值传参的形式会根据入参的值类别自动选择最优行为:- 传入左值时:触发一次拷贝构造形参,这个拷贝是不可避免的(原左值不能被修改,必须生成副本),开销和
Reversed1完全一致,没有额外损失。 - 传入右值时(比如你示例中
SomeFunction()返回的临时对象):从C++17开始,标准强制要求省略纯右值初始化按值形参时的拷贝/移动构造,临时对象会直接构造在Reversed2的形参value的内存位置上,参数传递阶段没有任何拷贝开销。
- 传入左值时:触发一次拷贝构造形参,这个拷贝是不可避免的(原左值不能被修改,必须生成副本),开销和
返回阶段的拷贝消除
很多人担心return value;(返回按值形参)会产生额外拷贝,这里可以明确两点:
- 虽然C17的强制返回值优化(RVO)不覆盖返回函数形参的场景,但从C11开始,标准就明确规定:返回函数的按值形参时,编译器会自动将该形参视为右值处理,就算不做返回值优化,也会优先调用移动构造——而对你的平凡类型来说,移动和拷贝开销完全相同,不会有额外成本。
- 所有主流编译器(GCC、Clang、MSVC)在O1及以上优化级别下,都会对返回按值形参的场景做命名返回值优化(NRVO),直接将形参
value的内存位置和调用方接收返回值的对象内存重合,返回阶段不会产生任何拷贝。
注意事项
- 不要给返回的形参加
std::move,即不要写return std::move(value);,这会阻碍NRVO的触发,反而可能引入不必要的拷贝,直接写return value;即可。 - 如果你需要兼容完全不做优化的debug编译场景,或者追求100%的标准层面零拷贝保证,也可以写两个重载分别处理左右值,但实际运行开销和单
Reversed2版本完全一致,且需要注意右值引用重载返回时要显式转为右值避免不必要拷贝:
// 处理右值入参:直接在临时对象上原地操作 inline MyType Reversed(MyType&& value) { Reverse(value); return std::move(value); } // 处理左值入参:拷贝后操作返回 inline MyType Reversed(const MyType& value) { MyType res = value; Reverse(res); return res; }
实际生产环境中直接使用单参数按值传递的Reversed2版本就足够,代码更简洁,优化效果完全符合预期。编译开优化后查看汇编可以验证:传入右值调用Reversed2时,整个流程不会出现任何memcpy类的内存拷贝操作,只会生成原地反转数组的指令。
补充说明:对于尺寸在几百字节的平凡可复制值类型,按值传参/返回的写法本身就是编译器优化的重点场景,哪怕在C++11/14标准下(没有强制拷贝省略规则),所有主流编译器也早就实现了对应的拷贝消除优化,实际运行时不会产生多余拷贝。
内容的提问来源于stack exchange,提问作者Maks Verver
相关产品推荐
相关产品推荐

