C++11 Return Value Optimization or Move

c++11 Return value optimization or move?

Use exclusively the first method:

Foo f()
{
Foo result;
mangle(result);
return result;
}

This will already allow the use of the move constructor, if one is available. In fact, a local variable can bind to an rvalue reference in a return statement precisely when copy elision is allowed.

Your second version actively prohibits copy elision. The first version is universally better.

Interaction between std::move, return value optimization and destructors

What will happen there? Is this guaranteed to work? Or can there be a situation in which the code doesn't compile or, worse, the destructor gets called twice on the same object?

As long as you're using automatic variables and temporary objects, C++ guarantees that each object constructed will be destroyed. Elisions will not break the meaning of your code; it's just an optimization.

Copy elision doesn't really elide a copy/move; it elides the existence of an object.

In pre-C++17, your code says to construct an ObjectContainer temporary, then copy/move it into the ObjectContainer return value from the function. A compiler may elide the creation of the temporary, instead constructing the return value object directly from the temporary's given parameters. In doing so, it elides away the copy/move, but it also elides away the destruction of the temporary that it doesn't create.

(in post-C++17, this code says to use the prvalue to initialize the return value object directly, so there's no possibility of a temporary being used here.)

Do I have to specify a copy constructor?

You only need to specify a copy constructor if you want the object to be copyable. If you provide a move constructor to a type, the compiler will automatically = delete the other copy/move constructors/assignment operators unless you explicitly write them.

What happens if I do?

That all depends on what you put into the copy constructor. If you do a standard copy of the value, then you've broken how your type works, since it's supposed to have unique ownership of the allocated pointer.

Or should I use std::move in the return statement?

... why would you? The return statement is given a prvalue; you never need to use std::move on prvalues.

C++ return value and move rule exceptions

You are confused by the fact that initialization via the move constructor is a special case of "copy initialization", and does not come as seperate concept. Check the notes on the cppreference page.

If other is an rvalue expression, move constructor will be selected by overload resolution and called during copy-initialization. There is no such term as move-initialization.

For returning a value from the function, check the description of returning on cppreference. It says in a box called "automatic move from local variables and parameters", where expression refers to what you return (warning: that quote is shortened! read the original for full details about other cases):

If expression is a (possibly parenthesized) id-expression that names a variable whose type is [...] a non-volatile object type [...] and that variable is declared [...] in the body or as a parameter of the [...] function, then overload resolution to select the constructor to use for initialization of the returned value is performed twice: first as if expression were an rvalue expression (thus it may select the move constructor), and if the first overload resolution failed [...] then overload resolution is performed as usual, with expression considered as an lvalue (so it may select the copy constructor).

So in the special case of returning a local variable, the variable can be treated as r-value, even if normal syntactic rules would make it a l-value. The spirit of the rule is that after the return, you can't find out whether the value of the local variable has been destroyed during the copy-initialization of the returned value, so moving it does not do any damage.

Regarding your second question: It is considered bad style to use std::move while returning, because moving will happen anyway, and it inhibits NRVO.

Quoting the C++ core guidelines linked above:

Never write return move(local_variable);, because the language already knows the variable is a move candidate. Writing move in this code won’t help, and can actually be detrimental because on some compilers it interferes with RVO (the return value optimization) by creating an additional reference alias to the local variable.

So that library code you quote is suboptimal.

Also, you can not implicitly move from anything that is not local to the function (that is local variables and value parameters), because implicit moving may move from something that is still visible after the function returned. In the quote from cppreference, the important point is "a non-volatile object type". When you return std::string& param, that is a variable with reference type.

Can we use the return value optimization when possible and fall back on move, not copy, semantics when not?

When the expression in the return statement is a non-volatile automatic duration object, and not a function or catch-clause parameter, with the same cv-unqualified type as the function return type, the resulting copy/move is eligible for copy elision. The standard also goes on to say that, if the only reason copy elision was forbidden was that the source object was a function parameter, and if the compiler is unable to elide a copy, the overload resolution for the copy should be done as if the expression was an rvalue. Thus, it would prefer the move constructor.

OTOH, since you are using the ternary expression, none of the conditions hold and you are stuck with a regular copy. Changing your code to

if(b)
return x;
return y;

calls the move constructor.

Note that there is a distinction between RVO and copy elision - copy elision is what the standard allows, while RVO is a technique commonly used to elide copies in a subset of the cases where the standard allows copy elision.

How implementing move constructor affects return value optimization?

Note that in copy elision optimization, the copy/move constructor still has to be present and accessible. And it's not guaranteed that copy elision will be performed in each case.

(emphasis mine)

Even when copy elision takes place and the copy-/move-constructor is
not called, it must be present and accessible (as if no optimization
happened at all
), otherwise the program is ill-formed.

For your code, the copy constructor has been deleteed, and if you remove the definition of move constructor, and it won't be implicitly declared because class A has a user-defined destructor, then both of move/copy constructor are not present and accessible, that's why compile failed.

In summary, the copy/move syntax is needed here and compiler will check for it. Then compiler will decide to perform copy elision or not (e.g. in debug mode).

BTW: You can use -fno-elide-constructors with clang and gcc to prohibit it.

Why return value optimization does not work when return ()

This is NRVO, not RVO.

Here is the rule which allows NRVO (class.copy/31):

in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv- unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

As you can see, in the case of (a), the expression is not a name (because of the added parenthesis), so NRVO is not allowed.

C++ Return Value Optimization

Return Value Optimization (RVO) states that a compiler can elide one or both copies, but it is not required. This means that:

A a (Foo());

Is free to do 0, 1, or 2 copy constructors:

2 - In function Foo(), A a creates an A. When it tries to return, it copies the A into the return value; the resulting initialization A a(Foo()); copies the result of Foo() into a new A.

1 - One of those copies doesn't happen (probably the copy into Foo's return value.

0 - Neither of those copies happen. The A a created inside of Foo directly becomes the A created in this line: A a(Foo());

Msdn has a lot of detail about how the visual c++ compiler handles RVO. It has some neat code examples that explain how it effectively works.

C++: should I explicitly use std::move() in a return statement to force a move?

You should prefer

std::vector<int> foo() {
std::vector<int> v(100000,1);
return v; // move or NRVO
}

over

std::vector<int> foo() {
std::vector<int> v(100000,1);
return std::move(v); // move
}

The second snippet prevent NRVO, and in worst case both would move construct.

Can I rely on named return value optimisation for complicated return types?

Before C++17, you cannot rely on copy elision at all, since it is optional. However, all mainstream compilers will very likely apply it (e.g., GCC applies it even with -O0 optimization flag, you need to explicitly disable copy elision by -fno-elide-constructors if you want to).

However, std::set supports move semantics, so even without NRVO, your code would be fine.

Note that in C++17, NRVO is optional as well. RVO is mandatory.


To be technically correct, IMO, there is no RVO in C++17, since when prvalue is returned, no temporary is materialized to be moved/copied from. The rules are kind of different, but the effect is more or less the same. Or, even stronger, since there is no need for copy/move constructor to return prvalue by value in C++17:

#include <atomic>

std::atomic<int> f() {
return std::atomic<int>{0};
}

int main() {
std::atomic<int> i = f();
}

In C++14, this code does not compile.



Related Topics



Leave a reply



Submit