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 ofCat: that you might actually be creating an object of a type derived fromCat. If you need this polymorphism, you absolutely need to return a pointer of some sort. This is why factory functions usually return pointers.Catcannot be moved cheaply or cannot be moved at all. "Inherently" unmovable types are rare; you should usually try to fixCatby making it cheaply movable. But of courseCatcould 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 useunique_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 theCatcannot be constructed; the other, in C++11/C++14, is to make the function returnstd::unique_ptr<Cat>and have it return a null pointer when noCatcan be constructed. In C++17, however, you should start returningstd::optional<Cat>instead ofstd::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.