25

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?

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • Here is [another example](http://codereview.stackexchange.com/questions/4679/shared-ptr-and-file-for-wrapping-cstdio-update-also-dlfcn-h). – Kerrek SB Jul 25 '12 at 14:54
  • But `SC_HANDLE` _is_ a pointer type! In `winsvc.h`: `DECLARE_HANDLE(SC_HANDLE)`. And in `winnt.h`: `#define DECLARE_HANDLE(n) typedef struct n##__{int i;}*n` or `#define DECLARE_HANDLE(n) typedef void *n` depending on some precompiler conditions. – rodrigo Jul 25 '12 at 22:19
  • 1
    @rodrigo The template parameter of a smart pointer is more often than not not a pointer though. `std::unique_ptr` is different from `std::unique_ptr`. More to the point, there *are* APIs where a handle can e.g. have integral type. – Luc Danton Jul 26 '12 at 01:41
  • @LucDanton: Yes, but in the particular example of the OP it **is** a pointer. And most Win32 handles use the `DECLARE_HANDLE` macro, and so they are _syntactically pointers_ too (not that they all point to real memory though). That opens the possibility to solve the OP sample problem using `std::shared_ptr`. – rodrigo Jul 26 '12 at 08:33

4 Answers4

15

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; });
Xeo
  • 129,499
  • 52
  • 291
  • 397
5

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.

emsr
  • 15,539
  • 6
  • 49
  • 62
  • 6
    Don't use a reserved identifier like `_M_sth`. – Xeo Jul 25 '12 at 14:53
  • This is the technique I have employed for about 12 years now. It is implemented in my personal gizmo library much the same as you have here. I was hoping to eliminate the need for this custom code. Alas, this appears to be impossible. – John Dibling Jul 25 '12 at 15:29
  • @Xeo Right. Somehow I had standard library container implementations on the brain. I fixed the example. – emsr Jul 27 '12 at 00:54
  • @JohnDibling There is a RAII-wrapper bubbling up in the standards world: [Scoped Resource - Generic RAII Wrapper for the Standard Library](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3949.pdf) – emsr Apr 05 '14 at 04:31
1

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.

EddieBytes
  • 1,333
  • 9
  • 20
0

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.

ForEveR
  • 55,233
  • 2
  • 119
  • 133