Boost logo

Boost :

Subject: Re: [boost] [smart_ptr] Is there any interest in unique_ptr with type erased deleter?
From: Andrey Semashev (andrey.semashev_at_[hidden])
Date: 2017-03-21 13:16:47


For some reason my email went directly to Andrey instead of the mailing
list. I'm re-sending the message below.

PS: This has happened to me multiple times now after we moved the ML
infrastructure. Is there a way to protect oneself from such mistakes? Am
I the only one so inattentive to have this problem?

On 03/21/17 16:11, Andrey Semashev wrote:
> On 03/21/17 14:54, Andrey Davydov wrote:
>> On Tue, Mar 21, 2017 at 2:15 PM, Andrey Semashev via Boost
>> <boost_at_[hidden] <mailto:boost_at_[hidden]>> wrote:
>>
>> On 03/21/17 12:54, Andrey Davydov via Boost wrote:
>>
>> On Tue, Mar 21, 2017 at 12:00 PM, Richard Hodges
>> <hodges.r_at_[hidden] <mailto:hodges.r_at_[hidden]>> 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.
>
>> Now, I can understand the need to perform casts on smart-pointers,
>> but they should still provide some level of safety, at least the
>> same level that is provided by pointer casts in the core language.
>> But supporting casts should not require a new smart-pointer. And
>> indeed it doesn't:
>>
>> template< typename U, typename T, typename D >
>> std::unique_ptr< U, D > static_pointer_cast(
>> std::unique_ptr< T, D >&& p)
>> {
>> D d = p.get_deleter();
>> return std::unique_ptr< U, D >(
>> static_cast< U* >(p.release()),
>> std::move(d));
>> }
>>
>> What is `D`? If it is std::default_delete<T> then function result type
>> will be std::unique_ptr<U, std::default_delete<T>> and unlikely this
>> type is very useful (even if it compiles).
>
> Ok, that won't work with std::default_delete<T>. But it will work with
> this deleter:
>
> struct static_polymorphic_deleter
> {
> template< typename T >
> void operator() (T* p) const noexcept
> {
> std::default_delete<T>()(p);
> }
> };
>
> And it should work with a runtime polymorphic deleter that can be
> written to implement your proposal (see below).
>
>> The other part of your proposal, which is polymorphic behavior with
>> a deleter knowing the type to call the destructor on, can be solved
>> by a custom deleter. I'm not sure a generalized version of such
>> deleter would have a large demand, given that there is the
>> alternative TONGARI J suggested, but I'm not opposed to a proposal.
>>
>> May be I doesn't understand what TONGARI J suggested, but
>> how can `unique_ptr<T, void(*)(T*)>` hold stateful deleter?
>
> It doesn't, unless the required state is contained within the object
> itself. But just to implement runtime polymorphism, no additional state
> is required.
>
> struct base
> {
> protected:
> ~base() = default;
> };
>
> struct derived : base
> {
> };
>
> template< typename T >
> void poly_deleter(base* p) noexcept
> {
> delete static_cast< T* >(p);
> }
>
> template< typename T >
> using poly_unique_ptr = std::unique_ptr< T, void(*)(base*) >;
>
> template< typename T >
> poly_unique_ptr< T > make_poly_unique()
> {
> T* p = new T();
> return poly_unique_ptr< T >{ p, &poly_deleter< T > };
> }
>
> poly_unique_ptr< derived > pd1 = make_poly_unique< derived >();
> poly_unique_ptr< base > pb1 = std::move(pd1);
> poly_unique_ptr< derived > pd2 =
> static_pointer_cast< derived >(std::move(pb1));
>
> 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<void, poly_deleter>
> (i.e. don't have a universal base class).
>


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk