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

hui*_*arc 122 c++ unique-ptr move-semantics c++11

我有一个带有unique_ptr成员的类.

class Foo {
private:
    std::unique_ptr<Bar> bar;
    ...
};
Run Code Online (Sandbox Code Playgroud)

Bar是第三方类,具有create()函数和destroy()函数.

如果我想std::unique_ptr在独立功能中使用它,我可以这样做:

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

std::unique_ptr作为班级成员,有没有办法做到这一点?

Cas*_*eri 121

假设create并且destroy是自由函数(这似乎是来自OP的代码片段的情况)具有以下签名:

Bar* create();
void destroy(Bar*);
Run Code Online (Sandbox Code Playgroud)

你可以写你的类Foo像这样

class Foo {

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

    // ...

public:

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

    // ...
};
Run Code Online (Sandbox Code Playgroud)

请注意,您不需要在此处编写任何lambda或自定义删除器,因为destroy它已经是删除器.

  • 使用C++ 11`std :: unique_ptr <Bar,decltype(&destroy)> ptr_;` (145认同)
  • 这个解决方案的缺点是它使每个“unique_ptr”的开销增加了一倍(它们必须都存储函数指针以及指向实际数据的指针),每次都需要传递销毁函数,它不能内联(因为模板不能专门针对特定函数,只能针对签名),并且必须通过指针调用该函数(比直接调用成本更高)。[rici](/sf/answers/1333799631/) 和 [Deduplicator's](/sf/answers/3567037001/) 答案都通过专门化函子来避免所有这些成本。 (4认同)
  • @ShadowRanger 不是每次都定义为 default_delete&lt;T&gt; 并存储函数指针,无论您是否显式传递它? (2认同)

Dre*_*kes 108

使用C++ 11中的lambda(在G ++ 4.8.2中测试)可以干净利落地完成这项工作.

鉴于此可重用typedef:

template<typename T>
using deleted_unique_ptr = std::unique_ptr<T,std::function<void(T*)>>;
Run Code Online (Sandbox Code Playgroud)

你可以写:

deleted_unique_ptr<Foo> foo(new Foo(), [](Foo* f) { customdeleter(f); });
Run Code Online (Sandbox Code Playgroud)

例如,用FILE*:

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

有了这个,您可以使用RAII获得异常安全清理的好处,而无需尝试/捕获噪音.

  • @ j00hi,在我看来,由于`std :: function`,这个解决方案有不必要的开销.与此解决方案不同,可以内联已接受答案中的Lambda或自定义类.但是,如果要在专用模块中隔离所有实现,则此方法具有优势. (15认同)
  • 如果std :: function构造函数抛出,这将泄漏内存(如果lambda太大而无法放入std :: function对象内,则可能会发生这种情况) (5认同)
  • lambda真的需要吗?它可以很简单`deleted_unique_ptr <Foo> foo(new Foo(),customdeleter);`if`customdeleter`遵循约定(它返回void并接受原始指针作为参数). (4认同)
  • 这应该是答案,imo.这是一个更美丽的解决方案.或者是否存在任何缺点,例如在定义中使用`std :: function`或类似? (2认同)

ric*_*ici 61

您只需要创建一个删除类:

struct BarDeleter {
  void operator()(Bar* b) { destroy(b); }
};
Run Code Online (Sandbox Code Playgroud)

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

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

  private:
    std::unique_ptr<Bar, BarDeleter> bar;
    ...
};
Run Code Online (Sandbox Code Playgroud)

据我所知,所有流行的c ++库都能正确实现; 因为BarDeleter 实际上没有任何状态,所以不需要占用任何空间unique_ptr.

  • 我会创建一个typedef以方便使用`typedef std :: unique_ptr <Bar,BarDeleter> UniqueBarPtr` (12认同)
  • 这个选项是唯一适用于数组,std :: vector和其他集合的选项,因为它可以使用零参数std :: unique_ptr构造函数.其他答案使用无法访问此零参数构造函数的解决方案,因为在构造唯一指针时必须提供Deleter实例.但是这个解决方案提供了一个Deleter类(`struct BarDeleter`)到`std :: unique_ptr`(`std :: unique_ptr <Bar,BarDeleter>`),它允许`std :: unique_ptr`构造函数自己创建一个Deleter实例. .即允许以下代码`std :: unique_ptr <Bar,BarDeleter> bar [10];` (7认同)

Jus*_*tin 15

除非您需要在运行时更改删除器,否则我强烈建议您使用自定义删除器类型.例如,如果为删除器使用函数指针,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()};
Run Code Online (Sandbox Code Playgroud)

在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()};
Run Code Online (Sandbox Code Playgroud)


Jan*_*ans 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);
}
Run Code Online (Sandbox Code Playgroud)

只是我的两分钱,使用 C++20。

https://godbolt.org/z/Pe3PT49h4


mka*_*aes 5

您可以简单地使用std::bind您的destroy函数。

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

但是当然您也可以使用lambda。

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


Ded*_*tor 5

您知道,使用自定义删除器并不是最好的方法,因为您必须在代码中全部提及它。
相反,由于::std只要涉及到自定义类型并且您尊重语义,就可以向名称空间级别的类添加特殊化,因此请执行以下操作:

专长std::default_delete

template <>
struct ::std::default_delete<Bar> {
    default_delete() = default;
    template <class U, class = std::enable_if_t<std::is_convertible<U*, Bar*>()>>
    constexpr default_delete(default_delete<U>) noexcept {}
    void operator()(Bar* p) const noexcept { destroy(p); }
};
Run Code Online (Sandbox Code Playgroud)

也许也可以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 };
}
Run Code Online (Sandbox Code Playgroud)

  • 我会非常小心。打开 `std` 会打开一个全新的蠕虫罐头。还要注意,在 C++20 之后不允许对 `std::make_unique` 进行特化(因此之前不应该这样做),因为 C++20 不允许对 `std` 中不是类模板的东西进行特化(` std::make_unique` 是一个函数模板)。请注意,如果传入 `std::unique_ptr&lt;Bar&gt;` 的指针不是从 `create()` 分配的,而是从其他一些分配函数分配的,那么你也可能最终得到 UB。 (3认同)
  • `make_unique` 专业化是有问题的,但我肯定使用了 `std::default_delete` 重载(不是用 `enable_if` 模板化的,仅适用于像 OpenSSL 的 `BIGNUM` 这样使用已知破坏函数的 C 结构,其中子类化不是'不会发生),这是迄今为止最简单的方法,因为代码的其余部分可以只使用“unique_ptr&lt;special_type&gt;”,而不需要将函子类型作为模板化的“Deleter”传递,也不需要使用“typedef” /`using` 为所述类型命名以避免该问题。 (2认同)
  • 这可能是最简单的,但也是未定义的行为。这样的专业化是不合法的,因为它**不**满足专业化类型的要求。简而言之,只有当您的特化在给定指针上调用“delete”时,特化“std::default_delete”才是合法的。是的,除了日志记录或类似目的之外,它的用途有限。 (2认同)