56

According to N3290, std::unique_ptr accepts a deleter argument in its constructor.

However, I can't get that to work with Visual C++ 10.0 or MinGW g++ 4.4.1 in Windows, nor with g++ 4.6.1 in Ubuntu.

I therefore fear that my understanding of it is incomplete or wrong. I can't see the point of a deleter argument that's apparently ignored, so can anyone provide a working example?

Preferably I'd like to see also how that works for unique_ptr<Base> p = unique_ptr<Derived>( new Derived ).

Possibly with some wording from the standard to back up the example, i.e. that with whatever compiler you're using, it actually does what it's supposed to do?

Lightness Races in Orbit
  • 358,771
  • 68
  • 593
  • 989
Cheers and hth. - Alf
  • 135,616
  • 15
  • 192
  • 304

4 Answers4

46

This works for me in MSVC10

int x = 5;
auto del = [](int * p) { std::cout << "Deleting x, value is : " << *p; };
std::unique_ptr<int, decltype(del)> px(&x, del);

And on gcc 4.5, here

I'll skip going to the standard, unless you don't think that example is doing exactly what you'd expect it to do.

Benjamin Lindley
  • 95,516
  • 8
  • 172
  • 256
  • The gcc link is broken, can someone regerate the code? What is the difference in `gcc`? – alfC Jan 25 '14 at 03:21
  • 1
    @alfC: There is no difference. It's the exact same code as shown in my answer. The link was just an online demonstration of the code compiling and running. I've updated it. – Benjamin Lindley Jan 25 '14 at 03:28
  • 2
    Why not just "std::unique_ptr px(&x);" ? – Jon May 29 '14 at 02:43
  • 2
    @Jon: Because lambda types do not have default constructors. – Benjamin Lindley May 29 '14 at 02:51
  • 1
    @Benjamin: Any idea why it works in VS2013? It's a pity that "del" has to be specified twice, and that we have to create an instance when all we really wanted was a type. – Jon May 29 '14 at 03:19
  • @Jon: Couldn't tell you why it works in VS2013. Either a compiler extension or a bug. What do you mean "we have to create an instance when all we really wanted was a type"? You can't define a lambda type without creating an instance of it. – Benjamin Lindley May 29 '14 at 03:36
  • @Benjamin: Yes, I know (why can't we decltype an unevaluated lambda?). My issue is where to put "auto del=..." if I want the unique_ptr to be a class member variable? – Jon May 29 '14 at 03:46
  • 1
    Quite pernicious because the pointer isn't deleted, and indeed shouldn't be as it is a reference to a local object. – Mikhail Jul 16 '15 at 01:19
  • 2
    This answer can be confusing for people trying to learn how to use custom deleters, as the decltype trick only works when the object being managed by unique_ptr is being destroyed at the end of the scope where it is created. If your unique_ptr is stored as a member variable or returned from a function, then there is no way to express that decltype, and you need to use std::function there instead. – pavon Feb 08 '16 at 23:40
26

To complement all previous answers, there is a way to have a custom deleter without having to "pollute" the unique_ptr signature by having either a function pointer or something equivalent in it like this:

std::unique_ptr< MyType, myTypeDeleter > // not pretty

This is achievable by providing a specialization to the std::default_delete template class, like this:

namespace std
{
template<>
class default_delete< MyType >
{
public:
  void operator()(MyType *ptr)
  {
    delete ptr;
  }
};
}

And now all std::unique_ptr< MyType > that "sees" this specialization will be deleted with it. Just be aware that it might not be what you want for all std::unique_ptr< MyType >, so chose carefully your solution.

  • 3
    Specializing std templates is legal and not a bad practice, but there are "rules" you need to follow, see [this post](http://stackoverflow.com/questions/8513417/what-can-and-cant-i-specialize-in-the-std-namespace). std::default_delete is the perfect candidate for template specialization. – Philippe Cayouette Dec 06 '13 at 03:38
  • Perhaps something like: class BIGNUM_ptr : public std::unique_ptr { BIGNUM_ptr(BIGNUM b) : std::unique_ptr(b, &::BN_free); }; although I haven't got it to work yet, and then I'd want to make the class a template... – Sam Liddicott Oct 29 '15 at 13:57
  • I've added a template alternative unique_dptr that allows the destructor to be specified once in the template rather than each time with the instantiation. (with help from @RSahu) http://stackoverflow.com/questions/33418029/calling-inherited-template-constructor-of-unique-ptr-subclass – Sam Liddicott Oct 29 '15 at 17:09
  • This answer using specialisation of `std::default_delete<>` is the most idiomatic and practical solution. Awaiting for [unwanted 24 hours](https://meta.stackexchange.com/questions/220176/existing-answers-should-be-eligible-for-bounty-auto-award-when-the-reason-for-bo) to be able to award a small "thank you" bounty. – iammilind Nov 12 '19 at 09:26
  • @iammilind Note that such a specialization is only legal if it calls `delete` on `ptr`. Which is not usually what you want. And if you implement it in any other way, then it is illegal. – spectras Aug 27 '20 at 10:59
11

My question has been pretty well answered already.

But just in case people wondered, I had the mistaken belief that a unique_ptr<Derived> could be moved to a unique_ptr<Base> and would then remember the deleter for the Derived object, i.e., that Base would not need to have a virtual destructor. That was wrong. I'd select Kerrek SB's comment as "the answer", except one cannot do that for a comment.

@Howard: the code below illustrates one way to achieve what I believed the cost of a dynamically assigned deleter had to mean that unique_ptr supported out of the box:

#include <iostream>
#include <memory>           // std::unique_ptr
#include <functional>       // function
#include <utility>          // move
#include <string>
using namespace std;

class Base
{
public:
    Base() { cout << "Base:<init>" << endl; }
    ~Base() { cout << "Base::<destroy>" << endl; }
    virtual string message() const { return "Message from Base!"; }
};

class Derived
    : public Base
{
public:
    Derived() { cout << "Derived::<init>" << endl; }
    ~Derived() { cout << "Derived::<destroy>" << endl; }
    virtual string message() const { return "Message from Derived!"; }
};

class BoundDeleter
{
private:
    typedef void (*DeleteFunc)( void* p );

    DeleteFunc  deleteFunc_;
    void*       pObject_;

    template< class Type >
    static void deleteFuncImpl( void* p )
    {
        delete static_cast< Type* >( p );
    }

public:
    template< class Type >
    BoundDeleter( Type* pObject )
        : deleteFunc_( &deleteFuncImpl< Type > )
        , pObject_( pObject )
    {}

    BoundDeleter( BoundDeleter&& other )
        : deleteFunc_( move( other.deleteFunc_ ) )
        , pObject_( move( other.pObject_ ) )
    {}

    void operator() (void*) const
    {
        deleteFunc_( pObject_ );
    }
};

template< class Type >
class SafeCleanupUniquePtr
    : protected unique_ptr< Type, BoundDeleter >
{
public:
    typedef unique_ptr< Type, BoundDeleter >    Base;

    using Base::operator->;
    using Base::operator*;

    template< class ActualType >
    SafeCleanupUniquePtr( ActualType* p )
        : Base( p, BoundDeleter( p ) )
    {}

    template< class Other >
    SafeCleanupUniquePtr( SafeCleanupUniquePtr< Other >&& other )
        : Base( move( other ) )
    {}
};

int main()
{
    SafeCleanupUniquePtr< Base >  p( new Derived );
    cout << p->message() << endl;
}
halfer
  • 18,701
  • 13
  • 79
  • 158
Cheers and hth. - Alf
  • 135,616
  • 15
  • 192
  • 304
  • 1
    can you explain what use it has to have a class with a non-virtual destructor, yet derive from it and wanting to get the derived destructor called through the base pointer? Doesn't this go against common sense about virtual destructors? – stijn Jun 18 '12 at 08:53
  • 3
    @stijn: As long as some other mechanism (such as a custom deleter) does the job of identifying the most derived class, the destructor doesn't technically need to be virtual. One valid reason for then making it non-virtual, is to retain compatibility with an externally imposed memory layout, i.e. you don't want a vtable ptr in there at the front of the memory region. Another valid reason is that if destruction is always supposed to be through a deleter, then making the destructor virtual would incorrectly indicate that one could use C++ `delete`, or at least that there was some reason for it. – Cheers and hth. - Alf Jun 18 '12 at 10:42
  • @stjn Another reason is where you are using multiple techniques of memory allocation/deallocation, and you only want the creation point to know about the allocation policy/technique for that particular instance. It is then useful to track the correspondingly correct dtor along with the smart pointer, so that other code points that interact with that smart pointer do not then need to know about the allocation/deallocation policy at compile time. In this context, it is a form of information hiding and reducing code duplication (DRY). – Preet Kukreti Mar 14 '13 at 09:09
6

This works. The destruction happens properly.

class Base
{
    public:
     Base() { std::cout << "Base::Base\n"; }
     virtual ~Base() { std::cout << "Base::~Base\n"; }
};


class Derived : public Base
{
    public:
     Derived() { std::cout << "Derived::Derived\n"; }
     virtual ~Derived() { std::cout << "Derived::~Derived\n"; }
};

void Delete(const Base* bp)
{
    delete bp;
}

int main()
{
    std::unique_ptr<Base, void(*)(const Base*)> ptr = std::unique_ptr<Derived, void(*)(const Base*)>(new Derived(), Delete);
}
Jagannath
  • 3,918
  • 22
  • 28