I have a class that manages a vector of unique_ptr
s:
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_ptr
s 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:
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.
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 .Use
shared_ptr
plusweak_ptr
: if you wantBar
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
andBar
.
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_ptr
s 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.