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@lists.boost.org <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
<hodges.r@gmail.com <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<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).