Re: [boost] [smart_ptr] Is there any interest in unique_ptr with type erased deleter?

On Tue, Mar 21, 2017 at 4:11 PM, Andrey Semashev
On 03/21/17 14:54, Andrey Davydov wrote:
On Tue, Mar 21, 2017 at 2:15 PM, Andrey Semashev via Boost
mailto:boost@lists.boost.org> wrote: On 03/21/17 12:54, Andrey Davydov via Boost wrote:
On Tue, Mar 21, 2017 at 12:00 PM, Richard Hodges
mailto:hodges.r@gmail.com> wrote: What is required, more than simply creating a type-erased deleter?
Also deleter must capture pointer, because pointer which was passed to constructor can differ from the pointer which will be deleted. For example,
struct Base { /* ... */ }; struct Derived : Base { /* ... */ };
ptr<Derived> p1(new Derived); void * raw_p1 = p1.get(); ptr<Base> p2 = std::move(p1); void * raw_p2 = p2.get(); assert(raw_p1 != raw_p2);
The above piece of code strikes me as very unsafe and better be avoided in the first place. Designing new tools to handle this case is IMHO misguided.
Why this code is very unsafe? If change `ptr` to `shared_ptr` this will become perfectly valid and widely used pattern.
Because comparing the void pointers is not safe in general. The above code in particular will probably work (although not sure if even that's guaranteed, depending on whether Base and Derived qualify as standard layout classes), but if the class hierarchy is different, that code will silently break.
I agree, I wrote this code just to demonstrate that deleter must capture pointer because it can be changed due to upcasting.
But, of course, nothing stops you from saving the state in the deleter.
struct poly_deleter { template< typename T > poly_deleter(T* p) : m_p(p), m_delete(&poly_deleter::do_delete< T >) { }
void operator() (void*) const noexcept { m_delete(m_p); }
private: template< typename T > static void do_delete(void* p) noexcept { delete static_cast< T* >(p); }
void* m_p; void (*m_delete)(void*); };
This will work even if you form std::unique_ptr
(i.e. don't have a universal base class).
Your poly_deleter implementation is the same as I used inside my smart
pointer implementation, but there is an issue with std::unique_ptr

On 03/21/17 16:33, Andrey Davydov wrote:
On Tue, Mar 21, 2017 at 4:11 PM, Andrey Semashev
mailto:andrey.semashev@gmail.com> wrote: struct poly_deleter { template< typename T > poly_deleter(T* p) : m_p(p), m_delete(&poly_deleter::do_delete< T >) { }
void operator() (void*) const noexcept { m_delete(m_p); }
private: template< typename T > static void do_delete(void* p) noexcept { delete static_cast< T* >(p); }
void* m_p; void (*m_delete)(void*); };
This will work even if you form std::unique_ptr
(i.e. don't have a universal base class). Your poly_deleter implementation is the same as I used inside my smart pointer implementation, but there is an issue with std::unique_ptr
, namely it is not constructible from std::unique_ptr because poly_deleter must capture pointer and so it is not constructible from std::default_delete.
I think that problem can be solved by make_poly_unique or a conversion helper function. Hardly a whole new smart-pointer is an adequate solution.

I think that problem can be solved by make_poly_unique or a conversion helper function. Hardly a whole new smart-pointer is an adequate solution.
+1. Writing a make_poly_unique() and maybe a make_poly_unique_from() would easily address this. Glen -- View this message in context: http://boost.2283326.n4.nabble.com/smart-ptr-Is-there-any-interest-in-unique... Sent from the Boost - Dev mailing list archive at Nabble.com.

A poly_deleter really should not hold just a function pointer as a deleter.
The custom deleter may be any function, containing any amount of state (it
could for example be returning resources to a memory pool, or managing a
semaphore).
fixing the deleter type at `void (*)(void*)` is a no-go. It's not useful.
On 21 March 2017 at 15:33, Andrey Semashev via Boost
On 03/21/17 16:33, Andrey Davydov wrote:
On Tue, Mar 21, 2017 at 4:11 PM, Andrey Semashev
mailto:andrey.semashev@gmail.com> wrote: struct poly_deleter { template< typename T > poly_deleter(T* p) : m_p(p), m_delete(&poly_deleter::do_delete< T >) { }
void operator() (void*) const noexcept { m_delete(m_p); }
private: template< typename T > static void do_delete(void* p) noexcept { delete static_cast< T* >(p); }
void* m_p; void (*m_delete)(void*); };
This will work even if you form std::unique_ptr
(i.e. don't have a universal base class). Your poly_deleter implementation is the same as I used inside my smart pointer implementation, but there is an issue with std::unique_ptr
, namely it is not constructible from std::unique_ptr because poly_deleter must capture pointer and so it is not constructible from std::default_delete. I think that problem can be solved by make_poly_unique or a conversion helper function. Hardly a whole new smart-pointer is an adequate solution.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman /listinfo.cgi/boost

On 03/21/17 17:53, Richard Hodges via Boost wrote:
A poly_deleter really should not hold just a function pointer as a deleter. The custom deleter may be any function, containing any amount of state (it could for example be returning resources to a memory pool, or managing a semaphore).
fixing the deleter type at `void (*)(void*)` is a no-go. It's not useful.
If you want something more generic then just use std::function as a deleter. It's been mentioned earlier in the thread. Personally though, I don't remember needing anything more elaborate than a `void (*)(void*)` function or similar. Probably because if I need something more advanced, I'll likely choose a virtual destructor or shared_ptr or intrusive_ptr. Carrying around a fat deleter in every pointer instance is what I consider a no-go.

Carrying around a fat deleter in every pointer instance is what I consider a no-go.
Nevertheless, there are many scenarios where it would be important. And in
keeping with the well-understood interfaces of shared_ptr and unique_ptr, I
strongly suggest it would be demanded by the community.
By all means have a specialisation of the deleter's implementation for this
case - it's a useful optimisation.
Some scenarios where you'd want a "fat" deleter:
1. you need to return objects to a pool
2. you need to release a semaphore
3. you need to post a notification on a message queue
4. deletion is necessarily constrained by accessing a mutex.
5. the deleter isn't actually fat - it's a stateless class such as
std::default_delete!
I still agree that a type-erased-deleter-unique_ptr is useful. One that
could only accept a function pointer as the deleter would be surprising to
anyone who has had contact with unique_ptr or shared_ptr. I could not
support this constraint.
On 21 March 2017 at 16:00, Andrey Semashev via Boost
On 03/21/17 17:53, Richard Hodges via Boost wrote:
A poly_deleter really should not hold just a function pointer as a deleter. The custom deleter may be any function, containing any amount of state (it could for example be returning resources to a memory pool, or managing a semaphore).
fixing the deleter type at `void (*)(void*)` is a no-go. It's not useful.
If you want something more generic then just use std::function as a deleter. It's been mentioned earlier in the thread.
Personally though, I don't remember needing anything more elaborate than a `void (*)(void*)` function or similar. Probably because if I need something more advanced, I'll likely choose a virtual destructor or shared_ptr or intrusive_ptr. Carrying around a fat deleter in every pointer instance is what I consider a no-go.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman /listinfo.cgi/boost
participants (4)
-
Andrey Davydov
-
Andrey Semashev
-
Glen Fernandes
-
Richard Hodges