如何将自定义删除器与 std: : unique _ ptr 成员一起使用?

我有一个具有惟一 _ ptr 成员的类。

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

Bar 是一个第三方类,它具有 create ()函数和 delete ()函数。

如果我想在独立函数中使用 std::unique_ptr,我可以这样做:

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

有没有一种方法可以将 std::unique_ptr作为一个类的成员来实现这一点?

153061 次浏览

您可以简单地使用 std::bind和销毁函数。

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

当然,你也可以使用 lambda。

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

您只需创建一个 deleter 类:

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

然后提供它作为 unique_ptr的模板参数:

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


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

据我所知,所有流行的 c + + 库都正确地实现了这一点; 因为 BarDeleter实际上没有任何状态,所以它不需要占用 unique_ptr中的任何空间。

假设 createdestroy是具有以下签名的自由函数(从 OP 的代码片段来看似乎是这种情况) :

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

你可以这样写你的类 Foo

class Foo {


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


// ...


public:


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


// ...
};

注意,这里不需要编写任何 lambda 或自定义 deleter,因为 destroy已经是一个 deleter。

可以使用 C + + 11中的 lambda (在 G + + 4.8.2中测试过)清晰地完成这项工作。

考虑到这种可重用的 typedef:

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

你可以写:

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

例如,使用 FILE*:

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

通过这种方法,您可以获得使用 RAII 进行异常安全清除的好处,而无需尝试/捕获噪声。

您知道,使用自定义删除器并不是最好的方法,因为您将不得不在代码中到处提到它。
相反,只要涉及到自定义类型并且尊重语义,因为您可以添加专门化::std中的名称空间级别类就可以这样做:

专业化 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); }
};

也许还有 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 };
}

除非您需要能够在运行时更改删除器,否则我强烈建议使用自定义删除器类型。例如,如果删除器使用函数指针,则为 sizeof(unique_ptr<T, fptr>) == 2 * sizeof(T*)。换句话说,unique_ptr对象的一半字节被浪费了。

但是,编写一个自定义删除器来包装每个函数是一件麻烦的事情。值得庆幸的是,我们可以在函数上编写一个模板化的类型:

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

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

使用 lambda 可以得到与普通 std::unique_ptr相同的尺寸:

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

它是下面的输出。(我在类的范围之外声明了 lambda。不确定是否可以在类中对其进行范围。)

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

我相当确信这是目前最好的方法:

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

因为您已经在 special _ ptr 的模板参数中指定了一个 Functor 作为删除器,所以在调用它的构造函数时不需要设置一个删除器。

Deleter 函数使用“ template auto”将一个删除函数(在本例中为 fclose)作为模板参数,因此这需要 C + + 17。

扩展它以支持其他类型只是为每个类型增加一行“使用”。

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

用 C + + 20就行了。

Https://godbolt.org/z/pe3pt49h4

简单也是:

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


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

当然,您也可以创建一些 helper 函数来执行任务,以便在任何时候都不具有初始状态。

事实上,在您的特定场景中,最简洁的方法是将您的 Bar(不是我的,抱歉造成了混淆)实际放入一个简单的包装器类中,这使得重用更加容易。