5

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?

nybon
  • 8,894
  • 9
  • 59
  • 67
  • 2
    Even with e.g. `std::unique_ptr` you could get RVO, of the `std::unique_ptr` object itself. Also note that if you have polymorphic types, a factory function really have no other way than to return a pointer (i.e. `std::unique_ptr`). – Some programmer dude Mar 05 '20 at 13:18
  • 2
    IIRC, since C++17, RVO is mandatory (RNVO is opitonal) due to _deferred temporary materialization_. Moreover, `unique_ptr` has an additional overhead of dynamic memory allocation. But I am afraid that the possible answer would also depend on what you do with the object on the caller side, which you do not specify. – Daniel Langr Mar 05 '20 at 13:18
  • @DanielLangr could you make it more clear what kind of scenarios are possible for caller side? Typically I instantiate a new object, do some transformation or data population for it and caller will use the newly prepared object, but I probably don't summarize this very well since C++ is new to me :( – nybon Mar 05 '20 at 23:42
  • NRVO is a move context . You can detect copy fallback by deleting your class's copy constructor – M.M Mar 06 '20 at 00:00
  • @M.M two cases make deleting copy constructor impractical: 1) I may want my class to be copied in some case but not copied in other cases, for example, returning one instance and copy it is not a big deal but copying a list of such objects are not desirable 2) some classes are out of my control so I cannot delete the copy constructors for them, like the STL classes or third party libarires – nybon Mar 06 '20 at 00:15
  • Maybe you could put some dummy statement just before the return call that will give an error if the class was copyable but non-movable – M.M Mar 06 '20 at 00:19

3 Answers3

10

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.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • I don't think globals are moved out, that sounds wrong. Also you might extend it to static storage duration since the same rule applies for static function variables. – Timo Mar 05 '20 at 13:53
  • @Timo I'm not saying they'll be moved. I say *That means the only time you'll get a copy [...] it's global*. `static` and `global` are basically the same so should be fine without mentioning it – NathanOliver Mar 05 '20 at 13:55
  • @NathanOliver in many cases, I still have to give the variable a name (NRVO), for example, instantiating a vector and populating its data using a loop, does it mean there is no approach I can guarantee RVO (or NRVO because I give it a name) in this case? – nybon Mar 05 '20 at 23:45
  • @nybon If it has a name, then you have no guarantee except that if it is a function local object with automatic storage duration , then it will be moved unless there is no move constructor. For your vector example, this mean you'll either get NRVO, or a move and a move for a vector is a couple/few pointer swaps. – NathanOliver Mar 06 '20 at 00:06
  • I think there is a "logical error" in your last sentence. You'll get a copy if you are returning a named global object which supports moving. But you said *That means the **only** time you'll get a copy if [...] **and** [...]*. – VainMan Apr 20 '21 at 08:43
  • @VainMan I've tweaked the last sentence. How does that read for you? – NathanOliver Apr 20 '21 at 12:06
2

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
}
Nikos C.
  • 50,738
  • 9
  • 71
  • 96
  • 3
    *You cannot return a unique_ptr without RVO or NRVO* That's not true. It's movable so you don't need any sort of "optimization". – NathanOliver Mar 05 '20 at 13:36
  • @NathanOliver That's not what I said. Please read again. I said: "You cannot return a unique_ptr without RVO or NRVO (or implicit move in case NRVO isn't possible.) " Note how there is no `std::move()` in the second example. – Nikos C. Mar 05 '20 at 13:42
  • 4
    @NikosC. You don't need `std::move` for it to work and using `std::move` in a return statement is a pessimization as it stops NRVO. Your first sentence should just be *you can return a function local unique pointer from a function* Because you **always** can. – NathanOliver Mar 05 '20 at 13:47
  • @NathanOliver Implicit is the opposite of explicit. Using `std::move` would be an explicit move, but it is not needed here. – VLL Mar 05 '20 at 14:05
  • @NikosC. *If your types are movable, you can be sure no copies are performed even in cases where NRVO isn't possible*, I understand this is possible, but if my types are NOT movable but can be copied, compiler will try copying it (since optimization is not guaranteed), correct? So if a value X is returned and compiler doesn't report any warning/error, how can I tell this value X is moved instead of copied? It is impractical in reality for people to verify every returned value is movable or copiable to understand the behavior. – nybon Mar 06 '20 at 00:12
1

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/