kzsnyk
kzsnyk

Reputation: 2211

How move semantics works here?

I have the following example and I am not sure I fully understand the move semantic logic:

#include <iostream>
#include <string>
#include <memory>

class Player
{
public:
    Player(std::string name)
        : m_name(std::move(name)) {}

private:
    std::string m_name;
};

int main()
{
    std::string s = "Zidane";
    std::cout << s << std::endl;

    Player player1(s); // not moved
    std::cout << s << std::endl; // s = "Zidane"

    Player player2(std::move(s)); // moved -> s is empty now
    std::cout << s << std::endl;

    return 0;
}

My explanation is that in the first case (Player1), name which is an lvalue of type std::string is actually copied before the ctor of m_name is called, then, the std::move acts on the copy, so at the end the copy is empty and its content has been moved to m_name using the move ctor. That's why the original argument s remains untouched. Is it correct?

In the second case, it's not clear: std::move converts the lvalue parameter into a reference rvalue and from there what happen ? In this case, the argument s is empty after the call.

Upvotes: 2

Views: 193

Answers (1)

divinas
divinas

Reputation: 1907

A new std::string is always created to 'populate' the name argument, before Player(std::string name) is called.

Overload resolution will dictate whether the copy ctor, or move ctor of std::string will be called.

Case 1: Player player1(s);

The new string is created by the copy ctor with signature basic_string( const basic_string& other );, because 's' was an lvalue.

The total sum of operation that you pay is 1 move & 1 copy constructions of a string:

  1. One copy ctor, for the name arg to the ctor
  2. One move ctor, for the m_name class member

Case 2: Player player2(std::move(s));

The new string is created by the move ctor for std::string, with signature basic_string( basic_string&& other ) noexcept;

In the second case, you call std::move, which casts s into an rvalue reference. std::string has 2 constructors, one which takes a const std::string&, the other one takes a std::string&&. An rvalue ref can bind to both an lvalue ref and an rvalue ref, but the rvalue ref version is a better match, so it will be selected.

The total sum of operation that you pay is 2 move constructions of a string:

  1. One move ctor, for the name arg to the ctor
  2. One move ctor, for the m_name class member

Please do note that, as pointed out by @aschepler and @underscore_d, the move ctor of std::string does not need to clear the source string. One should not depend on this behavior, as it is not guaranteed and depends on how the move ctor for the strings are implemented.

Upvotes: 4

Related Questions