Search code examples
c++interfaceheap-memoryshared-ptrunique-ptr

std::unique_ptr and std::shared_ptr as parameters for virtual functions


I'm designing a C++ class interface based on virtual methods to be able to provide extensibility points.

Lots of these public methods require heap allocated object as parameters.

Since I'm making use of modern C++ patterns I'm planning to use std::unique_ptr or std::shared_ptr for that but I have doubts on both of them.

Using std::unique_ptr it looks like something like this:

class IFoo {
  virtual void doSomethingWithUser(std::unique_ptr<User> user) = 0;
}

Forcing the caller to provide std::unique_ptr has downsides:

  • the caller cannot do any operation on the provided user since it has to be moved
  • in case any of doSomethingWithUser implementation needs to store the user in some container, it is not constructible from std::shared_ptr

Using std::shared_ptr for all the public methods could solve the problem but we have to pay for extra memory space plus the atomic increment and decrement of the references count.

Is there any rule of thumb I can follow?


Solution

  • If doSomethingWithUser don't need ownership, you shouldn't transfer it to this method.

    Indeed, copying a shared pointer or moving a unique pointer effectively transfers the ownership of the resource.

    Your functions parameters should reflect your intentions about the assumed ownership of the passed resources.

    If you only need to observe the value, and maybe mutating it, you should pass a non-owning handle to your function.

    If you need to keep the resource alive and maybe deleting it, then you should pass a owning handle, whether it's a shared it unique ownership.

    In your case, the name of the function tells me that you need to "do something with the user", without containing it passed the caller's lifetime. So you should pass a non owning handle. That handle can be a User* or User&, and even a std::reference_wrapper<User> or a boost::optional<User&>, depending on your needs.

    Your interface should indeed express what an object should be able to do, and indeed enforce what parameters should each function take, but your interface should also express what ownership it has over it's parameters.

    I usually prefer references since they ensure it cannot be null and work well with object in automatic storage. Some may argue that they prefer raw pointers as a non-null non-owning handle, but I strongly disagree with that, because they force the &object syntax and allows null.

    There is nothing wrong with non-owning raw pointers. However, raw owning pointers should not be used in modern C++.