在 C++ 中不使用 std::move 实现移动操作
Move in C++ without a std:move

原始链接: https://andreasfertig.com/blog/2026/09/move-in-cpp-without-a-stdmove/

作者强调,为了在 C++ 中实现最佳性能,应避免不必要的拷贝,并依赖编译器优化,而非手动调用 `std::move`。 最佳策略是“返回值优化”(RVO),它通过在调用点直接构造对象来消除拷贝。自 C++17 起,许多场景下的拷贝消除已成为强制要求,使得手动移动变得多余。 虽然移动是消除拷贝之外的次选方案,但作者指出,开发者经常在复杂的函数返回场景中不必要地使用 `std::move`。在过去,某些情况(如返回右值引用参数)需要手动干预以避免昂贵的拷贝。然而,随着 C++ 标准的演进,特别是 C++23,编译器现在可以隐式执行这些移动。通过利用现代 C++ 特性,开发者应遵循“仅在绝对必要时使用 `std::move`”的原则,让编译器自动处理性能优化。

这个 Hacker News 讨论帖探讨了一篇关于如何在不使用 `std::move` 的情况下在 C++ 中执行“移动(moves)”的博客文章。 讨论的主要内容包括: * **移动语义的复杂性:** C++ 开发者们探讨了移动语义带来的认知负担。批评者认为该语言过于复杂且容易导致误用(“埋雷”),而支持者则指出这些特性对于高性能优化至关重要。 * **RVO 和 NRVO:** 讨论的很大一部分集中在返回值优化(RVO)和具名返回值优化(NRVO)上。参与者澄清了 RVO 是一种前端编译器优化,它允许对象直接在调用者的内存中构建,从而避免了不必要的拷贝或移动。 * **“语言律师”之争:** 许多用户认为,对于大多数应用程序而言,过度担心深拷贝属于过早优化。然而,其他人则坚持认为,在系统编程和模拟器开发领域,理解这些底层的 ABI 细节是不可或缺的。 * **与 Rust 的比较:** 一些评论者将 C++ 的“通过类型转换实现移动(move-by-cast)”与 Rust 的破坏性移动语义进行了对比。许多人指出,Rust 的模型更直观,且没有 C++ 为了保持向后兼容性而背负的沉重历史包袱。
相关文章

原文

In one of my earlier posts, Why you should use std::move only rarely I said that you should use std::move only rarely. In today's post, I would like to show you the benefits of this advice: best performance by default.

Return value optimization

One of the worst enemies for performance are unnecessary copy operations.

You surely heard of return value optimization (RVO). This is what you should always aim for if possible. RVO implies that the returned object isn't created on the stack inside the function but at the call-side, where the value will end up anyway. This spares you copy and move. The standard referees to this a copy elision, as the standard never talks about compiler optimizations.

You get guaranteed copy elision since C++17 in the following case:

Apple RVO()
{
  return {};
}

This is pure RVO. Then you have named return value optimization (NRVO):

Apple NRVO()
{
  Apple res{};

  return res;
}

The latter is not subject to guaranteed copy elision. You probably don't pay for a copy or move there as well.

Move instead of copy

The next best thing after copy elision is moving an object. The language had a few places where implicit moves happened since C++11:

Apple Fun(Apple val)
{
  return val;
}

In this code, the resulting object is moved from the parameter.

Do you like this content?

I'm available for in-house C++ training classes worldwide, on-site or remote. Here is a sample list of my classes:
  • From C to C++
  • Programming with C++11 to C++17
  • Programming with C++20
All classes can be customized to your team's needs. Training services

But we had cases in the language where things have been a bit more complicated.

The first one I'd like to share is the following:

Apple Cat(Apple&& val)
{
  return val;
}

You have a function that takes a rvalue reference parameter and returns the just received object. While this code compiles, you will get a copy construction of the return value before C++20. Well, with a conforming compiler like GCC. The less-conforming compiler Clang gives you a move construction. Yes, sometimes not playing by the book can be better.

The other example that I disliked more is the following:

Apple&& Dog(Apple&& val)
{
  return std::move(val);
}

You once again have a function taking an rvalue reference parameter, but this time, a rvalue reference is returned. This time the code would not compile without moving the return value manually. Which goes against my advice.

To be fair, to get the best result in both cases, moving the return value is required. So both cases go against my advice.

Once you switch your compiler to C++23 mode, both cases do an implicit move. No std::move required. You now can follow my rule to use std::move rarely even more!

Andreas

联系我们 contact @ memedata.com