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

C++C++11Move SemanticsUnique Ptr

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

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.

Solution 2 - C++

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.

Solution 3 - C++

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.

Solution 4 - C++

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()};

Solution 5 - C++

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 };
}

Solution 6 - C++

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);});

Solution 7 - C++

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;
}

Solution 8 - C++

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.

Solution 9 - C++

#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

Solution 10 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionhuitlarcView Question on Stackoverflow
Solution 1 - C++Cassio NeriView Answer on Stackoverflow
Solution 2 - C++Drew NoakesView Answer on Stackoverflow
Solution 3 - C++riciView Answer on Stackoverflow
Solution 4 - C++JustinView Answer on Stackoverflow
Solution 5 - C++DeduplicatorView Answer on Stackoverflow
Solution 6 - C++mkaesView Answer on Stackoverflow
Solution 7 - C++johvView Answer on Stackoverflow
Solution 8 - C++user240515View Answer on Stackoverflow
Solution 9 - C++Jan WilmansView Answer on Stackoverflow
Solution 10 - C++IceFireView Answer on Stackoverflow