以下回调类是“可调用事物”的通用包装器。我真的很喜欢它的API,它没有模板而且很干净,但是引擎盖下有一些我无法避免的动态分配。
在维护回调类的语义和API的同时,有没有办法摆脱下面代码中的 new 和 delete ?我真的希望能。
需要的东西:
// base class for something we can "call"
class callable {
public:
virtual void operator()() = 0;
virtual ~callable() {}
};
// wraps pointer-to-members
template<class C>
class callable_from_object : public callable {
public:
callable_from_object(C& object, void (C::*method)())
: o(object), m(method) {}
void operator()() {
(&o ->* m) ();
}
private:
C& o;
void (C::*m)();
};
// wraps pointer-to-functions or pointer-to-static-members
class callable_from_function : public callable {
public:
callable_from_function(void (*function)())
: f(function) {}
void operator()() {
f();
};
private:
void (*f)();
};
回调类:
// generic wrapper for any callable
// this is the only class which is exposed to the user
class callback : public callable {
public:
template<class C>
callback(C& object, void (C::*method)())
: c(*new callable_from_object<C>(object, method)) {}
explicit callback(void (*function)())
: c(*new callable_from_function(function)) {}
void operator()() {
c();
}
~callback() {
std::cout << "dtor\n"; // check for mem leak
delete &c;
}
private:
callable& c;
};
API示例:
struct X {
void y() { std::cout << "y\n"; }
static void z() { std::cout << "z\n"; }
} x;
void w() { std::cout << "w\n"; }
int main(int, char*[]) {
callback c1(x, &X::y);
callback c2(X::z);
callback c3(w);
c1();
c2();
c3();
return 0;
}
非常感谢!! : - )
答案 0 :(得分:5)
您可以使用展示位置新功能。比如,设置允许callback
允许的最大大小限制,例如16
个字节。然后,将unsigned char
缓冲区放入正好宽的callback
类中,并确保它正确对齐(GCC
具有该属性,如果幸运的话,微软有一个也属于那个)。
如果你使用一个联合,你也可以相当安全,并且在char缓冲区旁边放入你想要填充的类型的假人 - 这也将确保正确的对齐。
然后,使用placement new,而不是使用普通的new,例如
if(placement_allocated< callable_from_object<C> >::value) {
new ((void*)buffer.p) // union member p is the unsigned char buffer
callable_from_object<C>(object, method);
c = (callable*)buffer.p;
} else {
c = new callable_from_object<C>(object, method);
}
然后,将c
成员改为指针。您还需要设置一个标志,以便记住是否必须在析构函数中调用delete,或者单独保留放置缓冲区并显式调用析构函数。
基本 boost::function
如何做到这一点。然而,它还有很多其他的东西来优化分配。它使用自己的vtable机制来优化空间,当然也经过了很好的测试。
当然,这并不容易。但这似乎是唯一要做的事情。
答案 1 :(得分:3)
<强> YAY !!! 强>
我最好的解决方案,不使用模板,不使用动态分配,不使用继承(就像联合一样):
#include <iostream>
#include <stdexcept>
class callback {
public:
callback() :
type(not_a_callback) {}
template<class C>
callback(C& object, void (C::*method)()) :
type(from_object),
object_ptr(reinterpret_cast<generic*>(&object)),
method_ptr(reinterpret_cast<void (generic::*) ()>(method)) {}
template<typename T>
explicit callback(T function) :
type(from_function),
function_ptr((void (*)()) function) {}
void operator()() {
switch(type) {
case from_object:
(object_ptr ->* method_ptr) ();
break;
case from_function:
function_ptr();
break;
default:
throw std::runtime_error("invalid callback");
};
}
private:
enum { not_a_callback, from_object, from_function } type;
class generic;
union {
void (*function_ptr)();
struct {
generic* object_ptr;
void (generic::*method_ptr)();
};
};
};
好吧,它很难看,但它很快。对于2000万次迭代,Boost版本需要11.8s,我使用动态alloc需要9.8s,而使用union需要4.2s。它使用动态分配比我小60%,比增强小130%。
编辑:更新了默认构造函数。
答案 2 :(得分:1)
在您的示例中,您可以通过删除callback
类来删除新的和删除。这只是callable_from_object
和callable_from_object
上的一个装饰器,它提供了一些语法糖:自动选择要委托的正确可调用对象。
然而这种糖很好,你可能想保留它。此外,您可能还必须将对象放在堆上的其他可调用对象。
对我来说,更大的问题是为什么要创建一个回调库?如果只是练习c ++那么这很好,但是已经有很多这样的例子:
为什么不使用这些?
通过查看您的示例,如果您继续前进的路径,您的解决方案将汇聚到boost :: function,而不具备灵活性。那么为什么不使用呢?虽然我并不认为提升开发人员是上帝,但他们是非常沮丧的工程师,他们拥有出色的同行评审流程,从而产生了非常强大的图书馆。我认为大多数个人或组织都不能重塑更好的图书馆。
如果您的担忧是过度的记忆分配和解除分配,那么解决方案就是这种情况可能是各种可调用子类型的自定义分配器。但我再次允许其他人研究这些技术并使用他们的库。
答案 3 :(得分:1)
使用boost :: function和boost:bind。
typedef boost::function<void ()> callback;
int main(int, char*[]) {
callback d1 = boost::bind(&X::y, &x);
callback d2 = &X::z;
callback d3 = w;
d1();
d2();
d3();
return 0;
}
是的,它有效,但它比我的实现慢20%(动态分配),代码大80%。
PS。我重复了主代码2000万次。 Boost版本需要11.8秒,我需要9.8秒。
修改强>
请参阅this
答案 4 :(得分:0)
你无法摆脱新的删除作为你的多态性,因此它最终会尝试将子类复制到父类中并且松散子类功能。