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

10

181

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?

Armoury answered 27/9, 2013 at 14:31 Comment(0)
P
171

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.

Po answered 27/9, 2013 at 15:24 Comment(3)
With C++11 std::unique_ptr<Bar, decltype(&destroy)> ptr_;Elbowroom
Downside to this solution is that it doubles the overhead of every unique_ptr (they must all store the function pointer along with the pointer to the actual data), requires passing the destruction function every time, it can't inline (since the template can't specialize to the specific function, only the signature), and must call the function through the pointer (more costly than direct call). Both rici and Deduplicator's answers avoid all of these costs by specializing to a functor.Linkoski
@Linkoski isn't it defined to default_delete<T> and stored function pointer everytime whether you pass it explicitly or not?Goatee
I
154

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.

Intelligence answered 9/10, 2014 at 11:5 Comment(8)
This should be the answer, imo. It's a more beautiful solution. Or are there any downsides, like e.g. having std::function in the definition or suchlike?Urea
@j00hi, in my opinion this solution has unnecessary overhead because of std::function. Lambda or custom class as in accepted answer can be inlined unlike this solution. But this approach has advantage in case when you want to isolate all implementation in dedicated module.Mineraloid
This will leak memory if std::function constructor throws (which might happen if lambda is too big to fit inside std::function object)Hamil
Is lambda really requires here? It can be simple deleted_unique_ptr<Foo> foo(new Foo(), customdeleter); if customdeleter follows the convention (it returns void and accepts raw pointer as an argument).Cornucopia
There is one downside to this approach. std::function is not required to use move constructor whenever possible. This means that when you std::move(my_deleted_unique_ptr), contents enclosured by lambda will possibly be copied instead of moved, which may or may be not what you want.Advisement
@VictorPolevoy: Agreed; the lambda wrapping is useful when it gets you type-specialization (avoiding calling a function through a function pointer, and allowing inlining due to full specialization), but in this case, the lambda is being assigned to std::function, which removes both benefits; it can't be inlined, and must be called dynamically (because the same specialization is used for anything deleter with the same signature).Linkoski
@GeniusIsme: I wouldn't consider that a real problem; this lambda (and I imagine most lambdas used for this purpose) has no captures, so there's no state to copy/move anyway.Linkoski
@ShadowRanger: I was bitten by this before. It was not exactly custom unique_ptr deleters, but it involved moving std::function and destructors. Just giving a heads-up.Advisement
R
107

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.

Recurvate answered 27/9, 2013 at 15:15 Comment(4)
this option is the only that works with arrays, std::vector and other collections since it can use the zero parameter std::unique_ptr constructor . other answers use solutions that do not have access to this zero parameter constructor because a Deleter instance must be provided when constructing a unique pointer. But this solution provides a Deleter class (struct BarDeleter) to std::unique_ptr (std::unique_ptr<Bar, BarDeleter>) which allows the std::unique_ptr constructor create a Deleter instance on its own. i.e the following code is allowed std::unique_ptr<Bar, BarDeleter> bar[10];Wampler
I would create a typedef for easy use typedef std::unique_ptr<Bar, BarDeleter> UniqueBarPtrWampler
@DavidF: Or use Deduplicator's approach, which has the same advantages (inlining deletion, no extra storage on each unique_ptr, no need to provide an instance of the deleter when constructing), and adds the benefit of being able to use std::unique_ptr<Bar> anywhere without needing to remember to use the special typedef or explicitly provider the second template parameter. (To be clear, this is a good solution, I up-voted, but it stops one step shy of a seamless solution)Linkoski
I've been using this solution which matches my needs so far, however I find I can't implicitely convert from derived pointer types to base pointer types. That was possible with std::unique_ptr but not with my custom typedef of it that has the custom deleter. What's missing? I didnt want to use the default_delete specialization atm because it's not really matching my needs (even though it might not have this problem).Copenhaver
A
51

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()};
Arvell answered 10/7, 2018 at 21:17 Comment(5)
Nifty. Am I correct that this achieves the same benefits (halved memory overhead, calling function directly rather than through function pointer, potential inlining function call away entirely) as the functor from rici's answer, just with less boilerplate?Linkoski
Yes, this should provide all the benefits of a custom deleter class, since that's what deleter_from_fn is.Merrie
// #19053851 // #38456627 #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) //C++17 specific stuff here // my_unique_ptr<Bar, destroy> p{create()}; #define MY_UNIQUE_PTR(T, D) my_unique_ptr<T, D> #else // my_unique_ptr<Bar, decltype(destroy), destroy> p{create()}; #define MY_UNIQUE_PTR(T, D) my_unique_ptr<T, decltype(&D), D> #endifPrawn
For the pre-C++17 version, I had to use decltype(&destroy) instead of decltype(destroy) - I think this might be a typo in the code.Margy
@Margy You are correct. It could also be solved by using D* fn as the template parameter and using deleter_from_fn<D*, fn>, but doing that would mean that my_unique_ptr would only work for function pointers, not for arbitrary function objects.Arvell
U
21

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 };
}
Unmeaning answered 20/6, 2018 at 22:0 Comment(5)
I would be very careful with this. Opening up std opens up a whole new can of worms. Also note that the specialization of std::make_unique is not allowed post C++20 (thus shouldn't be done before) because C++20 disallows the specialization of things in std which are not class templates (std::make_unique is a function template). Note that you'll also probably end up with UB if the pointer passed into std::unique_ptr<Bar> was not allocated from create(), but from some other allocation function.Arvell
I'm not convinced that this is allowed. It seems to me like it's hard to prove that this specialization of std::default_delete meets the requirement of the original template. I would imagine that std::default_delete<Foo>()(p) would be a valid way to write delete p;, so if delete p; would be valid to write (i.e. if Foo is complete), this wouldn't be the same behavior. Furthermore, if delete p; was invalid to write (Foo is incomplete), this would be specifying new behavior for std::default_delete<Foo>, rather than keeping the behavior the same.Arvell
The make_unique specialization is problematic, but I've definitely used the std::default_delete overload (not templated with enable_if, just for C structs like OpenSSL's BIGNUM that use a known destruction function, where subclassing isn't going to happen), and it's by far the easiest approach, as the rest of your code can just use unique_ptr<special_type> without needing to pass the functor type as the templated Deleter all over, nor use typedef/using to give a name to said type to avoid that issue.Linkoski
It might be the easiest, but it is also undefined behavior. Such a specialization is not legal, because it does not fulfill the requirements for the specialized type. In short, it is only legal to specialize std::default_delete if your specialization calls delete on the given pointer. Yes, it is of limited use beyond logging or similar purposes.Toothache
providing operator()(Bar* p) in the specialization of default_delete seems to be enough to make it workRiana
S
7
#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

Sweettempered answered 15/3, 2022 at 14:7 Comment(0)
C
5

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);});
Coster answered 27/9, 2013 at 14:44 Comment(1)
Both approaches waste space for the deleter instance inside of the unique_ptr object. Such state is not necessary.Erubescent
L
3

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;
}
Ludwigg answered 14/5, 2021 at 13:51 Comment(0)
U
1

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.

Upsurge answered 6/1, 2022 at 6:3 Comment(0)
B
1

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.

Bedcover answered 22/4, 2022 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.