【问题标题】:How can I pass a function pointer as a template value argument without creating a function object in C++?如何在不使用 C++ 创建函数对象的情况下将函数指针作为模板值参数传递?
【发布时间】:2019-07-26 13:49:08
【问题描述】:

我在这里看到了这个问题的许多变体,但我仍然觉得我的具体情况有所不同。

我的目标是封装一个如下所示的 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 类感觉很脏。我想这样的事情应该可以工作(有或没有std::function

template<typename Obj, std::function<void(Obj*)> Deleter>
class handle {
  // ...
}

但我找不到让编译器满意的方法。据我了解,这有点类似于接受删除器类型对象的std::unique_ptr,而std::shared_ptr 接受删除器函数指针并将其存储在共享对象中。我不介意显式存储指针(并使用额外的内存),但同时,考虑到我将创建很多这些类型,我想有一些方法让它在语法上很好。我真的不想将删除器指针传递给正在创建的对象的每个实例,这就是我试图将它隐藏在模板中的原因。

【问题讨论】:

  • 我最近遇到了这个问题,我的结论是“不”。我很确定您的工作解决方案是 the 使用的解决方案,unique_ptr 也有理由使用它。除了它还要求删除器的实例,而不是创建一个默认实例。
  • @akub Arnold 你能将类缓冲区声明为模板类吗?
  • 删除器功能可以变化吗?如果没有,您可以只创建不带模板参数的 handle 类并在其析构函数中调用 TF_DeleteBuffer
  • @VladfromMoscow 是的,我可以将缓冲区定义为任何东西,从那时起我就可以将专业化存储在一个合理的名称下
  • @MikeLischke "Obj" 和 "Deleter" 有许多不同的组合,所以这就是为什么 handle 是通用的......我基本上想用 RAII 包装一大堆 C API

标签: c++ templates function-pointers


【解决方案1】:

您可以将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> {
  ...
};

【讨论】:

  • 谢谢,这正是我想要的!似乎不需要 &amp;TF_DeleteBuffer 并且只需传递 TF_DeleteBuffer 就可以了(我猜函数会被转换为函数指针?)......无论如何,我想我在尝试这个时不知道函数语法是否正确呵呵
  • @JakubArnold 是的,对于非成员函数和静态成员函数,&amp; 是可选的,因为函数到指针的衰减。
  • 我认为模板别名 (using buffer = handle&lt;TF_Buffer, &amp;TF_DeleteBuffer&gt;) 在这里使用起来会更简单、更简洁,当然如果可以的话。
  • @JakubArnold 注意:这是一个演示 sn-p 不适合实际程序;你应该修改它以尊重 0/3/5 的规则;)
  • @Demolishun 作为模板参数,编号。
【解决方案2】:

我会重复使用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 :)

【讨论】:

    【解决方案3】:

    添加到已接受的答案:
    根据您的用例,使用基于type trait 的方法(类似于std::allocator)将是一种更简洁的解决方案。
    (特别是如果您有许多不同的句柄类型需要用handle&lt;&gt; 包装)

    例子:

    // 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

    【讨论】:

      【解决方案4】:

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

      它唯一关心的是,当涉及到使用真实类型时,它们必须满足类内部执行的操作。在 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; 
             });
      

      【讨论】:

      • 注意:要定义模板参数,关键字classtypename 是精确的同义词。也许选择一个并坚持下去。
      • @YSC 是正确的。这就是为什么没有理由太在意它
      • (开玩笑)你熟悉我最喜欢的源代码质量指标:wtf/line吗?
      • @YSC 每行一个 wtf 可以,每行 2 到 3 个,我们可能有问题?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-10
      • 2022-01-20
      相关资源
      最近更新 更多