huitlarc
huitlarc

Reputation: 2173

How do I use a custom deleter with a std::unique_ptr member?

I have a class with a unique_ptr member.

class Foo {
private:
    std::unique_ptr<Bar> bar;
    ...
};

The Bar is a third party class that has a create() function and a destroy() function.

If I wanted to use a std::unique_ptr with it in a stand alone function I could do:

void foo() {
    std::unique_ptr<Bar, void(*)(Bar*)> bar(create(), [](Bar* b){ destroy(b); });
    ...
}

Is there a way to do this with std::unique_ptr as a member of a class?

Upvotes: 190

Views: 199949

Answers (10)

IceFire
IceFire

Reputation: 4147

Simple is also:

class Foo {};
class Bar
{
public:
    Bar()
    {
        // actual initialisation at some point
    }

private:
    std::unique_ptr<Foo, void(*)(Foo*)> foo = {{}, {}}; // or = {nullptr, {}}
};

Sure, you can also create some helper function to do the job to not have the initial state at any time.

In fact, in your specific scenario, the cleanest way is to actually put your Bar (not mine, sorry for the confusion) into a simple wrapper class, which makes reuse easier.

Upvotes: 1

Jan Wilmans
Jan Wilmans

Reputation: 823

#include "fmt/core.h"
#include <memory>

class example {};

void delete_example(example *)
{
    fmt::print("delete_example\n");
}

using example_handle = std::unique_ptr<example, decltype([] (example * p) 
{ 
    delete_example(p); 
})>;

int main()
{
    example_handle handle(new example);
}

Just my two cents, using C++20.

https://godbolt.org/z/Pe3PT49h4

Upvotes: 6

Justin
Justin

Reputation: 25377

Unless you need to be able to change the deleter at runtime, I would strongly recommend using a custom deleter type. For example, if use a function pointer for your deleter, sizeof(unique_ptr<T, fptr>) == 2 * sizeof(T*). In other words, half of the bytes of the unique_ptr object are wasted.

Writing a custom deleter to wrap every function is a bother, though. Thankfully, we can write a type templated on the function:

Since C++17:

template <auto fn>
struct deleter_from_fn {
    template <typename T>
    constexpr void operator()(T* arg) const {
        fn(arg);
    }
};

template <typename T, auto fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;

// usage:
my_unique_ptr<Bar, destroy> p{create()};

Prior to C++17:

template <typename D, D fn>
struct deleter_from_fn {
    template <typename T>
    constexpr void operator()(T* arg) const {
        fn(arg);
    }
};

template <typename T, typename D, D fn>
using my_unique_ptr = std::unique_ptr<T, deleter_from_fn<D, fn>>;

// usage:
my_unique_ptr<Bar, decltype(&destroy), destroy> p{create()};

Upvotes: 59

user240515
user240515

Reputation: 3207

I'm fairly convinced that this is the best current way to do it:

#include <memory>
#include <stdio.h>

template <typename T, auto fn>
struct Deleter
{
  void operator()(T *ptr)
  {
    fn(ptr);
  }
};

template <typename T, auto fn>
using handle = std::unique_ptr<T, Deleter<T, fn>>;

using file = handle<FILE, fclose>;

int main()
{
  file f{fopen("a.txt", "w")};
  return 0;
}

Because you've specified a Functor as the deleter in the unique_ptr's template arguments, you don't need to set a deleter when calling its constructor.

The Deleter functor uses "template auto" to take a deletion function (in this example: fclose) as a template argument, so this needs C++17.

Expanding it to support other types is just one extra "using" line per type.

Upvotes: 4

johv
johv

Reputation: 4594

With a lambda you can get the same size as a plain std::unique_ptr. Compare the sizes:

plain: 8
lambda: 8
fpointer: 16
std::function: 40

Which is the output of the following. (I declared the lambda outside the scope of the class. Not sure if you can scope it inside the class.)

