![](/img/trans.png)
[英]How do I initialize a std::unique_ptr with a deleter in a constructor?
[英]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 的成员来执行此操作?
假设create
和destroy
是具有以下签名的免费函数(从 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。
使用 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.