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

It is widely known that deleter type is part of std::unique_ptr type. It is
the most effective decision, but sometimes not very convenient. The most
important use cases (for me) when std::unique_ptr

2017-03-21 15:04 GMT+08:00 Andrey Davydov via Boost
I have implemented smart pointer with semantics of unique ownership and type erased deleter (https://github.com/AndreyG/unnamed). It has size of 3 pointers and it doesn't require additional memory allocations for the case when deleter is empty class (for example, std::default_delete or non-capturing lambda) and for the case when deleter is known at compile time function (for instance, fclose). The are some simple examples of usage and tests in the repo.
Why don't just use unique_ptr
>? The main reason is that due to the small object optimization std::function has rather big size. For instance, sizeof(unique_ptr >) == 72 for MSVC x64! Results for the other platforms can be found in the repo README. Does it seem useful?
Why not:
```
template<class T>
using my_ptr = unique_ptr

On Tue, Mar 21, 2017 at 11:10 AM, TONGARI J via Boost wrote: 2017-03-21 15:04 GMT+08:00 Andrey Davydov via Boost :
[...] I have implemented smart pointer with semantics of unique ownership and
type erased deleter (https://github.com/AndreyG/unnamed). It has size
of 3
pointers and it doesn't require additional memory allocations for the
case
when deleter is empty class (for example, std::default_delete or
non-capturing lambda) and for the case when deleter is known at compile
time function (for instance, fclose). The are some simple examples of
usage
and tests in the repo. Why don't just use unique_ptr Does it seem useful? Why not:
```
template<class T>
using my_ptr = unique_ptr Because I'd like, for example, cast my_ptr<Derived> to my_ptr<Base>.
--
Andrey Davydov

It’s an interesting and, on the face of it, obviously useful idea. What is required, more than simply creating a type-erased deleter? Isn’t this simply a partial specialisation of std::unique_ptr?
On 21 Mar 2017, at 08:04, Andrey Davydov via Boost
wrote: It is widely known that deleter type is part of std::unique_ptr type. It is the most effective decision, but sometimes not very convenient. The most important use cases (for me) when std::unique_ptr
doesn't work are following: 1. unique_ptr to incomplete class struct MyClass; unique_ptr<MyClass> create(); auto ptr = create(); // compilation error 2. upcast to base without polymorphic destructor struct Base { // ... // no virtual ~Base() here }; struct Derived : Base { // ... }; unique_ptr<Base> ptr = make_unique<Derived>(); // compiles by leaks!
3. unique_ptr with non-default deleter unique_ptr
open_file(std::string const & path) // works but looks ugly, why should the fact that fclose returns `int` be visible from the signature of the function `open_path`? { return { std::fopen(path, "r"), &std::fclose }; } Of course, all this examples could be fixed if unique_ptr would be replaced by shared_ptr, but the semantic of the shared ownership is not desirable often. I have implemented smart pointer with semantics of unique ownership and type erased deleter (https://github.com/AndreyG/unnamed). It has size of 3 pointers and it doesn't require additional memory allocations for the case when deleter is empty class (for example, std::default_delete or non-capturing lambda) and for the case when deleter is known at compile time function (for instance, fclose). The are some simple examples of usage and tests in the repo.
Why don't just use unique_ptr
>? The main reason is that due to the small object optimization std::function has rather big size. For instance, sizeof(unique_ptr >) == 72 for MSVC x64! Results for the other platforms can be found in the repo README. Does it seem useful?
-- Andrey Davydov
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On Tue, Mar 21, 2017 at 12:00 PM, Richard Hodges
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);
Isn’t this simply a partial specialisation of std::unique_ptr?
It seems to me that to declare specialization of std::unique_ptr is undefined behavior. -- Andrey Davydov.

On 03/21/17 12:54, Andrey Davydov via Boost wrote:
On Tue, Mar 21, 2017 at 12:00 PM, Richard Hodges
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. 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)); } 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.

Here's a first cut of a polymorphic deleter (which at least allows you to
spell the name of the unique_ptr<A> in the example). It's not optimally
efficient, but a little SFO should do it.
#include <memory>
#include <iostream>
namespace notstd {
class polymorphic_deleter
{
struct concept
{
virtual void impl_call() const = 0;
};
template
On 03/21/17 12:54, Andrey Davydov via Boost wrote:
On Tue, Mar 21, 2017 at 12:00 PM, Richard Hodges
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.
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)); }
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.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman /listinfo.cgi/boost

On Tue, Mar 21, 2017 at 2:22 PM, Richard Hodges via Boost < boost@lists.boost.org> wrote:
Here's a first cut of a polymorphic deleter (which at least allows you to spell the name of the unique_ptr<A> in the example). It's not optimally efficient, but a little SFO should do it.
#include <memory> #include <iostream>
namespace notstd {
class polymorphic_deleter { struct concept { virtual void impl_call() const = 0; };
template
struct model final : concept { model(T *p, Deleter del) : p_(p) , del_(std::move(del)) {} void impl_call() const { del_(p_); }
T *p_; Deleter del_; };
std::unique_ptr<concept> impl_;
template
static auto make_model(T* p, Del&& del) { using deleter_type = std::decay_t<Del>; using pointer_type = T; return std::make_unique > ( p, std::forward<Del>(del) ); }; public:
template
polymorphic_deleter(T* p, Del&& del) : impl_(make_model(p, std::forward<Del>(del))) {} void operator()(void*) const { impl_->impl_call(); }
};
template
auto make_polymorphic_unique(Args&&...args) { auto pt = std::make_unique<T>(std::forward<Args>(args)...); auto del = polymorphic_deleter(pt.get(), std::default_delete<T>()); return std::unique_ptr (pt.release(), std::move(del)); }; }
struct A { ~A() { std::cout << "deleting A\n"; } };
struct B : A { ~B() { std::cout << "deleting B\n"; } };
int main() { auto pb = notstd::make_polymorphic_unique<B>(); auto pa = std::unique_ptr( std::move(pb)); pa.reset(); }
Class `polymorphic_deleter` is implementation of the `function

Richard Hodges wrote:
Here's a first cut of a polymorphic deleter...
It's easier to write if you wrap boost::detail::shared_count. That way you'd even be able to form weak pointers (although I'm not sure we have the necessary public interface for that.) shared_ptr<void> works as a deleter too, this keeps us entirely within the public interface, but we'll waste a pointer. Although with it you'll be able to use make_shared inside make_poly_unique.

Richard Hodges wrote:
Here's a first cut of a polymorphic deleter...
It's easier to write if you wrap boost::detail::shared_count. That way you'd even be able to form weak pointers (although I'm not sure we have the necessary public interface for that.)
shared_ptr<void> works as a deleter too, this keeps us entirely within the public interface, but we'll waste a pointer. Although with it you'll be able to use make_shared inside make_poly_unique.
Although on second thought both of these break horribly after release().

On Tue, Mar 21, 2017 at 2:15 PM, Andrey Semashev via Boost < 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
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.
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).
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

May be the best solution will be implement type_erased_unique_ptr
Re-posting here as I replied direct to Andrey by mistake:
publicly inheriting from std::unique_ptr
On Tue, Mar 21, 2017 at 2:15 PM, Andrey Semashev via Boost < 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
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.
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).
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? -- Andrey Davydov
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/ mailman/listinfo.cgi/boost
participants (5)
-
Andrey Davydov
-
Andrey Semashev
-
Peter Dimov
-
Richard Hodges
-
TONGARI J