What I'm wondering is, how does returning by value a Cat
actually differ from returning an std::unique_ptr<Cat>
in terms of passing them around, memory management and using them in practice.
Memory management wise, aren't they the same? As both a returned by value object and an object wrapped in a unique_ptr will have their destructors triggered once they go out of scope?
So, how would you compare both pieces of code:
Cat catFactory(string catName) {
return Cat(catName);
}
std::unique_ptr<Cat> catFactory(string catName) {
return std::unique_ptr(new Cat(catName));
}
Returning by value should be considered the default. (*) Deviating from the default practice, by returning std::unique_ptr<Cat>
, should require justification.
There are three main reasons to return a pointer:
Polymorphism. This is the best reason to return std::unique_ptr<Cat>
instead of Cat
: that you might actually be creating an object of a type derived from Cat
. If you need this polymorphism, you absolutely need to return a pointer of some sort. This is why factory functions usually return pointers.
Cat
cannot be moved cheaply or cannot be moved at all. "Inherently" unmovable types are rare; you should usually try to fix Cat
by making it cheaply movable. But of course Cat
could be a type owned by someone else, to which you cannot add a move constructor (or perhaps even a copy constructor). In that case, there is not much you can do other than use unique_ptr
(and complain to the owner).
The function has the potential to fail and be unable to construct any valid Cat
. In that case, one possibility is return by value anyway but throw an exception if the Cat
cannot be constructed; the other, in C++11/C++14, is to make the function return std::unique_ptr<Cat>
and have it return a null pointer when no Cat
can be constructed. In C++17, however, you should start returning std::optional<Cat>
instead of std::unique_ptr<Cat>
in that case, to avoid unnecessary heap allocation.
(*) This also applies to passing objects when the function being called needs its own copy of the value, e.g., a constructor that will initialize a class member from one of its arguments. Accept the object by value and move.