My understanding is that in C++11, when you return a local variable from a funct
ID: 658950 • Letter: M
Question
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 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?
Explanation / 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
Related Questions
Navigate
Integrity-first tutoring: explanations and feedback only — we do not complete graded work. Learn more.