Home > Back-end >  What is the best/most intuitive way for a class to store a reference to a unique_ptr?
What is the best/most intuitive way for a class to store a reference to a unique_ptr?

Time:11-10

I have a class that manages a vector of unique_ptrs:

class Foo
{
public:
...
private:
    std::vector<std::unique_ptr<SomeClass>> vec_;
};

I have another class, Bar, that I want to store some reference type that refers to an object in the vector. What is the most logical and intuitive way to achieve this?

The ones I can think of are four options:

Bar stores a reference

struct Bar
{
    Bar(std::unique_ptr<SomeClass>& ref) : ref_(ref) {}
    std::unique_ptr<SomeClass>& ref_;
};

This is simple enough, but references can't be reset without constructing the object again. Is this still the preferred method?

Use shared_ptrs both in Foo and Bar

class Foo
{
public:
...
private:
    std::vector<std::shared_ptr<SomeClass>> vec_;
};

struct Bar
{
    std::shared_ptr<SomeClass> ref_;
};

The problem with this one is that it implies that Foo and Bar have equal ownership over the object, even though Foo should have ownership. But maybe that isn't such a big deal? The reason I hesitate is that I've read that you should only use shared_ptr if the ownership really is equal. I also know that shared_ptr comes with quite some overhead compared to unique_ptr.

Use a weak_ptr to show that Bar does not have ownership of the object (however, forcing you to still use a shared_ptr in Foo)

struct Bar
{
    std::weak_ptr<SomeClass> ref_;
};

This one seems like a nice and clean method to me. The only flaw I can see is that we have to use a shared_ptr in Foo even though Foo has sole ownership.

Use unique_ptr::get to get a raw pointer that Bar stores

struct Bar
{
    SomeClass* ref_;
}

This is my least favorite option. The whole reason we use smart pointers is to avoid having to work with raw pointers. It feels silly to use them for something as simple as referencing a smart pointer.

Which of these options is preferred when factoring in performance and code maintainability, or is it subjective?

CodePudding user response:

The whole reason we use smart pointers is to avoid having to work with raw pointers

No: the whole reason we use smart pointers is to avoid having to work with owning raw pointers.

See, for reference, the C Core Guidelines:

R.3: A raw pointer (a T*) is non-owning

We do indeed prefer smart pointers to own resources. However, this doesn't own the resource, so using a raw pointer is fine.

Your alternatives from worst to best:

  1. Bar stores a reference: if you ever want to reseat it, don't use a reference. You say you do want to reseat it, so this is out. There's no benefit in trying to work around this, it just makes your code more surprising and no better.

  2. Use shared_ptr in both places: do this if you need the object kept alive so long as a reference to it exists.

    Don't worry about "equal ownership", worry about whether the semantics make sense for your program. Your job is to decide whether resetting the pointer in Foo::vec_ should destroy the object, not to express the communist manifesto in C .

  3. Use shared_ptr plus weak_ptr: if you want Bar to detect that the object has been destroyed.

    The advantage is that it documents the relationship nicely, and you don't have to worry about accidentally accessing a dangling pointer in between updating Foo and Bar.

CodePudding user response:

Having a non owning raw pointer is perfectly fine, as long as you are confident that you are not going to accidentally dereference once it is no longer valid. With that being said, overheads are of a shared_ptr come mostly during constructor and destructor calls ( some small overhead in used memory as an extra control block needs to be allocated somewhere per object owned via shared_ptr), so as long as you don't care about every single byte of memory and don't pass around shared_ptrs by value everywhere you are not very likely to notice any significant performance difference- hence having a shared_ptr that owns the object and a weak_ptr used to store a reference might be a safer and a nicer approach.

  • Related