C++ shared_ptr vs. unique_ptr for resource management
Asked Answered
C

3

5

I've been mulling over use of unique_ptr vs shared_ptr vs own_solution. I've discounted the latter as I'll almost certainly get it wrong, but I have a problem with both unique_ptr and shared_ptr in that neither captures precisely what I want. I want to create a resource manager which explicitly owns a resource, however I'd like the resource manager to also hand out references to the resource.

If I use unique_ptr in the resource manager and hand out raw pointers there's the possibility they could escape elsewhere (though this would be against the class "contract" I suppose). If I use shared_ptr and hand out weak_ptr, there's nothing stopping a caller from converting the weak_ptr to a shared_ptr and storing that, thereby potentially creating a cycle or worse, a resource living beyond the lifetime of the resource manager. So I suppose what I'm looking for is a deferencable weak_ptr that cannot be converted into a shared_ptr.

Or am I just looking to enforce the contract with some strongly worded comments in the code?

Thanks for any thoughts you might have on this.

Conclusion answered 11/4, 2014 at 15:32 Comment(1)
Any pointer could "escape" just as easily as a raw pointer so I don't see how that's much of a con wrt raw pointers.Imbricate
D
6

Smart pointers like shared_ptr and unique_ptr are a good tools when you have owning pointers.
But for non-owning pointers, i.e. observing pointers, using a raw pointer is just fine.

In your design, I think the resource manager is the only "owner" of the resources, so you could simply have some form of smart pointer inside the resource manager. For example, the resource manager can have a std::vector<std::unique_ptr<Resource>> as a data member, or even a simpler std::vector<Resource> if your Resource class is designed to be correctly storable in a std::vector.

Then, the resource manager can give to the outside just non-owning observing pointers, and raw pointers (or C++ references) are fine for this case.

Of course, it's important that the lifetime of the resource manager exceeds that of the "resource clients".

Desireah answered 11/4, 2014 at 15:46 Comment(0)
T
9

In the end, you cannot force anyone to listen. Ask at microsoft, apple or any open source library developer, they all know that song. A comment in the right words and places is your best bet.

Avoid creating your own smart pointer class, it hinders composition and reduces readability. As a last resort, try looking in boost, or any framework your code already has to work with.

If you have non-owners, they are either electable for holding weak_ptrs or (if it is guaranteed to stay valid for the duration) raw pointers.
If you use shared_ptrs internally (why should you), best provide weak_ptr and raw pointers.

All those smart pointers explicitly denote an ownership policy. Raw pointers denote none or non-owning.

  • auto_ptr: Do not use, deprecated with too many traps even for the wary.
  • unique_ptr: Sole ownership.
  • shared_ptr: Shared ownership
  • weak_ptr: No ownership, might get deleted behind your back.
  • raw pointer
    • Explicitly no ownership with guaranteed bigger lifetime
    • or manual ownership management.
Telophase answered 11/4, 2014 at 15:34 Comment(2)
"Avoid creating your own smart pointer class": C++ STD is not a complete set of solutions for everything. If you want something for a special application, write it and use it. In particular C++ is a language which provides you that freedom compared to Java or C#. Just my 5 cents.Gratuity
@Danvil: That's why it is only avoid, with a first fallback to common/already used frameworks. Too often the user will as first order of business evade your smart-poiter class, if it does not fit with other libraries he is using. And he will resent having to jump through those hoops.Telophase
D
6

Smart pointers like shared_ptr and unique_ptr are a good tools when you have owning pointers.
But for non-owning pointers, i.e. observing pointers, using a raw pointer is just fine.

In your design, I think the resource manager is the only "owner" of the resources, so you could simply have some form of smart pointer inside the resource manager. For example, the resource manager can have a std::vector<std::unique_ptr<Resource>> as a data member, or even a simpler std::vector<Resource> if your Resource class is designed to be correctly storable in a std::vector.

Then, the resource manager can give to the outside just non-owning observing pointers, and raw pointers (or C++ references) are fine for this case.

Of course, it's important that the lifetime of the resource manager exceeds that of the "resource clients".

Desireah answered 11/4, 2014 at 15:46 Comment(0)
G
3

So I suppose what I'm looking for is a deferencable weak_ptr that cannot be converted into a shared_ptr.

You could hand out your one little helper class:

template<typename T>
class NonConvertibleWeakPtr
{
public:
   NonConvertibleWeakPtr(const std::shared_ptr<T>& p) : p_(p) {}
   ... // other constructors / assignment operators
   bool expired() const { return p_.expired(); }
   T* operator->() const { return get(); }
   T& operator*() const { return *get(); }
private:
   T* get() const { return p_.lock().get(); }
private:
   std::weak_ptr<T> p_;
};

This is slightly better than a raw pointer, because you can check if your pointer is still valid.

Example usage:

std::shared_ptr<int> sp = std::make_shared<int>(5);
{
    NonConvertibleWeakPtr<int> wp(sp);
    if(!wp.expired()) {
        std::cout << *wp << std::endl;
    }
}

However a user can still misuse it for example with std::shared_ptr<T> blah(&(*wp));, but it takes a bit more criminal energy.

Gratuity answered 11/4, 2014 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.