Move semantics can be useful when the compiler cannot use RVO and NRVO. But in which case can't the compiler use these features?
The answer is that it is compiler and situation dependent. E.g. control flow branching might confuse optimizers. Wikipedia give this example:
#include <string>
std::string f(bool cond = false) {
std::string first("first");
std::string second("second");
// the function may return one of two named objects
// depending on its argument. RVO might not be applied
return cond ? first : second;
}
int main() {
std::string result = f();
}
Well, it's not so much whether the compiler can use RVO, but whether it thereby can avoid a copy construction.
Consider:
struct Blah
{
int x;
Blah( int const _x ): x( _x ) { cout << "Hum de dum " << x << endl; }
};
Blah foo()
{
Blah const a( 1 );
if( fermatWasRight() ) { return Blah( 2 ); }
return a;
}
Getting the side effects (output from the constructor) right here, is at first glance pretty incompatible with constructing a directy in storage provided by the caller. But if the compiler is smart enough then it can note that destroying this object is a null-operation. And more generally, for any particular situation, if the compiler is smart enough then maybe it can manage to avoid a copy operation no matter how sneakily we design the code.
I'm not sure of the formal though, but the above, with more payload in the object so that copying would be more expensive, is one case where move semantics can help, so that optimization will be guaranteed no matter the smarts of the compiler (or not).
Related
Consider this code:
#include <iostream>
template<typename A>
struct S{
S(const A& a){
std::cout << "L\n";
}
S(A&& a){
std::cout << "R\n";
}
};
S<int> f1(){
int a = 1;
return {a};
}
S<int> f2(){
int a = 1;
return a;
}
S<int> f3(){
int a = 1;
return {std::move(a)};
}
int main()
{
f1();
f2();
f3();
}
Output is
L
R
R
As you may know C++ implicitly moves in the return (in f2). When we do it manually in the initializer list it works (f3), but it is not done automagically by C++ in f1.
Is there a good reason why this does not work, or is it just a corner case deemed not important enough to be specified by the standard?
P.S. I know compilers can (sometimes must) do RVO, but I do not see how this could explain the output.
The nice thing about:
return name;
Is that it's a simple case to reason about: you're obviously returning just an object, by name, there's no other shenanigans going on here at all. And yet, this specific case, has led to patch after patch after patch after patch. So, maybe not so simple after all.
Once we throw in any further complexity on top of that, it gets way more complicated.
With returning an initializer list, we would have to start considering all sorts of other cases:
// obviously can't move
return {name, name};
// 'name' might refer to an automatic storage variable, but
// what if 'other_name' is an alias to it? What if 'other_name'
// is a separate automatic storage variable but is somehow
// dependent on 'name' in a way that matters?
return {name, other_name};
You just... can't know. The only case that we could definitely consider is an initializer list consisting of a single name:
return {name};
That case is probably fine to implicitly move from. But the thing is, in that case, you can just move:
return {std::move(name)};
The problem specifically with the return name; case is that return std::move(name); was sometimes mandatory and sometimes a pessimization and we would like to get the point where you always just write the one thing and get the optimal behavior. There's no such concern here, return {std::move(name)}; can't inhibit copy elision in the same way. So it's just less of an issue to have to write that.
If a function return a value like this:
std::string foo() {
std::string ret {"Test"};
return ret;
}
The compiler is allowed to move ret, since it is not used anymore. This doesn't hold for cases like this:
void foo (std::string str) {
// do sth. with str
}
int main() {
std::string a {"Test"};
foo(a);
}
Although a is obviously not needed anymore since it is destroyed in the next step you have to do:
int main() {
std::string a {"Test"};
foo(std::move(a));
}
Why? In my opinion, this is unnecessarily complicated, since rvalues and move semantic are hard to understand especially for beginners. So it would be great if you wouldn't have to care in standard cases but benefit from move semantic anyway (like with return values and temporaries). It is also annoying to have to look at the class definition to discover if a class is move-enabled and benefits from std::move at all (or use std::move anyway in the hope that it will sometimes be helpfull. It is also error-prone if you work on existing code:
int main() {
std::string a {"Test"};
foo(std::move(a));
// [...] 100 lines of code
// new line:
foo(a); // Ups!
}
The compiler knows better if an object is no longer used used. std::move everywhere is also verbose and reduces readability.
It is not obvious that an object is not going to be used after a given point.
For instance, have a look at the following variant of your code:
struct Bar {
~Bar() { std::cout << str.size() << std::endl; }
std::string& str;
}
Bar make_bar(std::string& str) {
return Bar{ str };
}
void foo (std::string str) {
// do sth. with str
}
int main() {
std::string a {"Test"};
Bar b = make_bar(a);
foo(std::move(a));
}
This code would break, because the string a is put in an invalid state by the move operation, but Bar is holding a reference to it, and will try to use it when it's destroyed, which happens after the foo call.
If make_bar is defined in an external assembly (e.g. a DLL/so), the compiler has no way, when compiling Bar b = make_bar(a);, of telling if b is holding a reference to a or not. So, even if foo(a) is the last usage of a, that doesn't mean it's safe to use move semantics, because some other object might be holding a reference to a as a consequence of previous instructions.
Only you can know if you can use move semantics or not, by looking at the specifications of the functions you call.
On the other side, you can always use move semantics in the return case, because that object will go out of scope anyway, which means any object holding a reference to it will result in undefined behaviour regardless of the move semantics.
By the way, you don't even need move semantics there, because of copy elision.
Its all sums up on what you define by "Destroyed"? std::string has no special effect for self-destroying but deallocating the char array which hides inside.
what if my destructor DOES something special? for example - doing some important logging? then by simply "moving it because it's not needed anymore" I miss some special behavior that the destructor might do.
Because compilers cannot do optimizations that change behavior of the program except when allowed by the standard. return optimization is allowed in certain cases but this optimization is not allowed for method calls. By changing the behavior, it would skip calling copy constructor and destructor which can have side effects (they are not required to be pure) but by skipping them, these side effects won't happen and therefore the behavior would be changed.
(Note that this highly depends on what you try to pass and, in this case, STL implementation. In cases where all code is available at the time of compilation, the compiler may determine both copy constructor and destructor are pure and optimize them out.)
While the compiler is allowed to move ret in your first snippet, it might also do a copy/move elision and construct it directly into the stack of the caller.
This is why it is not recommended to write the function like this:
std::string foo() {
auto ret = std::string("Test");
return std::move(ret);
}
Now for the second snippet, your string a is a lvalue. Move semantics only apply to rvalue-references, which obtained by returning a temporary, unnamed object, or casting a lvalue. The latter is exactly what std::move does.
std::string GetString();
auto s = GetString();
// s is a lvalue, use std::move to cast it to rvalue-ref to force move semantics
foo(s);
// GetString returns a temporary object, which is a rvalue-ref and move semantics apply automatically
foo(GetString());
Lets assume i use Visual Studio or modern GCC with -O2. Will compiler create S inside func() and then copy it to a my_result, or will it create my_result with constructor (5, 6, 5 + 6) without creating temporary S?
NOTE: Function func() definition and its usage are in separate .obj files!
struct S
{
S(int _x, int _y, int _z) : x(_x), y(_y), z(_z) { }
int x, y, z;
};
S func(int a, int b)
{
return S(a, b, a + b);
}
/// USAGE ///
S my_result = func( 5, 6 );
Modern compilers will often optimize this kind of operation. See return value optimization
It's an optimization which pretty much by definition means it's optional for the compiler and up to each aprticular compiler to decide what to do. How can you find out for sure? Check the disassembly of the generated code!
That said most compilers should do this optimization (return value optimization [RVO]) as it's relatively easy to do in this case (no multiple returns, it's an unnamed temporary so you don't have aliasing, etc).
It seems to me that the provided test case is simple enough for RVO to apply.
I would doubt the temporary is optimised out. You could test it by putting a print statement in the constructor and copy constructor and see what's printed under different compiler settings.
You can test this yourself, because the optimization in question has observable differences!
Most forms of optimization in C++ follow the as-if rule, which makes them difficult to detect. However, in a few cases, eliding (skipping) copy and move constructor is allowed, even if the difference results in observable behavior changes.
In this case, add the following to S:
struct S {
// ...
S( S const& o ):x(o.x), y(o.y), z(o.z) {
std::cout << "copy ctor!\n";
}
S& operator=( S const& o ) {
x=o.x;
y=o.y;
z=o.z;
std::cout << "copy assign!\n";
return *this;
}
S( S && o ):x(std::move(o.x)), y(std::move(o.y)), z(std::move(o.z)) {
std::cout << "move ctor!\n";
}
S& operator=( S const& o ) {
std::tie( x,y,z ) = std::tie( std::move(o.x),std::move(o.y),std::move(o.z) );
std::cout << "move assign!\n";
return *this;
}
}
and run your code. With zero optimization you'll get copies and/or moves.
With any non-trivial level of optimization, the prints will disappear, because RVO (and, in related cases, NRVO) will run, eliminating the copies. (If your compiler isn't C++11, remove the move constructors above -- the optimization in C++03 was still allowed)
In C++11 you can explicitly construct the return value instead of relying on NRVO/RVO via the return {stuff} syntax.
Note that RVO (return value optimization) and NRVO (named return value optimization) are relatively fragile, and if you are relying on them you both have to understand how they work, what makes them break, and any quirks your particular compiler has in its implementation (if any).
What does it take to use the move assignment operator of std::string (in VC11)?
I hoped it'd be used automatically as v isn't needed after the assignment anymore.
Is std::move required in this case? If so, I might as well use the non-C++11 swap.
#include <string>
struct user_t
{
void set_name(std::string v)
{
name_ = v;
// swap(name_, v);
// name_ = std::move(v);
}
std::string name_;
};
int main()
{
user_t u;
u.set_name("Olaf");
return 0;
}
I hoped it'd be used automatically as v isn't needed after the assignment anymore. Is std::move required in this case?
Movement always must be explicitly stated for lvalues, unless they are being returned (by value) from a function.
This prevents accidentally moving something. Remember: movement is a destructive act; you don't want it to just happen.
Also, it would be strange if the semantics of name_ = v; changed based on whether this was the last line in a function. After all, this is perfectly legal code:
name_ = v;
v[0] = 5; //Assuming v has at least one character.
Why should the first line execute a copy sometimes and a move other times?
If so, I might as well use the non-C++11 swap.
You can do as you like, but std::move is more obvious as to the intent. We know what it means and what you're doing with it.
The accepted answer is a good answer (and I've upvoted it). But I wanted to address this question in a little more detail:
The core of my question is: Why doesn't it pick the move assignment
operator automatically? The compiler knows v isn't used after the
assignment, doesn't it? Or does C++11 not require the compiler to be
that smart?
This possibility was looked at during the design of move semantics. At an extreme, you might want the compiler to do some static analysis and move from objects whenever possible:
void set_name(std::string v)
{
name_ = v; // move from v if it can be proven that some_event is false?
if (some_event)
f(v);
}
Ultimately demanding this kind of analysis from the compiler is very tricky. Some compilers may be able to make the proof, and others may not. Thus leading to code that isn't really portable.
Ok, so what about some simpler cases without if statements?
void foo()
{
X x;
Y y(x);
X x2 = x; // last use? move?
}
Well, it is difficult to know if y.~Y() will notice x has been moved from. And in general:
void foo()
{
X x;
// ...
// lots of code here
// ...
X x2 = x; // last use? move?
}
it is difficult for the compiler to analyze this to know if x is truly no longer used after the copy construction to x2.
So the original "move" proposal gave a rule for implicit moves that was really simple, and very conservative:
lvalues can only be implicitly moved from in cases where copy
elision is already permissible.
For example:
#include <cassert>
struct X
{
int i_;
X() : i_(1) {}
~X() {i_ = 0;}
};
struct Y
{
X* x_;
Y() : x_(0) {}
~Y() {assert(x_ != 0); assert(x_->i_ != 0);}
};
X foo(bool some_test)
{
Y y;
X x;
if (some_test)
{
X x2;
return x2;
}
y.x_ = &x;
return x;
}
int main()
{
X x = foo(false);
}
Here, by C++98/03 rules, this program may or may not assert, depending on whether or not copy elision at return x happens. If it does happen, the program runs fine. If it doesn't happen, the program asserts.
And so it was reasoned: When RVO is allowed, we are already in an area where there are no guarantees regarding the value of x. So we should be able to take advantage of this leeway and move from x. The risk looked small and the benefit looked huge. Not only would this mean that many existing programs would become much faster with a simple recompile, but it also meant that we could now return "move only" types from factory functions. This is a very large benefit to risk ratio.
Late in the standardization process, we got a little greedy and also said that implicit move happens when returning a by-value parameter (and the type matches the return type). The benefits seem relatively large here too, though the chance for code breakage is slightly larger since this is not a case where RVO was (or is) legal. But I don't have a demonstration of breaking code for this case.
So ultimately, the answer to your core question is that the original design of move semantics took a very conservative route with respect to breaking existing code. Had it not, it would surely have been shot down in committee. Late in the process, there were a few changes that made the design a bit more aggressive. But by this time the core proposal was firmly entrenched in the standard with a majority (but not unanimous) support.
In your example, set_name takes the string by value. Inside set_name, however, v
is an lvalue. Let's treat these cases separately:
user_t u;
std::string str("Olaf"); // Creates string by copying a char const*.
u.set_name(std::move(str)); // Moves string.
Inside set_name you invoke the assignment operator of std::string,
which incurs an unnecessary copy. But there is also an rvalue
overload of operator=,
which makes more sense in your case:
void set_name(std::string v)
{
name_ = std::move(v);
}
This way, the only copying that takes place is the string constrution
(std::string("Olaf")).
Is RVO (Return Value Optimization) guaranteed or applicable for all objects and situations in C++ compilers (specially GCC)?
If answer is "no", what are the conditions of this optimization for a class/object? How can I force or encourage the compiler to do a RVO on a specific returned value?
Return Value Optimization can always be applied, what cannot be universally applied is Named Return Value Optimization. Basically, for the optimization to take place, the compiler must know what object is going to be returned at the place where the object is constructed.
In the case of RVO (where a temporary is returned) that condition is trivially met: the object is constructed in the return statement, and well, it is returned.
In the case of NRVO, you would have to analyze the code to understand whether the compiler can know or not that information. If the analysis of the function is simple, chances are that the compiler will optimize it (single return statement that does not contain a conditional, for example; multiple return statements of the same object; multiple return statements like T f() { if (condition) { T r; return r; } else { T r2; return r2; } } where the compiler knows that r or r2 will be returned...)
Note that you can only assume the optimization in simple cases, specifically, the example in wikipedia could actually be optimized by a smart enough compiler:
std::string f( bool x ) {
std::string a("a"), b("b");
if ( x ) return a;
else return b;
}
Can be rewritten by the compiler into:
std::string f( bool x ) {
if ( x ) {
std::string a("a"), b("b");
return a;
} else {
std::string a("a"), b("b");
return b;
}
}
And the compiler can know at this time that in the first branch a is to be constructed in place of the returned object, and in the second branch the same applies to b. But I would not count on that. If the code is complex, assume that the compiler will not be able to produce the optimization.
EDIT: There is one case that I have not mentioned explicitly, the compiler is not allowed (in most cases even if it was allowed, it could not possibly do it) to optimize away the copy from an argument to the function to the return statement:
T f( T value ) { return value; } // Cannot be optimized away --but can be converted into
// a move operation if available.
Is RVO (Return Value Optimization) guaranteed for all objects in gcc compilers?
No optimisation is ever guaranteed (though RVO is fairly dependable, there do exist some cases that throw it off).
If answer is "no", what is the conditions of this optimization for a class/object?
An implementation detail that's quite deliberately abstracted from you.
Neither know nor care about this, please.
To Jesper: if the object to be constructed is big, avoiding the copy might be necessary (or at the very least highly desirable).
If RVO happens, the copy is avoided and you need not write any more lines of code.
If it doesn't, you'll have to do it manually, writing extra scaffolding yourself. And this will probably involve designating a buffer in advance, forcing you to write a constructor for this empty (probably invalid, you can see how this is not clean) object and a method to ‘construct’ this invalid object.
So ‘It can reduce my lines of code if it's guaranteed. Isn't it?’ does not mean that Masoud is a moron. Unfortunately for him however, RVO is not guaranteed. You have to test if it happens and if it doesn't, write the scaffolding and pollute your design. It can't be herped.
Move semantics (new feature of C++11) is a solution to your problem, which allows you to use Type(Type &&r); (the move constructor) explicitly, instead of Type(const Type &r) (the copy constructor).
For example:
class String {
public:
char *buffer;
String(const char *s) {
int n = strlen(s) + 1;
buffer = new char[n];
memcpy(buffer, s, n);
}
~String() { delete [] buffer; }
String(const String &r) {
// traditional copy ...
}
String(String &&r) {
buffer = r.buffer; // O(1), No copying, saves time.
r.buffer = 0;
}
};
String hello(bool world) {
if (world) {
return String("Hello, world.");
} else {
return String("Hello.");
}
}
int main() {
String foo = hello();
std::cout <<foo.buffer <<std::endl;
}
And this will not trigger the copy constructor.
I don't have a yes or no answer, but you say that you can write fewer lines of code if the optimization you're looking for is guaranteed.
If you write the code you need to write, the program will always work, and if the optimization is there, it will work faster. If there is indeed a case where the optimization "fills in the blank" in the logic rather than the mechanics of the code and makes it work, or outright changes the logic, that seems like a bug that I'd want fixed rather than an implementation detail I'd want to rely on or exploit.