Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should I return by value, as opposed to returning a unique pointer

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));
}
like image 332
user1113314 Avatar asked Jun 08 '17 23:06

user1113314


People also ask

What happens when you return a unique_ptr?

If a function returns a std::unique_ptr<> , that means the caller takes ownership of the returned object. class Base { ... }; class Derived : public Base { ... }; // Foo takes ownership of |base|, and the caller takes ownership of the returned // object.

Why unique pointer is used?

In theory, you should use unique_ptr for all pointers unless you know you want to share it, in which case you should use shared_ptr . The reason is that unique_ptr has less overhead since it doesn't count references.

What happens when a unique pointer goes out of scope?

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.

How do you define a unique pointer?

A unique pointer is an object that owns another object and manages that other object through a pointer. The unique pointer has exclusive ownership of the object it points to. This means unique_ptr does not share its pointer with any other unique_ptr. It cannot be copied to another unique_ptr.


2 Answers

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:

  1. 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.

  2. 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).

  3. 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.

like image 93
Brian Bi Avatar answered Nov 10 '22 00:11

Brian Bi


By default, return by value.

Exceptions to this rule:

  1. The Cat needs to exist on the heap, so as to outlast the code triggering its creation... but in this case perhaps it shouldn't really be a unique_ptr that's returned, but rather a shared_ptr.
  2. You're not actually constructing the Cat but rather getting access to something which can be interpreted as a Cat; in this case, again, you probably don't want a unique pointer but a regular one (or a unique pointer with a custom deleter).
  3. Polymorphism - if it's a factory and Cat is one of its products, you can probably also make a Dog and a Horse, all being Animals, so you'll return a pointer to an Animal. That's definitely a case where you would use a unique pointer.
  4. Dark voodoo in your copy, assignment and/or move constructors which makes it important to always make sure you only poke your Cat from afar.

I disagree with @Brian's answer regarding two of the exceptions he suggests:

  • I would suggest not to use a pointer return type so as to be able to indicate failure by returning nullptr. Failing to return a valid value is what exceptions are for, and even if you want to avoid them - I'd suggest returning an std::optional.
  • You usually don't need a move constructor for return value optimization to kick in - so the lack of a move constructor should not be a reason to return a pointer.
like image 29
einpoklum Avatar answered Nov 10 '22 00:11

einpoklum