49

Recently I read an example from cppreference.../vector/emplace_back:

struct President
{
    std::string name;
    std::string country;
    int year;

    President(std::string p_name, std::string p_country, int p_year)
        : name(std::move(p_name)), country(std::move(p_country)), year(p_year)
    {
        std::cout << "I am being constructed.\n";
    }

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?

That would be really annoying to add std::move on initialization list to every heavy member (like std::string, std::vector). Imagine hundreds of KLOC project written in C++03 - shall we add everywhere this std::move?

This question: move-constructor-and-initialization-list answer says:

As a golden rule, whenever you take something by rvalue reference, you need to use it inside std::move, and whenever you take something by universal reference (i.e. deduced templated type with &&), you need to use it inside std::forward

But I am not sure: passing by value is rather not universal reference?

[UPDATE]

To make my question more clear. Can the constructor arguments be treated as XValue - I mean expiring values?

In this example AFAIK we do not use std::move:

std::string getName()
{
   std::string local = "Hello SO!";
   return local; // std::move(local) is not needed nor probably correct
}

So, would it be needed here:

void President::setDefaultName()
{
   std::string local = "SO";
   name = local; // std::move OR not std::move?
}

For me this local variable is expiring variable - so move semantics could be applied... And this similar to arguments passed by value....

Community
  • 1
  • 1
PiotrNycz
  • 23,099
  • 7
  • 66
  • 112
  • 2
    The compiler can't just make things up (despite the way many Stack OVerflow posts talk about the compiler). It has to play by the language rules, which are very specific about overload resolution. – Kerrek SB May 29 '14 at 09:18
  • 8
    It is needed, because those are lvalues. You need `std::move` to turn them into rvalues and pick up the right constructor overloads. See this [**related post**](http://stackoverflow.com/questions/21830894/do-c11-compilers-turn-local-variables-into-rvalues-when-they-can-during-code-o/21830925#21830925). 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 May 29 '14 at 09:18
  • @KerrekSB - see my update. Can compiler assume variables used for the last time as xvalue? – PiotrNycz May 29 '14 at 10:44
  • 1
    @PiotrNycz: To repeat: The compiler can't go around making assumptions. It has to implement the language, which specifies the rules very clearly. – Kerrek SB May 29 '14 at 10:46
  • @PiotrNycz, the samples you mentioned in your "post update" are definitely recognizable by modern compilers. First case is traditional RVO, second one will eliminate redundant "local" variable and assign directly to "name" (same time, "SO" literal will still seat in const objects section). – Yury Schkatula May 29 '14 at 10:50
  • @KerrekSB OK - I got your point. However for me hypothetical rule "If the variable is used as a source for copying and this is the last time variable is used - then move is used instead of copy" would be quite clear for compilers. My current understanding is that there is no such rule and it has to be proposed if it makes sense at all... – PiotrNycz May 29 '14 at 10:51
  • 1
    To get anywhere with this, you need to stop talking about the compiler, and instead consider what kind of *language rule* would help with these optimizations... – Kerrek SB May 29 '14 at 10:55
  • 2
    _Imagine hundreds of KLOC project written in C++03_ surely this would be written using pass-by-const-ref anyway? If you're changing to pass-by-value, you can add the moves at the same time. – Useless May 29 '14 at 15:10

4 Answers4

19

My question: is this std::move really needed? My point is that compiler sees that this p_name is not used in the body of constructor, so, maybe, there is some rule to use move semantics for it by default?

In general, when you want to turn an lvalue to an rvalue, then yes, you need a std::move(). See also Do C++11 compilers turn local variables into rvalues when they can during code optimization?

void President::setDefaultName()
{
   std::string local = "SO";
   name = local; // std::move OR not std::move?
}

For me this local variable is expiring variable - so move semantics could be applied... And this similar to arguments passed by value....

Here, I would want the optimizer to eliminate the superfluous local ALTOGETHER; unfortunately, it is not the case in practice. Compiler optimizations get tricky when heap memory comes in to play, see BoostCon 2013 Keynote: Chandler Carruth: Optimizing the Emergent Structures of C++. One of my takeaways from Chandler's talk is that optimizers simply tend to give up when it comes to heap allocated memory.

See the code below for a disappointing example. I don't use std::string in this example because that's a heavily optimized class with inline assembly code, often yielding counterintuitive generated code. To add injury to insult, std::string is roughly speaking a reference counted shared pointer in gcc 4.7.2 at least (copy-on-write optimization, now forbidden by the 2011 standard for std::string). So the example code without std::string:

#include <algorithm>
#include <cstdio>

int main() {
   char literal[] = { "string literal" };
   int len = sizeof literal;
   char* buffer = new char[len];
   std::copy(literal, literal+len, buffer);
   std::printf("%s\n", buffer);
   delete[] buffer;
}

Clearly, according to the "as-if" rule, the generated code could be optimized to this:

int main() {
   std::printf("string literal\n");
}

I have tried it with GCC 4.9.0 and Clang 3.5 with link time optimizations enabled (LTO), and none of them could optimize the code to this level. I looked at the generated assembly code: They both allocated the memory on the heap and did the copy. Well, yeah, that's disappointing.

Stack allocated memory is different though:

#include <algorithm>
#include <cstdio>

int main() {
   char literal[] = { "string literal" };
   const int len = sizeof literal;
   char buffer[len];
   std::copy(literal, literal+len, buffer);
   std::printf("%s\n", buffer);
}

I have checked the assembly code: Here, the compiler is able to reduce the code to basically just std::printf("string literal\n");.

So my expectations that the superfluous local in your example code could be eliminated is not completely unsupported: As my latter example with the stack allocated array shows, it can be done.

Imagine hundreds of KLOC project written in C++03 - shall we add everywhere this std::move?
[...]
But I am not sure: passing by value is rather not universal reference?

"Want speed? Measure." (by Howard Hinnant)

You can easily find yourself in a situation that you do your optimizations just to find out that your optimizations made the code slower. :( My advice is the same as Howard Hinnant's: Measure.

std::string getName()
{
   std::string local = "Hello SO!";
   return local; // std::move(local) is not needed nor probably correct
}

Yes, but we have rules for this special case: It is called named return value optimization (NRVO).

Community
  • 1
  • 1
Ali
  • 56,466
  • 29
  • 168
  • 265
  • "Maybe, maybe in the particular case you show, such an optimization could be implemented, according to the "as-if" rule." -- It cannot, if there is *any* way in which the program could detect the difference, including as sneakily as defining a custom global `operator new` and counting how many times it gets called. And I think a program *could* do that. –  May 29 '14 at 11:16
  • @hvd Interesting. Does the standard mandate the `operator new` being called? I am looking at simpler examples now and I see that `std::string` is incredibly optimized and I often don't understand why I get the assembly I get. Often counterintuitive. – Ali May 29 '14 at 11:21
  • 1
    You are correct that it does not explicitly mandate it, but I do not see how it is possible to meet the requirements of the standard otherwise (for strings that are longer than `sizeof(std::string)`, anyway). By the way, about your "Last time I checked, the GCC implementation was copy-on-write": you're right, and that's treated as a bug by the GCC developers. Work is done on a new implementation which does not use COW, and that new implementation will be used as `std::string` in the future. –  May 29 '14 at 11:25
  • @hvd Could you provide feedback on the correctness of my updated answer, please? – Ali May 29 '14 at 12:04
  • Interesting example, but that too has the problem that `operator new[]` and `operator delete[]` *could* have custom implementations (edit: you mention LTO, and with LTO it should be *possible* for the compiler to know that that is not the case, but I do not know how hard it would be). Also, although `printf` is optimised to `puts`, the compiler has no knowledge of `puts`'s internals and cannot rule out the possibility that `puts` will free the pointed-to memory and exit the program without returning, where freeing the pointed-to memory would then fail. So... optimising is hard. –  May 29 '14 at 12:23
  • @hvd My takeaway from Chandler's talk is that optimizers simply tend to give up when it comes to heap allocated memory. Not entirely sure why but I am not a compiler writer. As for `puts`: [Is Clang really this smart?](http://stackoverflow.com/q/23926075/341970) although that is for stack allocated memory. I think it could be possible for the compiler to know a little bit more about functions in the standard library (for example there could be implementation specific annotations to inform the compiler that `puts` won't free the pointed to memory, etc). Anyway, thanks for checking! – Ali May 29 '14 at 14:27
  • Yes, that's correct. When I wrote "the compiler has no knowledge of `puts`", I meant just that. Indeed, the compiler can be modified to gain such knowledge. As for your other link, I think there was a *very* recent question that showed that actually, clang *isn't* that smart: it misapplies this optimisation and generates incorrect code in some cases. I see it's linked in the question you found. –  May 29 '14 at 14:32
  • @hvd Hmm, do you mean this quesition: [Why is Clang optimizing this code out?](http://stackoverflow.com/q/23838661/341970) I guess not, because it is an earlier question than the one linked. In any case, please let me know if you find it. – Ali May 29 '14 at 14:38
  • Yes, that's the one I meant. It shows that the same optimisation that does sometimes work, including in the "Is Clang really this smart?" question, fails in other cases. –  May 29 '14 at 14:41
  • @hvd Yes but it is a bug in the optimizer; it has been confirmed by the clang developers. – Ali May 29 '14 at 14:46
  • 1
    @Ali: My interpretation was that `operator new` is considered to have side effects (namely, allocating virtual and maybe physical memory), in which case, compilers are _forbidden_ from optimizing it out. That would certainly explain why they all give up. – Mooing Duck May 29 '14 at 16:30
  • @MooingDuck Yes, it would explain why the `buffer` is not eliminated in my example. However, as I see this, in general, the optimizer tends to give up and doesn't track what's going on when it comes to memory on the heap. So it doesn't attempt to eliminate at least the superfluous copy. Could you give me some reference, please: Where is it stated in the standard that the compiler is not allowed to optimize away `operator new` / `delete` calls? – Ali May 29 '14 at 16:54
  • @MooingDuck I should think this through: If the compiler is not allowed to optimize away `operator new` calls, then it is impossible to silently turn copies into moves as it would implicitly mean optimizing away `operator new` calls. – Ali May 29 '14 at 16:58
  • *"it would implicitly mean optimizing away `operator new` calls"* in case of `std::vector` for example. – Ali May 29 '14 at 17:06
  • @Ali: The spec doesn't say that a compiler can't optimize `operator new` specifically, but: 1.9 has a note: "an actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no side effects affecting the observable behavior of the program are produced." The question is if `new` has side effects or not. I think it does, but the spec doesn't say one way or the other. It could even be implementation defined. – Mooing Duck May 29 '14 at 17:09
  • [This question](http://stackoverflow.com/questions/6624022/does-allocating-memory-and-then-releasing-constitute-a-side-effect-in-a-c-prog) says that it's implementation defined if `operator new` has side-effects. – Mooing Duck May 29 '14 at 17:15
  • 1
    @MooingDuck Interesting. Clang happily optimizes away the `delete[] new char[10];` line; gcc doesn't. Thanks, learned something new today! – Ali May 29 '14 at 17:22
  • Compilers generally treat functions with no definition available as compile-time barriers because of unknown side effects. This is why it can't optimize across calls to opaque functions. – Maxim Egorushkin Jun 03 '14 at 08:36
16

The current rule, as amended by DR1579, is that xvalue transformation occurs when a NRVOable local or parameter, or an id-expression referring to a local variable or parameter, is the argument to a return statement.

This works because, clearly, after the return statement the variable can't be used again.

Except that's not the case:

struct S {
    std::string s;
    S(std::string &&s) : s(std::move(s)) { throw std::runtime_error("oops"); }
};

S foo() {
   std::string local = "Hello SO!";
   try {
       return local;
   } catch(std::exception &) {
       assert(local.empty());
       throw;
   }
}

So even for a return statement, it's not actually guaranteed that a local variable or parameter appearing in that statement is the last use of that variable.

It's not totally out of the question that the standard could be changed to specify that the "last" usage of a local variable is subject to xvalue transformation; the problem is defining what the "last" usage is. And another problem is that this has non-local effects within a function; adding e.g. a debugging statement lower down could mean that an xvalue transformation you were relying on no longer occurs. Even a single-occurrence rule wouldn't work, as a single statement can be executed multiple times.

Perhaps you'd be interested in submitting a proposal for discussion on the std-proposals mailing list?

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • The example is based on the fact the `S` constructor only offers the basic exception guarantee. If the `return` statement (including the conversion to `S`) were to offer the strong guarantee then `local` couldn't be trashed and later used. Do you know, was this the committee's position as to why they thought the rule harmless, or just something I've noticed? – Steve Jessop May 29 '14 at 17:03
  • @SteveJessop very much before my time, I'm afraid, but looking at http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#Moving from local values it doesn't seem to have been a consideration ("The auto-local is about to be conceptually destructed anyway"). – ecatmur May 29 '14 at 17:15
16

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.

peppe
  • 21,934
  • 4
  • 55
  • 70
  • Question to: "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.". What about copy-elision: http://en.wikipedia.org/wiki/Copy_elision? – PiotrNycz May 29 '14 at 14:03
  • 1
    Let me remark my point: the way the expression is *parsed* will never trigger the move-ctor (because there's a lvalue passed). Rereading N3337 §12.8.31 (copy elision), it does not apply here: 1) it's not a `return` ; 2) it's not a `throw` ; 3) it's not a *temporary* copied/moved ; 4) it's not a `catch`. – peppe May 29 '14 at 14:37
  • 1
    Aren't there zero moves in your "Take 2" example? std::move() into a const reference should just accomplish nothing. – Dan Olson Nov 30 '17 at 19:23
  • I believe in Take 3, there is still a move to do for && versions. See https://stackoverflow.com/questions/31213539/c-stdmove-with-rvalue-reference-is-not-moving-contents – Teloze Apr 02 '21 at 13:42
  • You are absolutely correct, my bad for not amending this before. Hope this is OK now. – peppe Apr 03 '21 at 16:20
1

I made some further investigation and querying another forums on net.

Unfortunately it seems that this std::move is necessary not only because C++ standard says so, but also otherwise it would be dangerous:

((credit to Kalle Olavi Niemitalo from comp.std.c++ - his answer here))

#include <memory>
#include <mutex>
std::mutex m;
int i;
void f1(std::shared_ptr<std::lock_guard<std::mutex> > p);
void f2()
{
    auto p = std::make_shared<std::lock_guard<std::mutex> >(m);
    ++i;
    f1(p);
    ++i;
}

If f1(p) automatically changed to f1(std::move(p)), then the mutex would be unlocked already before the second ++i; statement.

The following example seems more realistic:

#include <cstdio>
#include <string>
void f1(std::string s) {}
int main()
{
    std::string s("hello");
    const char *p = s.c_str();
    f1(s);
    std::puts(p);
}

If f1(s) automatically changed to f1(std::move(s)), then the pointer p would no longer be valid after f1 returns.

PiotrNycz
  • 23,099
  • 7
  • 66
  • 112