如何在不在 C++ 中创建函数对象的情况下将函数指针作为模板值参数传递?
How can I pass a function pointer as a template value argument without creating a function object in C++?
我已经看到这里提出的这个问题的许多变体,但我仍然觉得我的具体情况有所不同。
我的目标是包装一个如下所示的 C API:
TF_Buffer* buf = TF_AllocateBuffer();
// ...
TF_DeleteBuffer(buf);
因为我有很多这样的对象,所以我想创建一个名为 handle
的通用类型,它可以保存给定的指针并在销毁时调用适当的释放器。我想象的用例是
class buffer : public handle<TF_Buffer, TF_DeleteBuffer> {
public:
buffer(TF_Buffer* b): handle(b) {}
}
不幸的是,我无法让它工作,因为 TF_DeleteBuffer
是一个简单的函数(void TF_DeleteBuffer(TF_Buffer*)
类型)。我确实设法通过为函数创建一个函数对象来解决这个问题,所以下面的方法确实有效
template<typename Obj, typename Deleter>
class handle {
public:
Obj* obj;
handle(Obj* o): obj(o) {};
~handle() { if (obj) Deleter()(obj); }
};
struct buffer_deleter {
void operator()(TF_Buffer* b) { TF_DeleteBuffer(b); }
};
class buffer : public handle<TF_Buffer, buffer_deleter> {
public:
buffer(TF_Buffer* b): handle(b) {}
}
但是为了这个目的不得不定义 buffer_deleter
class 感觉很脏。我想像这样的东西应该可以工作(有或没有 std::function
)
template<typename Obj, std::function<void(Obj*)> Deleter>
class handle {
// ...
}
但我找不到让编译器满意的方法。据我了解,这有点类似于 std::unique_ptr
接受删除器类型对象,而 std::shared_ptr
接受删除器函数指针并将其存储在共享对象中。我不介意显式存储指针(并使用额外的内存),但与此同时,考虑到我将创建许多此类类型,我希望有一些方法可以使它在语法上更好。我真的不想将删除器指针传递给正在创建的对象的每个实例,这就是我试图将其隐藏在模板中的原因。
你可以定义一个non-type template parameter作为函数指针。
template<typename Obj, void(*Deleter)(Obj*)>
class handle {
public:
Obj* obj;
handle(Obj* o): obj(o) {};
~handle() { if (obj) Deleter(obj); }
};
并像
一样使用它
class buffer : public handle<TF_Buffer, &TF_DeleteBuffer> {
...
};
我会重用 std::shared_ptr
。它适用于所有情况并且已经过全面测试:
template<class Buffer, class Destructor>
auto make_handle(Buffer buffer, Destructor dstr)
{ return std::shared_ptr<std::remove_pointer_t<Buffer>>(buffer, dstr); }
用法:
auto h = make_handle(TF_AllocateBuffer(), TF_DeleteBuffer);
完整演示:https://coliru.stacked-crooked.com/a/b12e4adc559cbfd7
作为奖励,您现在可以复制 句柄,它会做正确的事:
{
auto h2 = h;
} // does not free h's buffer until h is out of scope :)
要添加到已接受的答案中:
根据您的用例,使用基于 type trait
的方法(类似于 std::allocator
)将是更简洁的解决方案。
(特别是如果您有许多不同的句柄类型需要用 handle<>
包装)
示例:
// Boilerplate.
// Assume TF_Buffer and Foobar_Buffer would be handle types
struct TF_Buffer {};
struct Foobar_Buffer {};
// TF_Buffer functions
TF_Buffer* TF_AllocateBuffer() { return new TF_Buffer(); };
void TF_DeleteBuffer(TF_Buffer* buf) { delete buf; }
// Foobar_Buffer functions
Foobar_Buffer* Foobar_AllocateBuffer() { return new Foobar_Buffer(); };
void Foobar_DeleteBuffer(Foobar_Buffer* buf) { delete buf; }
// Generic handle_allocator for all handles that are not specified.
// if you don't have a generic way of allocating handles simply leave them out,
// which will lead to a compile-time error when you use handle<> with a non-specialized type.
template<typename handle_type>
struct handle_allocator {
/*
static handle_type* allocate() {
// Generic handle allocate
}
static void deallocate(handle_type* handle) {
// Generic handle delete
}
*/
};
// Traits for TF_Buffer
template<>
struct handle_allocator<TF_Buffer> {
static TF_Buffer* allocate() { return TF_AllocateBuffer(); }
static void deallocate(TF_Buffer* handle) { TF_DeleteBuffer(handle); }
};
// Traits for Foobar_Buffer
template<>
struct handle_allocator<Foobar_Buffer> {
static Foobar_Buffer* allocate() { return Foobar_AllocateBuffer(); }
static void deallocate(Foobar_Buffer* handle) { Foobar_DeleteBuffer(handle); }
};
template<typename Obj, typename allocator = handle_allocator<Obj>>
class handle {
public:
Obj* obj;
// you can also use the traits to default-construct a handle
handle() : obj(allocator::allocate()) {}
handle(Obj* o): obj(o) {};
~handle() { if (obj) allocator::deallocate(obj); }
};
class buffer : public handle<TF_Buffer> {
public:
buffer(TF_Buffer* b): handle(b) {}
};
// This will not work, because the generic handle_allocator
// doesn't have allocate() and deallocate() functions defined
/*
struct NotWorking {};
handle<NotWorking> w;
*/
C++ 不太关心函数的类型。例如,检查这个:
#include<iostream>
using namespace std;
int func(char* str) { cout << str << endl; return strlen(str); }
template<class T> T executor(T f) { return f; }
int main()
{
int x = executor(func)("hello");
return 0;
}
它唯一关心的是,在使用真实类型时,它们必须满足 class 内部执行的操作。在 Visual C++ 中做这样的事情是完全可以的:
#include<iostream>
using namespace std;
void strdtor(char* str)
{
cout << "deleting " << str << endl;
delete[] str;
}
template <class T, typename TDeletor> class memorizer
{
TDeletor& dtor;
T buffer;
public:
memorizer(T buf, TDeletor dt) : buffer(buf), dtor(dt){}
~memorizer(){dtor(buffer);}
};
int main()
{
char* c = new char[10];
sprintf_s(c, 10, "hello");
memorizer<char*, void(char*)> m(c, strdtor);
return 0;
}
作为 lambda:
char* d = new char[10];
sprintf_s(d, 10, "world");
memorizer<char*, void(char*)> m2(
d,
[](char* x) -> void
{
cout << "lambla deleting " << x << endl;
delete[] x;
});
我已经看到这里提出的这个问题的许多变体,但我仍然觉得我的具体情况有所不同。
我的目标是包装一个如下所示的 C API:
TF_Buffer* buf = TF_AllocateBuffer();
// ...
TF_DeleteBuffer(buf);
因为我有很多这样的对象,所以我想创建一个名为 handle
的通用类型,它可以保存给定的指针并在销毁时调用适当的释放器。我想象的用例是
class buffer : public handle<TF_Buffer, TF_DeleteBuffer> {
public:
buffer(TF_Buffer* b): handle(b) {}
}
不幸的是,我无法让它工作,因为 TF_DeleteBuffer
是一个简单的函数(void TF_DeleteBuffer(TF_Buffer*)
类型)。我确实设法通过为函数创建一个函数对象来解决这个问题,所以下面的方法确实有效
template<typename Obj, typename Deleter>
class handle {
public:
Obj* obj;
handle(Obj* o): obj(o) {};
~handle() { if (obj) Deleter()(obj); }
};
struct buffer_deleter {
void operator()(TF_Buffer* b) { TF_DeleteBuffer(b); }
};
class buffer : public handle<TF_Buffer, buffer_deleter> {
public:
buffer(TF_Buffer* b): handle(b) {}
}
但是为了这个目的不得不定义 buffer_deleter
class 感觉很脏。我想像这样的东西应该可以工作(有或没有 std::function
)
template<typename Obj, std::function<void(Obj*)> Deleter>
class handle {
// ...
}
但我找不到让编译器满意的方法。据我了解,这有点类似于 std::unique_ptr
接受删除器类型对象,而 std::shared_ptr
接受删除器函数指针并将其存储在共享对象中。我不介意显式存储指针(并使用额外的内存),但与此同时,考虑到我将创建许多此类类型,我希望有一些方法可以使它在语法上更好。我真的不想将删除器指针传递给正在创建的对象的每个实例,这就是我试图将其隐藏在模板中的原因。
你可以定义一个non-type template parameter作为函数指针。
template<typename Obj, void(*Deleter)(Obj*)>
class handle {
public:
Obj* obj;
handle(Obj* o): obj(o) {};
~handle() { if (obj) Deleter(obj); }
};
并像
一样使用它class buffer : public handle<TF_Buffer, &TF_DeleteBuffer> {
...
};
我会重用 std::shared_ptr
。它适用于所有情况并且已经过全面测试:
template<class Buffer, class Destructor>
auto make_handle(Buffer buffer, Destructor dstr)
{ return std::shared_ptr<std::remove_pointer_t<Buffer>>(buffer, dstr); }
用法:
auto h = make_handle(TF_AllocateBuffer(), TF_DeleteBuffer);
完整演示:https://coliru.stacked-crooked.com/a/b12e4adc559cbfd7
作为奖励,您现在可以复制 句柄,它会做正确的事:
{
auto h2 = h;
} // does not free h's buffer until h is out of scope :)
要添加到已接受的答案中:
根据您的用例,使用基于 type trait
的方法(类似于 std::allocator
)将是更简洁的解决方案。
(特别是如果您有许多不同的句柄类型需要用 handle<>
包装)
示例:
// Boilerplate.
// Assume TF_Buffer and Foobar_Buffer would be handle types
struct TF_Buffer {};
struct Foobar_Buffer {};
// TF_Buffer functions
TF_Buffer* TF_AllocateBuffer() { return new TF_Buffer(); };
void TF_DeleteBuffer(TF_Buffer* buf) { delete buf; }
// Foobar_Buffer functions
Foobar_Buffer* Foobar_AllocateBuffer() { return new Foobar_Buffer(); };
void Foobar_DeleteBuffer(Foobar_Buffer* buf) { delete buf; }
// Generic handle_allocator for all handles that are not specified.
// if you don't have a generic way of allocating handles simply leave them out,
// which will lead to a compile-time error when you use handle<> with a non-specialized type.
template<typename handle_type>
struct handle_allocator {
/*
static handle_type* allocate() {
// Generic handle allocate
}
static void deallocate(handle_type* handle) {
// Generic handle delete
}
*/
};
// Traits for TF_Buffer
template<>
struct handle_allocator<TF_Buffer> {
static TF_Buffer* allocate() { return TF_AllocateBuffer(); }
static void deallocate(TF_Buffer* handle) { TF_DeleteBuffer(handle); }
};
// Traits for Foobar_Buffer
template<>
struct handle_allocator<Foobar_Buffer> {
static Foobar_Buffer* allocate() { return Foobar_AllocateBuffer(); }
static void deallocate(Foobar_Buffer* handle) { Foobar_DeleteBuffer(handle); }
};
template<typename Obj, typename allocator = handle_allocator<Obj>>
class handle {
public:
Obj* obj;
// you can also use the traits to default-construct a handle
handle() : obj(allocator::allocate()) {}
handle(Obj* o): obj(o) {};
~handle() { if (obj) allocator::deallocate(obj); }
};
class buffer : public handle<TF_Buffer> {
public:
buffer(TF_Buffer* b): handle(b) {}
};
// This will not work, because the generic handle_allocator
// doesn't have allocate() and deallocate() functions defined
/*
struct NotWorking {};
handle<NotWorking> w;
*/
C++ 不太关心函数的类型。例如,检查这个:
#include<iostream>
using namespace std;
int func(char* str) { cout << str << endl; return strlen(str); }
template<class T> T executor(T f) { return f; }
int main()
{
int x = executor(func)("hello");
return 0;
}
它唯一关心的是,在使用真实类型时,它们必须满足 class 内部执行的操作。在 Visual C++ 中做这样的事情是完全可以的:
#include<iostream>
using namespace std;
void strdtor(char* str)
{
cout << "deleting " << str << endl;
delete[] str;
}
template <class T, typename TDeletor> class memorizer
{
TDeletor& dtor;
T buffer;
public:
memorizer(T buf, TDeletor dt) : buffer(buf), dtor(dt){}
~memorizer(){dtor(buffer);}
};
int main()
{
char* c = new char[10];
sprintf_s(c, 10, "hello");
memorizer<char*, void(char*)> m(c, strdtor);
return 0;
}
作为 lambda:
char* d = new char[10];
sprintf_s(d, 10, "world");
memorizer<char*, void(char*)> m2(
d,
[](char* x) -> void
{
cout << "lambla deleting " << x << endl;
delete[] x;
});