My question: is this std::move really needed? My point is that this p_name is not used in the body of constructor, so, maybe, there is some rule in the language to use move semantics for it by default?
Of course it's needed. p_name
is a lvalue, hence std::move
is needed to turn it into a rvalue and select the move constructor.
That's not only what the language says -- what if the type is like this:
struct Foo {
Foo() { cout << "ctor"; }
Foo(const Foo &) { cout << "copy ctor"; }
Foo(Foo &&) { cout << "move ctor"; }
};
The language mandates that copy ctor
must be printed if you omit the move. There are no options here. The compiler can't do this any different.
Yes, copy elision still applies. But not in your case (initialization list), see the comments.
Or does your question involve why are we using that pattern?
The answer is that it provides a safe pattern when we want to store a copy of the passed argument, while benefiting from moves, and avoiding a combinatorial explosion of the arguments.
Consider this class which holds two strings (i.e. two "heavy" objects to copy).
struct Foo {
Foo(string s1, string s2)
: m_s1{s1}, m_s2{s2} {}
private:
string m_s1, m_s2;
};
So let's see what happens in various scenarios.
Take 1
string s1, s2;
Foo f{s1, s2}; // 2 copies for passing by value + 2 copies in the ctor
Argh, this is bad. 4 copies happening here, when only 2 are really needed. In C++03 we'd immediately turn the Foo() arguments into const-refs.
Take 2
Foo(const string &s1, const string &s2) : m_s1{s1}, m_s2{s2} {}
Now we have
Foo f{s1, s2}; // 2 copies in the ctor
That's much better!
But what about moves? For instance, from temporaries:
string function();
Foo f{function(), function()}; // still 2 copies in the ctor
Or when explicitely moving lvalues into the ctor:
Foo f{std::move(s1), std::move(s2)}; // still 2 copies in the ctor
That's not that good. We could've used string
's move ctor to initialize directly the Foo
members.
Take 3
So, we could introduce some overloads for Foo's constructor:
Foo(const string &s1, const string &s2) : m_s1{s1}, m_s2{s2} {}
Foo(string &&s1, const string &s2) : m_s1{std::move(s1)}, m_s2{s2} {}
Foo(const string &s1, string &s2) : m_s1{s1}, m_s2{std::move(s2)} {}
Foo(string &&s1, string &&s2) : m_s1{std::move(s1)}, m_s2{std::move(s2)} {}
So, ok, now we have
Foo f{function(), function()}; // 2 moves
Foo f2{s1, function()}; // 1 copy + 1 move
Good. But heck, we get a combinatorial explosion: each and every argument now must appear in its const-ref + rvalue variants. What if we get 4 strings? Are we going to write 16 ctors?
Take 4 (the good one)
Let's instead take a look at:
Foo(string s1, string s2) : m_s1{std::move(s1)}, m_s2{std::move(s2)} {}
With this version:
Foo foo{s1, s2}; // 2 copies + 2 moves
Foo foo2{function(), function()}; // 2 moves in the arguments + 2 moves in the ctor
Foo foo3{std::move(s1), s2}; // 1 copy, 1 move, 2 moves
Since moves are extremely cheap, this pattern allows to fully benefit from them and avoid the combinatorial explosion. We can indeed move all the way down.
And I didn't even scratch the surface of exception safety.
As part of a more general discussion, let's now consider the following snippet, where all the classes involved make a copy of s by pass by value:
{
// some code ...
std::string s = "123";
AClass obj {s};
OtherClass obj2 {s};
Anotherclass obj3 {s};
// s won't be touched any more from here on
}
If I got you correctly, you'd really like that the compiler actually moved s
away on its last usage:
{
// some code ...
std::string s = "123";
AClass obj {s};
OtherClass obj2 {s};
Anotherclass obj3 {std::move(s)}; // bye bye s
// s won't be touched any more from here on.
// hence nobody will notice s is effectively in a "dead" state!
}
I told you why the compiler cannot do that, but I get your point. It would make sense from a certain point of view -- it's nonsense to make s
live any longer than its last usage. Food for thought for C++2x, I guess.
std::move
to turn them into rvalues and pick up the right constructor overloads. See this related post. The compiler may optimize following the "as if" rule. It is unlikely that it would do this by changing an lvalue to an rvalue! – juanchopanza