Re: [boost] [smart_ptr] Is there any interest in unique_ptr with type erased deleter?
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
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.
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 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
` 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
(i.e. don't have a universal base class).
participants (1)
-
Andrey Semashev