Andrey R
Andrey R

Reputation: 185

Make std::unique<T> compatible with std::unique<const T, CustomDeleterType>

In the code I have 3 std::unique_ptr pointer types defined for a particular object:

typedef std::unique_ptr<MyObject> nonConstPtrDefaultDelete;

typedef std::unique_ptr<MyObject, std::function<void(MyObject *)>>
                                                       nonConstPtrCustomDelete;

typedef std::unique_ptr<const MyObject, std::function<void(const MyObject *)>>
                                                       ConstPtrCustomDelete;

I ran into a use case where I need to a convert nonConstPtrDefaultDelete into ConstPtrCustomDelete and nonConstPtrCustomDelete into ConstPtrCustomDelete. In other words:

nonConstPtrDefaultDelete a;
nonConstPtrCustomDelete b;

ConstPtrCustomDelete c1(a);  // Compiler error Deleter has incompatible type
ConstPtrCustomDelete c2(b);  // Compiler error Deleter has incompatible type

The main problem comes from incompatibility of types signature for the deleting functions. It is possible to fix the nonConstPtrCustomDelete case by changing the definition of nonConstPtrCustomDelete type in the following way:

typedef std::unique_ptr<MyObject, std::function<void(const MyObject *)>>
                                                         nonConstPtrCustomDelete

However, the most frequent case with DefaultDelete is still producing compilation error, despite it is intuitively clear that conversion is possible. It there a way workaround that limitation and hint the compiler that functions are castable from one into another?

Thank you

Upvotes: 4

Views: 221

Answers (4)

Jonathan Wakely
Jonathan Wakely

Reputation: 171303

Why are you using std::function here?

Do you actually need different instances of nonConstPtrCustomDelete to have different deleters that share the same signature, or do all instances use the same deleter?

i.e. do you ever do this:

void foo_deleter(MyObject*);
void bar_deleter(MyObject*);

nonConstPtrCustomDelete ptr1{ foo(), foo_deleter };
nonConstPtrCustomDelete ptr1{ bar(), bar_deleter };

or only this:

nonConstPtrCustomDelete ptr1{ foo(), foo_deleter };
nonConstPtrCustomDelete ptr1{ bar(), foo_deleter };

If only the latter then using std::function is pointless, and inefficient. You don't need a polymorphic type-erased function object if you always use the same deleter.

Instead you should write a custom deleter type:

struct non_const_deleter {
  void operator()(MyObject*) const;
};

Then your typedef can be:

typedef std::unique_ptr<MyObject, non_const_deleter>
   nonConstPtrCustomDelete;

it's also easier to use, because that deleter can be default-constructed and so you don't need to provide it explicitly:

nonConstPtrCustomDelete ptr1{ foo() };

that is exactly equivalent to:

nonConstPtrCustomDelete ptr1{ foo(), non_const_deleter{} };

Now you could either add a second deleter type that a non_const_deleter can convert to:

struct const_deleter {
  const_deleter() = default;
  const_deleter(const non_const_deleter&) { }
  void operator()(const MyObject*) const;
};

or maybe just make the one type handle both cases:

struct const_and_non_const_deleter {
  void operator()(MyObject*) const;
  void operator()(const MyObject*) const;
};

Then both types can use the same deleter:

typedef std::unique_ptr<MyObject, const_and_non_const_deleter>
   nonConstPtrCustomDelete;
typedef std::unique_ptr<const MyObject, const_and_non_const_deleter>
   constPtrCustomDelete;

and you won't have problems converting nonConstPtr to constPtr.

Upvotes: 1

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145279

The example given,

nonConstPtrDefaultDelete a;
nonConstPtrCustomDelete b;

ConstPtrCustomDelete c1(a);  // Compiler error Deleter has incompatible type
ConstPtrCustomDelete c2(b);  // Compiler error Deleter has incompatible type

… has two problems:

  • You can't construct a unique_ptr from an lvalue expression. It's single ownership, but the lvalue expression asks for a copy.

  • For the deleter, that a function type void(T*) is not compatible with a function type void(T const*).

A void(T const*) deleter function is necessary when the pointer type is T const*, and works nicely also when the pointer type is T*. So, disregarding established practice, it would be the most practical convention. However, at one time, before the first standardization of C++, deletion via a T const* was considered so unnatural (and the changing const-ness of an object during construction and destruction was so not well understood) that delete p; for such a pointer was invalid.

For the first problem, the common solution is to use std::move from the <utility> header:

ConstPtrCustomDelete c1( move( a ) );
ConstPtrCustomDelete c2( move( b ) );

For the second problem one solution is to outfit every unique_ptr with a custom deleter such as an instance of std::default_delete<T const>.

Another solution is to use a more accepting deleter type in the cases where you want a custom deleter, namely one that 1handles const-ness:

template< class Type >
class Deleter
{
private:
    function<void(Type const*)> d_;

public:
    using Mutable_type = typename remove_const<Type>::type;

    void operator()( Type const* p ) const
    {
        d_( p );
    }

    Deleter(): d_( default_delete<Type const>() ) {}

    Deleter( default_delete<Mutable_type> const& )
        : d_( default_delete<Type const>() )
    {}

    template< class D >
    Deleter( D const& d )
        : d_( [d]( Type const* p ) {
            d( const_cast<Mutable_type*>( p ) );
            } )
    {}
};

Your example code with move and with such a custom deleter, is then

typedef std::unique_ptr<MyObject> nonConstPtrDefaultDelete;

typedef std::unique_ptr<MyObject, Deleter<MyObject>>
    nonConstPtrCustomDelete;

typedef std::unique_ptr<const MyObject, Deleter<MyObject const>>
    ConstPtrCustomDelete;

nonConstPtrDefaultDelete a;
nonConstPtrCustomDelete b;

ConstPtrCustomDelete c1( move( a ) );
ConstPtrCustomDelete c2( move( b ) );

1 There is a potential problem with the const_cast for a rare case, namely formal UB when the object is originally const and the deleter function d calls some non-const member function before the destructor. That can be solved by making this conversion support an explicit choice via template parameter. I.e. client code explicitly guaranteeing an ordinary well-behaved function.

Upvotes: 4

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275500

This should solve your problem:

template<class T>
using smarter_default_delete = std::default_delete<const T>;

template<class T, class D=smarter_default_delete<T>>
using my_unique_ptr = std::unique_ptr<T, D>;

typedef my_unique_ptr<MyObject> nonConstPtrDefaultDelete;

basically, default_delete doesn't accept T const*s. The above fixes that.

You could also jump through hoops on the other two types to force the conversion to work. But the above looks easier.

Upvotes: 3

Slava
Slava

Reputation: 44258

If you are sure that deleter is proper you can convert DefaultDelete to your type:

nonConstPtrDefaultDelete a;
ConstPtrCustomDelete c1( a.release(), your_deleter );

same with const/non const version. But why you need 2 versions (one for const and 1 for not) is not clear.

Upvotes: 6

Related Questions