173

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?

huitlarc
  • 1,993
  • 2
  • 13
  • 12

10 Answers10

165

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.

Cassio Neri
  • 19,583
  • 7
  • 46
  • 68
  • 195
    With C++11 `std::unique_ptr ptr_;` – Joe Mar 26 '15 at 17:32
  • 10
    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](https://stackoverflow.com/a/19054280/364696) and [Deduplicator's](https://stackoverflow.com/a/50957671/364696) answers avoid all of these costs by specializing to a functor. – ShadowRanger Nov 21 '19 at 21:07
  • 3
    @ShadowRanger isn't it defined to default_delete and stored function pointer everytime whether you pass it explicitly or not? – IC_ Jun 24 '20 at 04:19
146

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.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • 2
    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? – j00hi Jul 08 '15 at 10:38
  • 26
    @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. – magras Oct 12 '15 at 17:57
  • 9
    This will leak memory if std::function constructor throws (which might happen if lambda is too big to fit inside std::function object) –  Mar 15 '16 at 12:48
  • 4
    Is lambda really requires here? It can be simple `deleted_unique_ptr foo(new Foo(), customdeleter);` if `customdeleter` follows the convention (it returns void and accepts raw pointer as an argument). – VP. Sep 05 '17 at 08:47
  • 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. – GeniusIsme Sep 27 '17 at 12:26
  • 1
    @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). – ShadowRanger Nov 21 '19 at 20:57
  • @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. – ShadowRanger Nov 21 '19 at 21:09
  • @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. – GeniusIsme Dec 03 '19 at 07:23
103

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.

rici
  • 234,347
  • 28
  • 237
  • 341
  • 14
    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`) 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[10];` – DavidF Jul 08 '16 at 11:47
  • 15
    I would create a typedef for easy use `typedef std::unique_ptr UniqueBarPtr` – DavidF Jul 08 '16 at 11:51
  • @DavidF: Or use [Deduplicator's approach](https://stackoverflow.com/a/50957671/364696), 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` 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) – ShadowRanger Nov 21 '19 at 21:00
  • 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). – Zylann Apr 10 '22 at 17:29
47

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()};
Justin
  • 24,288
  • 12
  • 92
  • 142
  • 1
    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](https://stackoverflow.com/a/19054280/364696), just with less boilerplate? – ShadowRanger Nov 21 '19 at 21:12
  • Yes, this should provide all the benefits of a custom deleter class, since that's what `deleter_from_fn` is. – rmcclellan Feb 02 '20 at 22:01
  • // https://stackoverflow.com/questions/19053351/how-do-i-use-a-custom-deleter-with-a-stdunique-ptr-member/51274008#51274008 // https://stackoverflow.com/questions/38456127/what-is-the-value-of-cplusplus-for-c17/58316194#58316194 #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) //C++17 specific stuff here // my_unique_ptr p{create()}; #define MY_UNIQUE_PTR(T, D) my_unique_ptr #else // my_unique_ptr p{create()}; #define MY_UNIQUE_PTR(T, D) my_unique_ptr #endif – samm Jun 01 '21 at 08:11
  • 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. – asherkin Jan 07 '22 at 16:11
  • @asherkin You are correct. It could also be solved by using `D* fn` as the template parameter and using `deleter_from_fn`, but doing that would mean that `my_unique_ptr` would only work for function pointers, not for arbitrary function objects. – Justin Jan 08 '22 at 07:01
19

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 };
}
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • 4
    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` was not allocated from `create()`, but from some other allocation function. – Justin Jul 10 '18 at 20:18
  • 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()(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`, rather than keeping the behavior the same. – Justin Jul 10 '18 at 21:08
  • 1
    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` 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. – ShadowRanger Nov 21 '19 at 20:54
  • 1
    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. – spectras Apr 09 '21 at 16:59
4

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);});
mkaes
  • 13,781
  • 10
  • 52
  • 72
4
#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

Jan Wilmans
  • 665
  • 6
  • 10
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;
}
johv
  • 4,424
  • 3
  • 26
  • 42
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.

user240515
  • 3,056
  • 1
  • 27
  • 34
0

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.

IceFire
  • 4,016
  • 2
  • 31
  • 51