Answers:
假设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
它已经是删除器。
unique_ptr
(它们必须全部将函数指针与指向实际数据的指针一起存储),要求每次都传递销毁函数,因此无法内联(因为模板无法专门针对特定的函数(仅签名),并且必须通过指针调用该函数(比直接调用的开销更大)。无论RICI和Deduplicator的答案,由专业的仿避免所有这些成本。
可以使用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::function
定义等?
std::function
。与此解决方案不同,可以内联Lambda或自定义类(如接受的答案)。但是,如果您想隔离专用模块中的所有实现,则此方法具有优势。
deleted_unique_ptr<Foo> foo(new Foo(), customdeleter);
如果customdeleter
遵循约定(返回空值并接受原始指针作为参数),则可能会很简单。
您只需要创建一个删除器类:
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
。
struct BarDeleter
)至std::unique_ptr
(std::unique_ptr<Bar, BarDeleter>
),该类允许std::unique_ptr
构造函数自行创建Deleter实例。即允许以下代码std::unique_ptr<Bar, BarDeleter> bar[10];
typedef std::unique_ptr<Bar, BarDeleter> UniqueBarPtr
unique_ptr
,构造时无需提供删除程序的实例),并增加了可以在std::unique_ptr<Bar>
任何地方使用而无需记住的好处使用特殊typedef
或显式提供程序的第二个模板参数。(要明确地说,这是一个很好的解决方案,我投票支持,但比无缝解决方案少了一步)
除非您需要能够在运行时更改删除程序,否则我强烈建议您使用自定义删除程序类型。例如,如果将函数指针用于删除器,则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()};
deleter_from_fn
。
您知道,使用自定义删除器并不是最好的方法,因为您必须在代码中全部提及它。
相反,由于::std
只要涉及到自定义类型并且您尊重语义,就可以向名称空间级别的类添加特殊化,因此请执行以下操作:
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 };
}
std
打开了一个全新的蠕虫罐头。还要注意,在std::make_unique
C ++ 20之后不允许进行特殊化(因此不应在此之前完成),因为C ++ 20禁止对std
不是类模板(std::make_unique
是函数模板)的事物进行特殊化。请注意,如果传递给的指针std::unique_ptr<Bar>
不是从分配的create()
,而是从其他分配函数分配的,则最终也可能以UB结尾。
std::default_delete
可以满足原始模板的要求。我想这std::default_delete<Foo>()(p)
将是一种有效的书写方式delete p;
,因此,如果delete p;
有效的书写方式(例如,如果Foo
完成的话),将不会是相同的行为。此外,如果delete p;
无效(Foo
不完整),则将为指定新的行为std::default_delete<Foo>
,而不是保持相同的行为。
make_unique
专业化是有问题的,但我肯定用的std::default_delete
过载(不与模板enable_if
,只为C的结构像OpenSSL的BIGNUM
使用已知的破坏作用,在子类是不会发生的),它是迄今为止最简单的方法,如您的其余代码可以直接使用,unique_ptr<special_type>
而无需将函子类型作为模板进行Deleter
遍历,也不必使用typedef
/ using
为该类型命名,以免发生此问题。
std::unique_ptr<Bar, decltype(&destroy)> ptr_;