如何在不在 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;
*/

Example in Godbolt

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; 
       });