#include <iostream>
#include <memory>
#include <functional>

struct Bar {};
void destroy(Bar* b) {}
Bar* create() { return 0; }

auto lambda_destroyer = [](Bar* b) {destroy(b);};

class Foo {
    
    std::unique_ptr<Bar, decltype(lambda_destroyer)> ptr_;

public:

    Foo() : ptr_(create(), lambda_destroyer) { /* ... */ }
};

int main()
{
    std::cout << "plain: "         << sizeof (std::unique_ptr<Bar>) << std::endl
              << "lambda: "        << sizeof (std::unique_ptr<Bar, decltype(lambda_destroyer)>) << std::endl
              << "fpointer: "      << sizeof (std::unique_ptr<Bar, void(*)(Bar*)>) << std::endl
              << "std::function: " << sizeof (std::unique_ptr<Bar, std::function<void(Bar*)>>) << std::endl;
}

Upvotes: 4

Deduplicator
Deduplicator

Reputation: 45704

You know, using a custom deleter isn't the best way to go, as you will have to mention it all over your code.
Instead, as you are allowed to add specializations to namespace-level classes in ::std as long as custom types are involved and you respect the semantics, do that:

Specialize std::default_delete:

template <>
struct ::std::default_delete<Bar> {
    default_delete() = default;
    template <class U>
    constexpr default_delete(default_delete<U>) noexcept {}
    void operator()(Bar* p) const noexcept { destroy(p); }
};

And maybe also do std::make_unique():

template <>
inline ::std::unique_ptr<Bar> ::std::make_unique<Bar>() {
    auto p = create();
    if (!p)
        throw std::runtime_error("Could not `create()` a new `Bar`.");
    return { p };
}

Upvotes: 23

Drew Noakes
Drew Noakes

Reputation: 311315

It's possible to do this cleanly using a lambda in C++11 (tested in G++ 4.8.2).

Given this reusable typedef:

template<typename T>
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;

You can write:

deleted_unique_ptr<Foo> foo(new Foo(), [](Foo* f) { customdeleter(f); });

For example, with a FILE*:

deleted_unique_ptr<FILE> file(
    fopen("file.txt", "r"),
    [](FILE* f) { fclose(f); });

With this you get the benefits of exception-safe cleanup using RAII, without needing try/catch noise.

Upvotes: 154

Cassio Neri
Cassio Neri

Reputation: 20543

Assuming that create and destroy are free functions (which seems to be the case from the OP's code snippet) with the following signatures:

Bar* create();
void destroy(Bar*);

You can write your class Foo like this

class Foo {

    std::unique_ptr<Bar, void(*)(Bar*)> ptr_;

    // ...

public:

    Foo() : ptr_(create(), destroy) { /* ... */ }

    // ...
};

Notice that you don't need to write any lambda or custom deleter here because destroy is already a deleter.

Upvotes: 179

rici
rici

Reputation: 241931

You just need to create a deleter class:

struct BarDeleter {
  void operator()(Bar* b) { destroy(b); }
};

and provide it as the template argument of unique_ptr. You'll still have to initialize the unique_ptr in your constructors:

class Foo {
  public:
    Foo() : bar(create()), ... { ... }

  private:
    std::unique_ptr<Bar, BarDeleter> bar;
    ...
};

As far as I know, all the popular c++ libraries implement this correctly; since BarDeleter doesn't actually have any state, it does not need to occupy any space in the unique_ptr.

Upvotes: 116

mkaes
mkaes

Reputation: 14129

You can simply use std::bind with a your destroy function.

std::unique_ptr<Bar, std::function<void(Bar*)>> bar(create(), std::bind(&destroy,
    std::placeholders::_1));

But of course you can also use a lambda.

std::unique_ptr<Bar, std::function<void(Bar*)>> ptr(create(), [](Bar* b){ destroy(b);});

Upvotes: 7

Related Questions