简体   繁体   中英

function returning - unique_ptr VS passing result as parameter VS returning by value

In c++, what the preferred/recommended way to create an object in a function/method and return it to be used outside the creation function's scope?

In most functional languages, option 3 (and sometimes even option 1 ) would be preferred, but what's the c++ way of best handling this?

Option 1 (return unique_ptr)

  • pros : function is pure and does not change input params
  • cons : is this an unnecessarily complicated solution?
std::unique_ptr<SomeClass> createSometing(){
    auto s = std::make_unique<SomeClass>();
    return s;
}

Option 2 (pass result as a reference parameter)

  • pros : simple and does not involve pointers
  • cons : input parameter is changed (makes function less pure and more unpredictable - the result reference param could be changed anywhere within the function and it could get hard/messy to track in larger functions).
void createSometing(SomeClass& result){
    SomeClass s;
    result = s;

}

Option 3 (return by value - involves copying)

  • pros : simple and clear
  • cons : involves copying an object - which could be expensive. But is this ok?
SomeClass createSometing(){
    SomeClass s;
    return s;
}

In modern C++, the rule is that the compiler is smarter than the programmer. Said differently the programmer is expected to write code that will be easy to read and maintain. And except when profiling have proven that there is a non acceptable bottleneck, low level concerns should be left to the optimizing compilers.

For that reason and except if profiling has proven that another way is required I would first try option 3 and return a plain object. If the object is moveable, moving an object is generally not too expensive. Furthermore, most compilers are able to fully elide the copy/move operation if they can. If I correctly remember, copy elision is even required starting with C++17 for statements like that:

T foo = functionReturningT();

This is a loaded question, because the matter involves a decision to create the object on the heap vs not creating it on the heap. In C++, it's ideal to have objects that can be passed around as values cheaply. std::string is a good example of that. It's generally a premature pessimization to allocate std::string on the heap. On the other hand, the object you may be creating may be large and expensive to copy. In that case, putting it on the heap would be preferable. But that assumes that a copy would have to take place. By default, the copy is eluded: But also. figure out if the type could be made cheaper to copy.

So there's no “one way suits all”. In my experience, legacy code tends to overuse the heap.

In most cases, returning by value is preferable, since all mainstream compilers will have the function instantiate the object in the storage where it'll reside, without moves nor copies.

Then, the object can be copy-constructed on the heap by the user of the function, if they so desire, and the compiler will get rid of that copy as well.

Micromanagement of this stuff, without looking at actual generated code, is typically a waste of time, since the code declares intent and not the implementation. Compilers these days literally produce code that has equivalent meaning, taking the C++ source's semantics, but not necessarily using the source to dictate identical implementation at the machine level.

Thus, in most instances, returning by value is the sensible default, unless the type is borked and doesn't support that. Unfortunately, some widely used types are in this camp, eg. Qt's QObject.

TL;DR: Given MyType myFactoryFunction(); , the statement auto obj = std::make_unique<MyType>(myFactoryFunction()); will not copy nor move on modern compilers in the release build, if the type is designed well.

There isn't a single right answer and it depends on the situation and personal preference to some extent. Here are pros and cons of different approaches.

  1. Just declare it
SomeClass foo(arg1, arg2);

Factory functions should be relatively uncommon and only needed if the code creating the object doesn't have all the necessary information to create it (or shouldn't, due to encapsulation reasons). Perhaps it's more common in other languages to have factory functions for everything, but instantiating objects directly should be the first pick.

  1. Return by value
SomeClass createSomeClass();

The first question is whether you want the resulting object to live on the stack or the heap. The default for small objects is the stack, since it's more efficient as you skip the call to malloc(). With Return Value Optimization usually there's no copy.

  1. Return by pointer
std::unique_ptr<SomeClass> createSomeClass();
or
SomeClass* createSomeClass();

Reasons you might pick this include being a large object that you want to be heap allocated; the object is created out of some data store and the caller won't own the memory; you want a nullable return type to signal errors.

  1. Out parameter
bool createSomeClass(SomeClass&);

Main benefits of using out parameters are when you have multiple return types. For example, you might want to return true/false for whether the object creation succeeded (eg if your object doesn't have a valid "unset" state, like an integer). You might also have a factory function that returns multiple things, eg

void createUserAndToken(User& user, Token& token);

In summary, I'd say by default, go with return by value. Do you need to signal failure? Out parameter or pointer. Is it a large object that lives on the heap, or some other data structure and you're giving out a handle? Return by pointer. If you don't strictly need a factory function, just declare it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM