C++ Move Semantics – Move-Return of Local Variables

cc++11

My understanding is that in C++11, when you return a local variable from a function by value, the compiler is allowed to treat that variable as an r-value reference and 'move' it out of the function to return it (if RVO/NRVO doesn't happen instead, of course).

My question is, can't this break existing code?

Consider the following code:

#include <iostream>
#include <string>

struct bar
{
  bar(const std::string& str) : _str(str) {}
  bar(const bar&) = delete;
  bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
  void print() {std::cout << _str << std::endl;}

  std::string _str;
};

struct foo
{
  foo(bar& b) : _b(b) {}
  ~foo() {_b.print();}

  bar& _b;
};

bar foobar()
{
  bar b("Hello, World!");
  foo f(b);

  return std::move(b);
}

int main()
{
  foobar();
  return EXIT_SUCCESS;
}

My thoughts were that it would be possible for a destructor of a local object to reference the object that gets implicitly moved, and therefore unexpectedly see an 'empty' object. I tried to test this (see http://ideone.com/ZURoeT ), but I got the 'correct' result without the explicit std::move in foobar(). I'm guessing that was due to NRVO, but I didn't try to rearrange the code to disable that.

Am I correct in that this transformation (causing a move out of the function) happens implicitly and could break existing code?

UPDATE
Here is an example which illustrates what I'm talking about. The following two links are for the same code.
http://ideone.com/4GFIRu – C++03
http://ideone.com/FcL2Xj – C++11

If you look at the output, it's different.

So, I guess this question now becomes, was this considered when adding implicit move to the standard, and it was decided that it was OK to add this breaking change as this kind of code is rare enough? I also wonder if any compilers will warn in cases like this…

Best Answer

Scott Meyers posted to comp.lang.c++ (August 2010) about a problem where implicit generation of move constructors could break C++03 class invariants:

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

Here the problem is that in C++03, X had an invariant that its v member always had 5 elements. X::~X() counted on that invariant, but the newly-introduced move constructor moved from v, thereby setting its length to zero.

This is related to your example since the broken invariant is only detected in the X's destructor (as you say it's possible for a destructor of a local object to reference the object that gets implicitly moved, and therefore unexpectedly see an empty object).

C++11 try to achieve a balance between breaking some of existing code and providing useful optimizations based on move constructors.

Committee initially decided that move constructors and move assignment operators should be generated by the compiler when not provided by the user.

Then decided that this was indeed cause for alarm and it restricted the automatic generation of move constructors and move assignment operators in such a way that it is much less likely, though not impossible, for existing code to break (e.g. explicitly defined destructor).

It’s tempting to think that preventing the generation of implicit move constructors when a user-defined destructor is present is enough but it's not true (N3153 - Implicit Move Must Go for further details).

In N3174 - To Move or not to Move Stroupstrup says:

I consider this a language design problem, rather than a simple backwards compatibility problem. It is easy to avoid breaking old code (e.g. just remove move operations from C++0x), but I see making C++0x a better language by making move operations pervasive a major goal for which it may be worth breaking some C++98 code.