Reputation: 101456
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:
typedef {TYPE} pointer;
where {TYPE}
is the non-pointer resource typeoperator()(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:
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;
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
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
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
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
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