我正在尝试编写符合RAII的资源包装器,而我却陷入了如何形成模板参数语义的问题。
例如,我可以编写一个删除资源的函数:
void int_cleaner(int val) {
std::cout << "Value of " << val << " has been cleaned up." << std::endl;
}
或者我可以把它写成一个Functor:
struct int_deleter {
void operator()(int val) const {
std::cout << "Value of " << val << " has been cleaned up." << std::endl;
}
};
但是我遇到了问题:如果我想将其传递给我的资源包装器,我必须更改模板参数的定义方式。
如果我这样写resource
:
template<typename T, typename Deleter>
class resource {
};
这适用于仿函数,但不适用于函数本身。
int main() {
resource<int, int_deleter> res; //Compiles fine
//resource<int, int_cleaner> res2; //Does Not Compile
return 0;
}
相反,如果我写这样的模板参数:
template<typename T>
using deleter_t = void(*)(T);
template<typename T, deleter_t<T> Deleter>
class resource {
};
int main() {
//resource<int, int_deleter> res; //Does Not Compile
resource<int, int_cleaner> res2; //Compiles fine
return 0;
}
现在,我可以编写两个版本的代码,但有两个原因我不想这样做:
resource
的定义,如果我需要更改一个,我还需要对另一个进行更改。void cleaner(T const&)
这样的版本,因为它不会绑定到void(*)(T)
。因此,我还需要制作两到三个版本,以便处理T
,T&
,T const&
和T&&
。如何以最小化代码重复的方式编写资源包装器,特别是考虑到仿函数版本和函数指针版本之间的删除机制会有所不同?
//example:
template<typename T>
using deleter_t = void(*)(T);
template<typename T, deleter_t<T> Deleter>
class resource {
~resource() {Deleter(val);}
};
template<typename T, typename Deleter>
class resource {
~resource() {Deleter{}(val);}//Note the subtle syntax change
};
答案 0 :(得分:4)
您的模板参数是 types 。要从“值”获取类型,您可以使用decltype
。比如说。
resource<int, decltype(int_cleaner)> res2;
您仍然需要将实际函数作为参数传递给某个函数(如构造函数),因为您无法从函数类型创建实例。
我建议您查看执行相同操作的标准类,例如: std::unique_ptr
。也许你应该使用而不是创建自己的类?或许std::shared_ptr
?
答案 1 :(得分:3)
待办事项
template<typename T, typename Deleter>
class resource {
};
然后写
template<auto k>
using constant_t = std::integral_constant<std::decay_t<decltype(k)>, k>;
template<auto k>
constexpr constant_t<k> constant{};
现在你的主要看起来像这样:
int main() {
resource<int, int_deleter> res; //Compiles fine
resource<int, constant_t<int_cleaner>> res2; //Also compiles fine
return 0;
}
我们已经完成了。
这是c++17。
在c++14中,您必须将constant_t<foo>
替换为std::integral_constant<std::decay_t<decltype(foo)>, foo>
,因为它缺少auto
模板参数。
在c++11 integral_constant
中无法使用函数指针,让您调用它们。你必须写一个派生类型:
namespace notstd {
template<class T, T t>
struct integral_constant:std::integral_constant<T, t> {
constexpr operator T()const{ return this->get(); }
}
}
并将std::integral_constant
替换为notstd::integral_constant
以启用该功能。 (对函数指针的隐式转换足以允许在整数常量上使用调用运算符。)
在c++03中,您需要获得一个新的编译器。
c++17中的另一种方法是使用所有值而不是所有类型。
resource foo(7, int_deleter{});
resource foo2(7, int_cleaner);
并教导资源保持值到他们的删除者。这会导致int_cleaner
占用存储空间并在资源中具有值。
resource foo(7, int_deleter{});
resource foo2(7, constant<int_cleaner>);
回到最初的计划,我们通过将其提升到类型系统中来制作无状态int_cleaner
指针。
通过使用EBO,resource
可以存储零开销的无状态删除器。
请注意,resource
看起来像<{1}}一样很多,其中unique_ptr<T, Deleter>
是一个包裹得很少的Deleter::pointer
(对于可为空性)。< / p>
std::optional<T>
或某些。