用C++lambda包装C回调,可以使用模板多态性
wrapping C callbacks with C++ lambdas, possible to use template polymorphism?
好的,我最近发布了一些问题,这些问题与用C++11-ish接口包装C回调API有关。我几乎得到了一个令人满意的解决方案,但我认为它可能更优雅,需要一些模板元编程向导的帮助:)
请耐心等待,因为示例代码有点长,但我已经尝试过一次性演示这个问题。基本上,我的想法是,给定一个函数指针和数据上下文指针的列表,我想提供一个可以提供的回调机制,
- 函数指针
- 函数对象(函子)
- Lambdas
此外,我想让这些函数可以被各种原型调用。我的意思是,C API为回调提供了大约7个不同的参数,但在大多数情况下,用户代码实际上只对其中的一个或两个感兴趣。因此,我希望用户能够只指定他感兴趣的参数
在本例中,标称C回调采用int
和float
参数,以及可用于返回一些额外数据的可选float*
。因此,C++代码的目的是能够以任何"可调用"的形式提供这些原型中任何一个的回调。(例如函子、lambda等)
int callback2args(int a, float b);
int callback3args(int a, float b, float *c);
这是我迄今为止的解决方案。
#include <cstdio>
#include <vector>
#include <functional>
typedef int call2args(int,float);
typedef int call3args(int,float,float*);
typedef std::function<call2args> fcall2args;
typedef std::function<call3args> fcall3args;
typedef int callback(int,float,float*,void*);
typedef std::pair<callback*,void*> cb;
std::vector<cb> callbacks;
template <typename H>
static
int call(int a, float b, float *c, void *user);
template <>
int call<call2args>(int a, float b, float *c, void *user)
{
call2args *h = (call2args*)user;
return (*h)(a, b);
}
template <>
int call<call3args>(int a, float b, float *c, void *user)
{
call3args *h = (call3args*)user;
return (*h)(a, b, c);
}
template <>
int call<fcall2args>(int a, float b, float *c, void *user)
{
fcall2args *h = (fcall2args*)user;
return (*h)(a, b);
}
template <>
int call<fcall3args>(int a, float b, float *c, void *user)
{
fcall3args *h = (fcall3args*)user;
return (*h)(a, b, c);
}
template<typename H>
void add_callback(const H &h)
{
H *j = new H(h);
callbacks.push_back(cb(call<H>, (void*)j));
}
template<>
void add_callback<call2args>(const call2args &h)
{
callbacks.push_back(cb(call<call2args>, (void*)h));
}
template<>
void add_callback<call3args>(const call3args &h)
{
callbacks.push_back(cb(call<call3args>, (void*)h));
}
template<>
void add_callback<fcall2args>(const fcall2args &h)
{
fcall2args *j = new fcall2args(h);
callbacks.push_back(cb(call<fcall2args>, (void*)j));
}
template<>
void add_callback<fcall3args>(const fcall3args &h)
{
fcall3args *j = new fcall3args(h);
callbacks.push_back(cb(call<fcall3args>, (void*)j));
}
// Regular C-style callback functions (context-free)
int test1(int a, float b)
{
printf("test1 -- a: %d, b: %f", a, b);
return a*b;
}
int test2(int a, float b, float *c)
{
printf("test2 -- a: %d, b: %f", a, b);
*c = a*b;
return a*b;
}
void init()
{
// A functor class
class test3
{
public:
test3(int j) : _j(j) {};
int operator () (int a, float b)
{
printf("test3 -- a: %d, b: %f", a, b);
return a*b*_j;
}
private:
int _j;
};
// Regular function pointer of 2 parameters
add_callback(test1);
// Regular function pointer of 3 parameters
add_callback(test2);
// Some lambda context!
int j = 5;
// Wrap a 2-parameter functor in std::function
add_callback(fcall2args(test3(j)));
// Wrap a 2-parameter lambda in std::function
add_callback(fcall2args([j](int a, float b)
{
printf("test4 -- a: %d, b: %f", a, b);
return a*b*j;
}));
// Wrap a 3-parameter lambda in std::function
add_callback(fcall3args([j](int a, float b, float *c)
{
printf("test5 -- a: %d, b: %f", a, b);
*c = a*b*j;
return a*b*j;
}));
}
int main()
{
init();
auto c = callbacks.begin();
while (c!=callbacks.end()) {
float d=0;
int r = c->first(2,3,&d,c->second);
printf(" result: %d (%f)n", r, d);
c ++;
}
}
好吧,正如你所看到的,这确实有效。然而,我发现必须将函子/lambdas显式包装为std::function
类型的解决方案有点不雅。我真的很想让编译器自动匹配函数类型,但这似乎不起作用。如果我删除了3参数变体,那么fcall2args
包装器是不需要的,但是add_callback
的fcall3args
版本的存在使它对编译器来说显然是不明确的。换句话说,它似乎无法基于lambda调用签名进行模式匹配。
第二个问题是,我当然使用new
来复制函子/lambda对象,但不使用delete
来复制这个内存。我现在不确定跟踪这些分配的最佳方式是什么,尽管我想在实际实现中,我可以在add_callback
是其成员的对象中跟踪它们,并在析构函数中释放它们。
第三,对于我想要允许的回调的每一种变体,都有特定的类型call2args
、call3args
等,我觉得这不是很优雅。这意味着,对于用户可能需要的每一个参数组合,我都需要一个类型爆炸。我希望可以有一些模板解决方案,使其更通用,但我有困难想出它。
编辑以进行解释:此代码中的定义std::vector<std::pair<callback*,void*>> callbacks
是问题定义的一部分,而不是答案的一部分。我试图解决的问题是将C++对象映射到这个接口上——因此,提出更好的方法来组织这个std::vector
并不能解决我的问题。谢谢。只是澄清一下。
编辑#2:好吧,忘记我的示例代码使用std::vector<std::pair<callback*,void*>> callbacks
来保存回调这一事实。相反,想象一下,由于这是实际场景,我有一些C库实现以下接口:
struct someobject *create_object();
free_object(struct someobject *obj);
add_object_callback(struct someobject *obj, callback *c, void *context);
其中callback
是
typedef int callback(int a,float b,float *c, void *context);
好的。因此,"someobject"将经历某种外部事件、网络数据或输入事件等,并在这些事件发生时调用其回调列表。
这是C中回调的一个非常标准的实现。重要的是,这是一个现有的库,对此我无法更改,但我正在尝试围绕它编写一个好的、惯用的C++包装器。我希望我的C++用户能够添加lambda作为回调。因此,我想设计一个C++接口,让用户能够做以下事情:
add_object_callback(struct someobject *obj, func);
其中func
是以下之一:
- 一个不使用CCD_ 18的正则C函数
- 函子对象
- λ
此外,在每种情况下,函数/函子/lambda都应该有可能具有以下签名之一:
int cb2args(int a, float b);
int cb2args(int a, float b, float *c);
我认为这应该是可能的,我已经完成了大约80%的工作,但我仍然停留在基于调用签名的模板多态性上。我不知道这是否可能。也许它需要一些涉及function_traits
之类的巫毒,但这有点超出了我的经验。在任何情况下,都有很多C库使用这样的接口,我认为在C++中使用它们时,如果能提供这种方便,那就太好了。
由于您在C++11中使用C API,因此您还可以将整个过程打包到C++类中。正如您在第二个问题中提到的,这对于解决资源泄漏也是必要的。
还要记住,没有捕获的lambda表达式可以隐式转换为函数指针。这可以移除所有call<*>
,因为它们可以移动到add_callback
s中。
最后,我们可以使用SFINAE来删除fcall3args
类型。结果如下。
class SomeObject {
// The real object being wrapped.
struct someobject* m_self;
// The vector of callbacks which requires destruction. This vector is only a
// memory store, and serves no purpose otherwise.
typedef std::function<int(int, float, float*)> Callback;
std::vector<std::unique_ptr<Callback>> m_functions;
// Add a callback to the object. Note the capture-less lambda.
template <typename H>
void add_callback_impl(H&& h) {
std::unique_ptr<Callback> callback (new Callback(std::forward<H>(h)));
add_object_callback(m_self, [](int a, float b, float* c, void* raw_ctx) {
return (*static_cast<Callback*>(raw_ctx))(a, b, c);
}, callback.get());
m_functions.push_back(std::move(callback));
}
public:
SomeObject() : m_self(create_object()) {}
~SomeObject() { free_object(m_self); }
// We create 4 public overloads to add_callback:
// This only accepts function objects having 2 arguments.
template <typename H>
auto add_callback(H&& h) -> decltype(h(1, 10.f), void()) {
using namespace std::placeholders;
add_callback_impl(std::bind(std::forward<H>(h), _1, _2));
}
// This only accepts function objects having 3 arguments.
template <typename H>
auto add_callback(H&& h) -> decltype(h(1, 1.0f, (float*)0), void()) {
add_callback_impl(std::forward<H>(h));
}
// This only accepts function pointers.
void add_callback(int(*h)(int, float)) const {
add_object_callback(m_self, [](int a, float b, float* c, void* d) {
return reinterpret_cast<int(*)(int, float)>(d)(a, b);
}, reinterpret_cast<void*>(h));
}
// This only accepts function pointers.
void add_callback(int(*h)(int, float, float*)) const {
add_object_callback(m_self, [](int a, float b, float* c, void* d) {
return reinterpret_cast<int(*)(int, float, float*)>(d)(a, b, c);
}, reinterpret_cast<void*>(h));
}
// Note that the last 2 overloads violates the C++ standard by assuming
// sizeof(void*) == sizeof(func pointer). This is valid in POSIX, though.
struct someobject* get_raw_object() const {
return m_self;
}
};
因此init()
变为:
void init(SomeObject& so) {
// A functor class
class test3 { ... };
so.add_callback(test1);
so.add_callback(test2);
// Some lambda context!
int j = 5;
so.add_callback(test3(j));
so.add_callback([j](int a, float b) -> int {
printf("test4 -- a: %d, b: %f", a, b);
return a*b*j;
});
so.add_callback([j](int a, float b, float *c) -> int {
printf("test5 -- a: %d, b: %f", a, b);
*c = a*b*j;
return a*b*j;
});
}
完整的测试代码(我这里不把它放在ideone上,因为g++4.5不支持将lambda隐式转换为函数指针,也不支持基于范围的。)
#include <vector>
#include <functional>
#include <cstdio>
#include <memory>
struct someobject;
struct someobject* create_object(void);
void free_object(struct someobject* obj);
void add_object_callback(struct someobject* obj,
int(*callback)(int, float, float*, void*),
void* context);
class SomeObject {
// The real object being wrapped.
struct someobject* m_self;
// The vector of callbacks which requires destruction. This vector is only a
// memory store, and serves no purpose otherwise.
typedef std::function<int(int, float, float*)> Callback;
std::vector<std::unique_ptr<Callback>> m_functions;
// Add a callback to the object. Note the capture-less lambda.
template <typename H>
void add_callback_impl(H&& h) {
std::unique_ptr<Callback> callback (new Callback(std::forward<H>(h)));
add_object_callback(m_self, [](int a, float b, float* c, void* raw_ctx) {
return (*static_cast<Callback*>(raw_ctx))(a, b, c);
}, callback.get());
m_functions.push_back(std::move(callback));
}
public:
SomeObject() : m_self(create_object()) {}
~SomeObject() { free_object(m_self); }
// We create 4 public overloads to add_callback:
// This only accepts function objects having 2 arguments.
template <typename H>
auto add_callback(H&& h) -> decltype(h(1, 10.f), void()) {
using namespace std::placeholders;
add_callback_impl(std::bind(std::forward<H>(h), _1, _2));
}
// This only accepts function objects having 3 arguments.
template <typename H>
auto add_callback(H&& h) -> decltype(h(1, 1.0f, (float*)0), void()) {
add_callback_impl(std::forward<H>(h));
}
// This only accepts function pointers.
void add_callback(int(*h)(int, float)) const {
add_object_callback(m_self, [](int a, float b, float* c, void* d) {
return reinterpret_cast<int(*)(int, float)>(d)(a, b);
}, reinterpret_cast<void*>(h));
}
// This only accepts function pointers.
void add_callback(int(*h)(int, float, float*)) const {
add_object_callback(m_self, [](int a, float b, float* c, void* d) {
return reinterpret_cast<int(*)(int, float, float*)>(d)(a, b, c);
}, reinterpret_cast<void*>(h));
}
// Note that the last 2 overloads violates the C++ standard by assuming
// sizeof(void*) == sizeof(func pointer). This is required in POSIX, though.
struct someobject* get_raw_object() const {
return m_self;
}
};
//------------------------------------------------------------------------------
int test1(int a, float b) {
printf("test1 -- a: %d, b: %f", a, b);
return a*b;
}
int test2(int a, float b, float *c) {
printf("test2 -- a: %d, b: %f", a, b);
*c = a*b;
return a*b;
}
void init(SomeObject& so) {
// A functor class
class test3
{
public:
test3(int j) : _j(j) {};
int operator () (int a, float b)
{
printf("test3 -- a: %d, b: %f", a, b);
return a*b*_j;
}
private:
int _j;
};
so.add_callback(test1);
so.add_callback(test2);
// Some lambda context!
int j = 5;
so.add_callback(test3(j));
so.add_callback([j](int a, float b) -> int {
printf("test4 -- a: %d, b: %f", a, b);
return a*b*j;
});
so.add_callback([j](int a, float b, float *c) -> int {
printf("test5 -- a: %d, b: %f", a, b);
*c = a*b*j;
return a*b*j;
});
}
//------------------------------------------------------------------------------
struct someobject {
std::vector<std::pair<int(*)(int,float,float*,void*),void*>> m_callbacks;
void call() const {
for (auto&& cb : m_callbacks) {
float d=0;
int r = cb.first(2, 3, &d, cb.second);
printf(" result: %d (%f)n", r, d);
}
}
};
struct someobject* create_object(void) {
return new someobject;
}
void free_object(struct someobject* obj) {
delete obj;
}
void add_object_callback(struct someobject* obj,
int(*callback)(int, float, float*, void*),
void* context) {
obj->m_callbacks.emplace_back(callback, context);
}
//------------------------------------------------------------------------------
int main() {
SomeObject so;
init(so);
so.get_raw_object()->call();
}
- 多态性和功能结合
- 找不到成员对象:没有名为get_event()的成员,也处理多态性和向量
- 使用取消引用的指针的多态性会产生意外的结果.为什么?
- C++boost序列化多态性问题
- 如何查找哪个类对象位于数组的特定索引上(多态性)
- 如何在多线程中正确使用unique_ptr进行多态性?
- 具有智能指针的多态性
- 在 C++ 中在堆栈上创建实例时如何保持多态性?
- 如何构建模板,使其具有多态性?
- CRTP 静态多态性:是否可以用模拟替换基类
- 是否可以将多态性类存储在共享内存中
- 用C++lambda包装C回调,可以使用模板多态性
- 在继承层次结构中将方法定义为虚拟方法一次,以使多态性发挥作用
- 可以使用不同的实现文件来实现多态性吗
- 我可以使用多态性将不同的对象存储在具有C++的数组中吗?
- 是否可以为模板化的基类提供专门的类层次结构,但仍利用它们之间的多态性
- 使多态性在C++映射中工作,而不会发生内存泄漏
- 我们可以通过const函数实现多态性吗
- 多态性和默认值:可以共存吗?
- 根据"foreign"子类型采取特定操作,不带开关、强制转换等。我们可以以某种方式使用多态性吗?