我目前正在使用一个 C 库,该库定义了许多数据类型,所有这些类型都需要由用户管理它们的生命周期。有许多函数以这种方式定义:
int* create() {
return new int();
}
void destroy(int* i) {
delete i;
}
其中大部分在创建后不需要访问。他们只需要存在。因此,我尝试使用在我需要它们存在的范围内声明的 unique_ptr
来管理它们。
这样的声明是这样的:
// Note that I'm avoiding writing the type's name manually.
auto a = std::unique_ptr<std::decay_t<decltype(*create())>, decltype(&destroy)>{create(), &destroy};
但这太冗长了,所以我将它封装在一个实用函数模板中:
template<typename T>
auto make_unique_ptr(T* p, void (*f)(T*)) {
return std::unique_ptr<T, decltype(f)>(p, f);
}
这样使用的:
auto b = make_unique_ptr(create(), &destroy);
这看起来不错,但是引入了一个非标准函数,它除了作为某些声明的语法糖之外没有任何实际用途。我的同事可能甚至不知道它的存在,并最终创建了具有不同名称的其他版本。
c++17介绍class template argument deduction .我认为这是解决我的问题的完美方法:一种无需借助用户定义的包装器即可推导出所有这些类型的标准方法。所以我尝试了这个:
auto c = std::unique_ptr{create(), &destroy};
按照 C++ 编译器和模板的规则,这会失败并显示几行长错误消息。以下是相关部分:
(...): error: class template argument deduction failed:
auto c = std::unique_ptr{create(), &destroy};
^
(...): note: candidate: 'template<class _Tp, class _Dp>
unique_ptr(std::unique_ptr<_Tp, _Dp>::pointer, typename std::remove_reference<_Dp>::type&&)-> std::unique_ptr<_Tp, _Dp>'
unique_ptr(pointer __p,
^~~~~~~~~~
(...): note: template argument deduction/substitution failed:
(...): note: couldn't deduce template parameter '_Tp'
auto c = std::unique_ptr{create(), &destroy};
^
理论上,我可以添加一个演绎指南来处理这个问题:
namespace std {
template<typename T>
unique_ptr(T* p, void (*f)(T*)) -> unique_ptr<T, decltype(f)>;
}
它至少在我的 gcc 版本上工作,但标准不太喜欢它:
[namespace.std]
1 Unless otherwise specified, the behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std.
4 The behavior of a C++ program is undefined if it declares
(...)
4.4 - a deduction guide for any standard library class template.
还有一些与区分指针和数组有关的问题,但让我们忽略它。
最后,问题:是否有任何其他方法可以“帮助”std::unique_ptr
(或者可能是std::make_unique
) 在使用自定义删除器时推断出正确的类型?以防万一这是一个 XY 问题,是否有任何我没有想到的解决方案来管理这些类型(可能是 std::shared_ptr
)?如果这两个答案是否定的,c++20 是否有任何改进?我应该期待,那解决这个问题?
最佳答案
我建议编写自定义删除器而不是使用函数指针。使用函数指针会使所有 unique_ptr
的大小无缘无故地翻倍。
相反,编写一个基于函数指针的删除器模板:
template <auto deleter_f>
struct Deleter {
template <typename T>
void operator()(T* ptr) const
{
deleter_f(ptr);
}
};
或者,作为 Yakk - Adam Nevraumont mentioned in the comments :
template <auto deleter_f>
using Deleter = std::integral_constant<std::decay_t<decltype(deleter_f)>, deleter_f>;
使用它变得非常干净:
auto a = std::unique_ptr<int, Deleter<destroy>>{create()};
尽管您可能希望将其与您的 make_unique_ptr
函数结合使用:
template <auto deleter_f, typename T>
auto create_unique_ptr(T* ptr)
{
return std::unique_ptr<T, Deleter<deleter_f>>{ptr};
}
// Usage:
auto a = create_unique_ptr<destroy>(create());
关于c++ - 通过 unique_ptr 和自定义删除器使用自动扣除,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51252087/