简体   繁体   English

如何在不在C ++中创建函数对象的情况下将函数指针作为模板值参数传递?

[英]How can I pass a function pointer as a template value argument without creating a function object in C++?

I've seen many variants of this question asked here, but I still feel my specific case is different. 我在这里看到过这个问题的很多变种,但我仍然觉得我的具体情况有所不同。

My goal is wrapping a C API that looks like this: 我的目标是包装一个如下所示的C API:

TF_Buffer* buf = TF_AllocateBuffer();
// ...
TF_DeleteBuffer(buf);

Since I have many of these objects, I'd love to create a generic type named handle that could hold a given pointer and call the appropriate deallocator upon destruction. 由于我有很多这些对象,我很乐意创建一个名为handle的泛型类型,它可以保存给定的指针并在销毁时调用相应的deallocator。 My imagined use case would be 我想象的用例就是

class buffer : public handle<TF_Buffer, TF_DeleteBuffer> {
public:
  buffer(TF_Buffer* b): handle(b) {}
}

unfortunately I'm unable to get this to work since TF_DeleteBuffer is a simple function (of type void TF_DeleteBuffer(TF_Buffer*) ). 不幸的是,我无法让这个工作,因为TF_DeleteBuffer是一个简单的函数(类型为void TF_DeleteBuffer(TF_Buffer*) )。 I did manage to work around the issue with creating a function object for the function, so the following does work 我确实设法通过为函数创建函数对象来解决这个问题,因此以下工作正常

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

but it feels dirty having to define the buffer_deleter class just for this purpose. 但是为了这个目的而必须定义buffer_deleter类感觉很脏。 I'd imagine something like this ought to work (with or without the std::function ) 我想像这样的东西应该有效(有或没有std::function

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

but I can't find a way to make the compiler happy. 但我找不到让编译器开心的方法。 From what I understand, this is somewhat similar to std::unique_ptr which accepts a deleter type object, vs std::shared_ptr which accepts a deleter function pointer and stores it in the shared object. 据我所知,这有点类似于接受删除器类型对象的std::unique_ptr ,而vsd std::shared_ptr接受删除函数指针并将其存储在共享对象中。 I don't mind storing the pointer explicitly (and using extra memory), but at the same time, given I'll be creating lots of these types, I'd like to have some way of making it syntactically nice. 我不介意显式地存储指针(并使用额外的内存),但同时,鉴于我将创建许多这些类型,我想有一些方法使它在语法上很好。 I really do not want to pass the deleter pointer to each instance of the object being created, which is why I'm trying to hide it in the template. 我真的不想将删除指针传递给正在创建的对象的每个实例,这就是为什么我试图将它隐藏在模板中。

You can define a non-type template parameter as function pointer. 您可以将非类型模板参数定义为函数指针。

template<typename Obj, void(*Deleter)(Obj*)>
class handle {
public:
  Obj* obj;

  handle(Obj* o): obj(o) {};
  ~handle() { if (obj) Deleter(obj); }
};

And use it like 并使用它

class buffer : public handle<TF_Buffer, &TF_DeleteBuffer> {
  ...
};

I'd reuse std::shared_ptr . 我重用了std::shared_ptr It just works in all cases and have been thoroughly tested: 它适用于所有情况并经过全面测试:

template<class Buffer, class Destructor>
auto make_handle(Buffer buffer, Destructor dstr)
{ return std::shared_ptr<std::remove_pointer_t<Buffer>>(buffer, dstr); }

Usage: 用法:

auto h = make_handle(TF_AllocateBuffer(), TF_DeleteBuffer);

Full demo: https://coliru.stacked-crooked.com/a/b12e4adc559cbfd7 完整演示: https//coliru.stacked-crooked.com/a/b12e4adc559cbfd7


As a bonus, you can now copy the handle and it does The Right Thing: 作为奖励,你现在可以复制句柄,它做了正确的事情:

{
    auto h2 = h;
} // does not free h's buffer until h is out of scope :)

To add to the accepted answer: 要添加到接受的答案:
Depending on your use-case, using a type trait -based approach (similar to std::allocator ) would be a cleaner solution. 根据您的使用情况,使用基于type trait的方法(类似于std::allocator )将是一个更清洁的解决方案。
(Especially if you have many different handle types that you need to wrap with handle<> ) (特别是如果你需要用handle<>包装许多不同的句柄类型)

Example: 例:

// 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 Godbolt的例子

C++ does not care much about types of functions. C ++并不关心函数的类型。 For instance, check this: 例如,检查一下:

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

The only thing it cares, when it comes to using the real types, they must satisfy the operation performed inside the class. 它唯一关心的是,当涉及到使用真实类型时,它们必须满足在类内执行的操作。 It is perfectly OK to do in Visual C++ something like this: 在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;
}

As lambda: 作为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; 
       });

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM