1

Let's consider this class, which owns or views a pointer depending on how it is constructed.

template<class T>
class OptionalUniquePtr
{
  public:

    OptionalUniquePtr(p*)
    : m_p(p)
    {}

    OptionalUniquePtr(std::unique_ptr<T>&& p)
    : m_owned_p(std::move(p))
    , m_p(p)
    {}

    T* get()
    {
      return m_p;
    }

 private:

   std::unique_ptr<T> m_owned_p;
   T *m_p;
};

Apart from tweaks or optimisations, my question is: is this a bad idea? I'm working on some code that could optionally own or view some pointers:

std::unique_ptr<Bar> b1 = ...;
Bar *b2 = ...;
// one million lines later
Foo f1(std::move(b1),...); // ownership transfered to f1
Foo f2(b2,...); // just viewing b2, caller manages lifetime. Risky, but usually owners have long lifetime

Imagine that Foo is big class which does something on a Bar among other things. I want Foo to be flexible to accept both, so it could have an OptionalUniquePtr inside. An alternative is templatise Foo like this

Foo<std::unique_ptr<Bar>> f1(std::move(b1),...);
Foo<Bar*> f1(b2,...);

The advantage of this second approach is to be more explicit about memory ownership.

Another alternative is to use std::shared_ptr to start with, but in huge codebases this is not feasible.

What is the community opinion on OptionalUniquePtr?

Thanks

Simone

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
S. Rinco
  • 123
  • 1
  • 7
  • I really don't understand what the use case for this thing might be. What's the point of `Foo` in your last example? It's an extra indirection that doesn't add anything compared to a unique pointer for ownership and a raw pointer for non-ownership. – Mat Apr 15 '20 at 10:02
  • @Mat Foo is a big class that uses a Bar among other stuff. Imaging its constructor has other 4 parameters. Depending on the case, I want it to own or just view a Bar. The different constructors of Foo will trigger one or the other behaviour. I edited the post to be more clear on the role of Foo. – S. Rinco Apr 15 '20 at 10:54
  • 1
    Consider using `std::shared_ptr`, it already does the optional ownership thing. – HolyBlackCat Apr 15 '20 at 11:00
  • How? I see shared_ptr can receive a unique_ptr (transfer ownership), but how do I handle the non-ownership case? I cannot change the original code to use shared_ptr to start with. – S. Rinco Apr 15 '20 at 11:10
  • I think I found the answer: for non-ownership use the shared_ptr aliasing constructor. – S. Rinco Apr 15 '20 at 11:24

1 Answers1

1

The answer is that this kind of "optional ownership" is already implemented by std::shared_ptr. The code looks like this:

std::unique_ptr<Bar> bar1 = ...;
Bar* bar2 = ...;

Foo f1(std::move(bar1));
Foo f2(bar2);

where

class Foo
{
  public:

    // Onwing ctor
    Foo(std::unique_ptr<Bar>&& b)
    : m_bar_ptr(std::move(b))
    {}

    // Viewing ctor. It uses shared_ptr aliasing ctor.
    Foo(Bar* b)
    : m_bar_ptr(nullptr, b)
    {}

  private:

    std::shared_ptr<Bar> m_bar_ptr;
  };
S. Rinco
  • 123
  • 1
  • 7
  • `shared_ptr` is for *shared ownership*, **not** *optional* ownership. It adds the overhead (and confusion to the reader) of allowing multiple owners when we know there is only one owner (and we even know it at compile time). I think you are looking for `unique_ptr`'s little used 2-argument overload: `unique_ptr`, which is discussed [here](https://stackoverflow.com/questions/24049590/smart-pointers-with-optional-ownership). – c z Jun 09 '23 at 11:12