Why You Should Only Rarely Use std::move

Why You Should Only Rarely Use std::move

By Andreas Fertig

Overload, 31(175):17-18, June 2023


std::move can allow the efficient transfer of resources from object to to object. Andreas Fertig reminds us that using std::move inappropriately can make code less efficient.

In this article, I try to tackle a topic that comes up frequently in my classes: move semantics, and when to use std::move. I will explain to you why you should suggest std::move yourself (in most cases). However, move semantics is way bigger than what this article covers, so don’t expect a full guide to the topic.

Looking More Closely

For a deeper dive, see ‘Nothing is better than copy or move’ [Orr18].

C++11 introduced ‘move semantics’ to facilitate transferring the contents of one object to another more efficiently than creating a copy and then erasing the original. This is particularly focused on optimising the performance of temporary objects, such as when passing them into or out of a function call.

However, in all the discussions about copying and moving, it is easy to forget that not creating an object in the first place may be even more efficient. This can be something done by design choice, or an optimisation applied during compilation. For example, introduction of a temporary object by copying can be removed; this is is called ‘copy elision’ in C++ and has been permitted in the language for many years.

C++17 adds some additional specification around the creation of temporary variables with the phrase ‘temporary materialization’.

Rog’s presentation looks at some ‘worked examples’ of how this behaves in practice, and some things to be aware of.

The example in Listing 1 is the code I used to make my point: don’t use std::move on temporaries! Plus, in general, trust the compiler and only use std::move rarely. For this article, let’s focus on the example code.

class S {
public:
  S() { printf("default constructor\n"); }
  ~S() { printf("deconstructor\n"); }
  // Copy constructor 
  S(const S&) { printf("copy constructor\n"); }
  // Move constructor 
  S(S&&) { printf("move constructor\n"); }
};
void Use()
{
  S obj{
    S{} // Creating obj with a temporary of S 
  };
}
Listing 1

Here we see a, well, perfectly movable class. I left the assignment operations out. They are not relevant. Aside from the constructor and destructor, we see in the copy constructor and in the move constructor. All special members print a message to identify them when they are called.

Further down in Use, we see , a temporary object of S used to initialize obj, also of type S. This is the typical situation where move semantics excels over a copy (assuming the class in question has movable members). The output I expect, and I wanted to show my participants, is:

  default constructor
  move constructor
  deconstructor
  deconstructor

However, the resulting output was:

  default constructor
  deconstructor

Performance-wise, the output doesn’t look bad, but it doesn’t show a move construction. The question is, what is going on here?

This is the time to apply std::move, right?

At this point, somebody’s suggestion was to add std::move.

  void Use()
  {
    S obj{
      // Moving the temporary into obj
      std::move(S{}) 
    };
  }

This change indeed leads to the desired output:

  default constructor
  move constructor
  deconstructor
  deconstructor

It looks like we just found proof that std::move is required all the time. The opposite is the case! std::move makes things worse here. To understand why, let’s first talk about the C++ standard I used to compile this code.

Wait a moment!

In C++14, the output is what I showed you for both Clang and GCC. Even if we compile with -O0 that doesn’t change a thing. We need std::move to see that the move constructor is called. The key here is that the compiler can optimize the temporary away, resulting in only a single default construction. We shouldn’t see a move here because the compiler is already able to optimize it away. The best move operation will not help us here. Nothing is better than eliding a certain step. Eliding is the keyword here. To see what is going on, we need to use the -fno-elide-constructors flag, which Clang and GCC support.

Now the output changes. Running the initial code, without std::move, in C++14 mode shows the expected output:

  default constructor
  move constructor
  deconstructor
  deconstructor

If we now switch to C++17 as the standard, the output is once again:

  default constructor
  deconstructor

Due to the mandatory copy elision in C++17, the compiler must elide this nonsense construction even with -fno-elide-constructors. However, if we apply std::move to the temporary copy, elision doesn’t apply anymore, and we’re back to seeing a move construction.

You can verify this on Compiler Explorer: godbolt.org/z/G1ebj9Yjj

The take away

That means, hands-off! Don’t move temporary objects! The compiler does better without us.

References

[Orr18] Roger Orr, ‘Nothing is better than copy or move’ presentation given at ACCU 2018, available at: https://youtu.be/-dc5vqt2tgA

Andreas Fertig is a trainer and lecturer on C++11 to C++20, who presents at international conferences. Involved in the C++ standardization committee, he has published articles (for example, in iX) and several textbooks, most recently Programming with C++20. His tool – C++ Insights (https://cppinsights.io) – enables people to look behind the scenes of C++, and better understand constructs.

This article was published on Andreas Fertig’s blog in February 2022 (https://andreasfertig.blog/2022/02/why-you-should-use-stdmove-only-rarely/).






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.