This is a part of the mini-series Move Semantics Explained.

Consider a variable s of type std::string, whose value is some string of characters. Suppose we now want to create another variable s2 of type std::string that should contain the same value as s.

std::string s("orange");
// The value of 's' is "orange".

// How do we define 's2' so that it will have the same value as 's'?

To achieve that, we need to “initialize s2 by s”. More precisely, the expression that initializes s2 must refer to s.

std::string s2( /* expression referring to 's' */ );
// => The value of 's2' is "orange".

The question now is how this expression should look like. To answer this, we first need to ask another question:

What should happen to the value of s?

There are two possible answers:

  1. We need the value of s to be preserved.

  2. We don’t need the value of s to be preserved.

If the first option applies, the initialization expression can be simply s itself:

// The value of 's' is "orange".
std::string s2(s);
// The value of 's' is "orange".

If the second option applies, the initialization expression typically used is std::move(s):

// The value of 's' is "orange".
std::string s2(std::move(s));
// The value of 's' does not need to be "orange".

What do these expressions have in common? They both refer to variable s. This guarantees that s2 will have the same value as s originally had.

// The value of 's' is "orange".
std::string s2(s); // 's' refers to 's'
// => The value of 's2' is "orange".
// The value of 's' is "orange".
std::string s2(std::move(s)); // 'std::move(s)' refers to 's'
// => The value of 's2' is "orange".

What is the difference between both these cases? They differ in which constructor of std::string is called for initialization of s2:

  1. The initialization expression s causes the copy constructor of std::string to be called.

  2. The initialization expression std::move(s) causes the move constructor of std::string to be called.

std::string s2(s); // 's2' is initialized by copy constructor of 'std::string'.
std::string s2(std::move(s)); // 's2' is intialized by move constructor of 'std::string'.

Both of them set the value of constructed s2 to the value of s; however:

  1. The copy constructor guarantees that the value of s is preserved.

  2. The move constructor is free not to preserve the value of s.

When we put it all together:

// The value of 's' is "orange".
std::string s2(s); // 's2' is initialized by copy constructor of 'std::string'.
// => The value of 's2' is "orange".
// => The value of 's' is "orange".
// The value of 's' is "orange".
std::string s2(std::move(s)); // 's2' is intialized by move constructor of 'std::string'.
// => The value of 's2' is "orange".
// => The value of 's' does not need to be "orange".

© 2025 Daniel Langr — All rights reserved.