John Dibling
John Dibling

Reputation: 101456

shared_ptr with non-pointer resources

In C++11 is it possible to use shared_ptr to control non-pointer resources?


It is possible to use unique_ptr to manage non-pointer resources. This is done by implementing a custom deleter class which provides:

  1. A typedef {TYPE} pointer; where {TYPE} is the non-pointer resource type
  2. operator()(pointer) which frees the controlled resource

...and then instantiating a unique_ptr with the custom deleter as the second template parameter.

For example, under Windows it is possible to create a unique_ptr which manages a service control handle. This handle type is not freed by calling delete, but by calling CloseServiceHandle(). Here is sample code which does this:

Custom Deleter

struct SvcHandleDeleter
{
    typedef SC_HANDLE pointer;
    SvcHandleDeleter() {};

    template<class Other> SvcHandleDeleter(const Other&) {};

    void operator()(pointer h) const
    {
        CloseServiceHandle(h);
    }
};


typedef std::unique_ptr<SC_HANDLE,SvcHandleDeleter> unique_sch;

Instantiation

unique_sch scm(::OpenSCManagerA(0, 0, SC_MANAGER_ALL_ACCESS));

Is it possible to use shared_ptr to control a non-pointer resource as well?

According to the documentation, there are shared_ptr constructor overloads which take provide the means to provide a custom deleter class, but none of the constructors accept a resource type that is not either a pointer or a wrapper around a pointer.

How can this be done?

Upvotes: 25

Views: 3190

Answers (4)

emsr
emsr

Reputation: 16333

Well, shared_ptr will invoke the destructor of the pointed to object as soon as the last reference to the pointer is released then whatever the class contains can be released. Just make a class maybe like this:

struct SvcHandle
{
  typedef SC_HANDLE pointer;
  SvcHandle()
  :M_handle(::OpenSCManagerA(0, 0, SC_MANAGER_ALL_ACCESS))
  { }

  ~SvcHandle()
  {
      CloseServiceHandle(M_handle);
  }
private:
  pointer M_handle;
};

Then just make a shared pointer with a new SvcHandle. The lifetime management of the handle will go with the shared_ptr - RAII at its finest.

Upvotes: 5

Xeo
Xeo

Reputation: 131789

Sadly, shared_ptr's need for type-erasure makes it impossible with the current interface to achieve exactly what you want. unique_ptr manages to do that because it has static information on the actual deleter type, from where it can draw the actual "pointer" type. In shared_ptr's case, the deleter type is lost in the type-erasure process (which is why you can't specify it in the shared_ptr template).

Also note that unique_ptr doesn't provide any converting constructors like shared_ptr does (e.g. template<class Y> shared_ptr(Y* p)). It can't do so because pointer is not necessarily a real pointer type, and so it can't restrict what can be accepted (except maybe through some SFINAE with std::is_convertible_to or something like that... but I digress).

Now, one obvious workaround is to simply new the resource handle, as dumb as it sounds. :/

std::shared_ptr<SC_HANDLE> sp(new SC_HANDLE(::OpenSCManagerA(0, 0, SC_MANAGER_ALL_ACCESS)),
    [](SC_HANDLE* p){ ::CloseServiceHandle(*p); delete p; });

Upvotes: 16

EddieBytes
EddieBytes

Reputation: 1343

How about this?

auto scm = make_shared<unique_sch>(::OpenSCManagerA(0, 0, SC_MANAGER_ALL_ACCESS));

unique_sch is the class you mentioned in your question. Now use scm as a shared pointer to your resource. It's not the nicest thing to dereference when needed, but you did ask if it is possible.

But that's still using a pointer. As can be seen in the documentation, the unique_ptr class takes a pointer class as a constructor parameter, which can in fact be anything. shared_ptr however takes a type as a template parameter and forcefully turns that into a pointer to that type on its constructor:

template< class Y, class Deleter > shared_ptr( Y* ptr, Deleter d );

I think it's safe to say it can't directly be used to manage a non-pointer resource, because the shared_ptr class makes the assumption that its resource is a pointer.

Upvotes: 1

ForEveR
ForEveR

Reputation: 55887

Think no. Since standard provide such constructors for shared_ptr and no other one.

// 20.7.2.2.1, constructors:
constexpr shared_ptr() noexcept;
template<class Y> explicit shared_ptr(Y* p);
template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d)
template <class D, class A> shared_ptr(nullptr_t p, D d, A a)
template<class Y> shared_ptr(const shared_ptr<Y>& r, T *p) noexcept;
shared_ptr(const shared_ptr& r) noexcept;
template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
shared_ptr(shared_ptr&& r) noexcept;
template<class Y> shared_ptr(shared_ptr<Y>&& r) noexcept;
template<class Y> explicit shared_ptr(const weak_ptr<Y>& r);
template<class Y> shared_ptr(auto_ptr<Y>&& r);
template <class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);
constexpr shared_ptr(nullptr_t) : shared_ptr() { }

And how you want to do for example (for unique_ptr)

pointer release() noexcept;

1 Postcondition: get() == nullptr. 2 Returns: The value get() had at the start of the call to release.

With no pointer resource? You try to hack language. It's always bad idea.

Upvotes: 0

Related Questions