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

时间:2013-09-27 14:31:11

标签: c++ c++11 move-semantics unique-ptr

我有一个带有unique_ptr成员的课程。

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

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

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

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

有没有办法用std::unique_ptr作为班级成员来做这件事?

7 个答案:

答案 0 :(得分:105)

假设createdestroy是自由函数(OP的代码片段似乎就是这种情况),并带有以下签名:

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

您可以像这样编写班级Foo

class Foo {

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

    // ...

public:

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

    // ...
};

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

答案 1 :(得分:94)

使用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获得异常安全清理的好处,而无需尝试/捕获噪音。

答案 2 :(得分:52)

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

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中的任何空格。

答案 3 :(得分:8)

除非您需要能够在运行时更改删除程序,否则我强烈建议您使用自定义删除程序类型。例如,如果将函数指针用于删除器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()};

答案 4 :(得分:4)

您知道,使用自定义删除器并不是最好的方法,因为您必须在代码中全部提及它。
相反,只要涉及自定义类型并且尊重语义,就as you are allowed to add specializations::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); }
};

也许也可以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 };
}

答案 5 :(得分:3)

你可以简单地使用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);});

答案 6 :(得分:0)

使用 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;
}
相关问题