How can I ensure RVO instead of copy is performed?

3k Views Asked by At

In many cases, I would like to create a new instance of data and return it to the API caller.

I learned that unique_ptr/shared_ptr can be used for factory pattern (for example, Factory pattern using unique_ptr in c++)

At the same time, I learned that returned value optimization (RVO) is possible in many compilers (for example, Efficient way to return a std::vector in c++).

I prefer RVO since it is easier to use the returned value without a wrapping unique_ptr and easier to read the code, however, since the RVO is not guaranteed, I don't want to sacrifice performance unexpectedly and have to use unique_ptr to ensure returned value is moved instead of copied.

Is there any approach that I can explicitly specify the return value to be moved, so that either it will not complain anything if RVO is possible or it will trigger some compiler warning if RVO is not possible? If this is possible, I can safely get rid of returning a unique_ptr in this case.

I am using C++17 and need to support Apple Clang 11.0 on macOS and g++ 9 on Linux.

Edited:

I am still learning C++ and didn't make distinction between RVO (Return Value Optimization) and NRVO (Named Return Value Optimization) when posting this question. It seems to me NRVO is more common and useful in patterns like factory method, for example:

vector<foo> vec;
// populate data into vec
return vec;

And I am looking for something like a return std::move_only(returned_value) that will give me a compiler warning if this value cannot be moved (not copy to move). Maybe I should re-phrase my question as: if NRVO is not guaranteed, why "return by value" is still the recommended way in this question (Efficient way to return a std::vector in c++), shouldn't the answer be "it depends" on your function implementation and whether or not you could accept unexpected performance cost?

3

There are 3 best solutions below

5
Nikos C. On

I prefer RVO since it is easier to use the returned value without a wrapping unique_ptr

You cannot return a unique_ptr without either RVO, NRVO, or implicit move in case NRVO isn't possible. It's not copyable:

std::unique_ptr<int> ptr1;
std::unique_ptr<int> ptr2;
ptr2 = ptr1; // error: not copyable

This does not compile. If it weren't for RVO, NRVO or move, this wouldn't compile either:

std::unique_ptr<int> foo()
{
    return std::unique_ptr<int>{};
}

In this case, this is due to C++17's guaranteed RVO. But even if there was no RVO, you'd still get a move instead of a copy.

And if it weren't for NRVO or guaranteed move fallback, this wouldn't compile:

std::unique_ptr<int> foo()
{
    std::unique_ptr<int> ptr;
    return ptr;
}

So you are already depending on RVO, NRVO or moves. No need for unique_ptr. If your types are movable, you can be sure no copies are performed even in cases where NRVO isn't possible, like when not all return statements return the same local object:

std::unique_ptr<int> foo(const bool flag)
{
    if (flag) {
        std::unique_ptr<int> ptr1;
        return ptr; // implicit move
    }
    std::unique_ptr<int> ptr2;
    return ptr2; // implicit move
}
6
NathanOliver On

How can I ensure RVO instead of copy is performed?

The language does this already for you starting in C++17. If you have a construct like

T foo() { /*stuff*/; return T{ /*stuff*/ }; }

Then the returned object is guaranteed to be elided thanks to guaranteed copy elision.

If you have a construct like

T foo() 
{
    T obj{ /*stuff*/ }; 
    // do stuff with obj
    return obj;
}

Then you will either get NRVO (Nammed Return Value Optimization) which is not guranteed, or the compiler will move obj because there is a rule in the standard that all function local objects with automatic storage duration will be moved out of the function if they have a move constructor.

That means the only time you'll get a copy is if you are returning an object that can't be optimized (it is a named local or it's a function parameter) and it doesn't support moving. Global objects are always copied as they are not scoped to the function.

0
thatsafunnyname On

trigger some compiler warning if RVO is not possible

gcc (trunk) (not yet released v14) has -Wnrvo

"Warn if the compiler does not elide the copy from a local variable to the return value of a function in a context where it is allowed by [class.copy.elision]. This elision is commonly known as the Named Return Value Optimization."

gcc (trunk) is available on https://godbolt.org/