C++ 中不使用 std::move 的移动
摘要
本文解释了 C++23 如何在特定情况下允许隐式移动,减少对 std::move 的需求,并通过返回值优化和拷贝省略提升性能。
暂无内容
查看缓存全文
缓存时间: 2026/09/02 14:53
# 不使用std::move的C++移动语义
来源:https://andreasfertig.com/blog/2026/09/move-in-cpp-without-a-stdmove/
在我之前的一篇文章《为何应极少使用std::move》(https://andreasfertig.com/blog/2022/02/why-you-should-use-stdmove-only-rarely/)中,我曾建议应尽量减少使用`std::move`。今天我想分享这条建议带来的好处:默认情况下就能获得最佳性能。
## 返回值优化
性能最大的敌人之一就是不必要的拷贝操作。
你一定听说过返回值优化(RVO)。只要条件允许,我们都应力求实现RVO。RVO意味着返回的对象不会在函数内部的栈上创建,而是在最终会使用该值的调用侧构造。这能避免拷贝和移动操作。标准规范中称之为"拷贝消除",因为标准从不直接讨论编译器优化。
自C++17起,在以下情况下保证实现拷贝消除:
```
Apple RVO()
{
return {};
}
```
这是纯粹的RVO。接着是命名返回值优化(NRVO):
```
Apple NRVO()
{
Apple res{};
return res;
}
```
后者不在保证性拷贝消除的范围内。不过实际执行时通常也不会产生拷贝或移动开销。
## 移动替代拷贝
仅次于拷贝消除的最佳选择是移动对象。自C++11起,语言中已有几处自动发生隐式移动的场景:
```
Apple Fun(Apple val)
{
return val;
}
```
这段代码中,结果对象将从参数进行移动构造。
但语言中存在一些更复杂的情况。
首先想分享的是这个例子:
```
Apple Cat(Apple&& val)
{
return val;
}
```
该函数接受右值引用参数并返回刚接收的对象。虽然代码能编译通过,但在C++20之前,返回值将进行拷贝构造。嗯,前提是使用符合标准的编译器如GCC。而规范遵从度较低的编译器Clang却会给出移动构造。确实,有时不墨守成规反而效果更好。
另一个我更不喜欢的例子如下:
```
Apple Cat(Apple&& val)
{
return val;
}
```
同样是接受右值引用参数的函数,但这次返回的是右值引用。此时若不手动移动返回值,代码将无法编译。这与我的建议相悖。
公平地说,要在这两种情况下获得最佳效果,都需要对返回值进行移动操作。因此两者都违背了我之前的建议。
当将编译器切换至C++23模式后,这两种情况都会自动进行隐式移动。无需使用`std::move`!现在你可以更彻底地遵循我"极少使用`std::move`"的原则了!
Andreas
相似文章
C++ 中的惰性初始化
本文解释 C++ 中的惰性初始化,通过将计算推迟到必要时来优化性能,使用辅助结构体来利用 std::optional 中的转换运算符。
const_cast:必要之恶
本文解释了为什么在C++中有时必须使用const_cast,特别是从std::priority_queue中移动对象时,以及如何安全地使用它。
批量 memmove 会加速 std::remove_if 吗?(不会。)
本文探讨了在 std::remove_if 中使用批量 memmove 是否比传统的逐元素移动能提升性能,结论是并不会,因为记账开销以及 memmove 的重叠检查带来了额外负担。
PImpl惯用法与C++26 std::indirect类型
解释C++中的PImpl惯用法,以及即将推出的C++26 std::indirect类型如何简化其实现,减少样板代码和手动内存管理。
信任你的编译器:现代C++
本文对比了旧的C++性能技巧与现代编译器的能力,表明编译器现在能够将朴素代码优化得比手工调整的技巧更好。包含在AMD Zen 5上使用Clang 21的基准测试。