繁体   English   中英

如何使用带有 std::unique_ptr 成员的自定义删除器?

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

我有一个 class 和一个 unique_ptr 成员。

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

Bar 是第三方 class,它有一个 create() function 和一个 destroy() function。

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

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

有没有办法将std::unique_ptr作为 class 的成员来执行此操作?

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

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

你可以这样写你的类Foo

class Foo {

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

    // ...

public:

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

    // ...
};

请注意,您无需在此处编写任何 lambda 或自定义删除器,因为destroy已经是一个删除器。

可以使用 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 进行异常安全清理的好处,而无需 try/catch 噪音。

你只需要创建一个删除器类:

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

并将其作为unique_ptr的模板参数提供。 您仍然需要在构造函数中初始化 unique_ptr :

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

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

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

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

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

从 C++17 开始:

template <auto fn>
using deleter_from_fn = std::integral_constant<decltype(fn), fn>;

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>
using deleter_from_fn = std::integral_constant<D, fn>;

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

您知道,使用自定义删除器并不是最好的方法,因为您必须在整个代码中提及它。
相反,只要涉及自定义类型并且您尊重语义,就可以向::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 };
}

您可以简单地将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);});
#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

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

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

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

扩展它以支持其他类型只是每种类型的一个额外的“使用”行。

简单的也是:

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

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

当然,您也可以创建一些帮助程序 function 来完成这项工作,以便在任何时候都没有初始的 state。

事实上,在你的特定场景中,最干净的方法是将你的Bar (不是我的,抱歉造成混淆)放入一个简单的包装器 class 中,这样可以更容易地重用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM