是否可以在 C++ 中实现事件?
我想用 C++ 实现一个 C# 事件,只是想看看我是否可以做到。我陷入困境,我知道底部是错误的,但我意识到我最大的问题是......
我如何重载 ()
运算符以成为 T
中的任何内容,在这种情况下int func(float)
?我不能吗?我可以吗?我可以实施一个好的替代方案吗?
#include <deque>
using namespace std;
typedef int(*MyFunc)(float);
template<class T>
class MyEvent
{
deque<T> ls;
public:
MyEvent& operator +=(T t)
{
ls.push_back(t);
return *this;
}
};
static int test(float f){return (int)f; }
int main(){
MyEvent<MyFunc> e;
e += test;
}
I wanted to implement a C# event in C++ just to see if I could do it. I got stuck, I know the bottom is wrong but what I realize my biggest problem is...
How do I overload the ()
operator to be whatever is in T
, in this case int func(float)
? I can't? Can I? Can I implement a good alternative?
#include <deque>
using namespace std;
typedef int(*MyFunc)(float);
template<class T>
class MyEvent
{
deque<T> ls;
public:
MyEvent& operator +=(T t)
{
ls.push_back(t);
return *this;
}
};
static int test(float f){return (int)f; }
int main(){
MyEvent<MyFunc> e;
e += test;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果您可以使用 Boost,请考虑使用 Boost.Signals2 ,它提供信号槽/事件/观察者功能。它简单易用,而且非常灵活。 Boost.Signals2 还允许您注册任意可调用对象(例如函子或绑定成员函数),因此它更加灵活,并且它具有许多功能来帮助您正确管理对象生命周期。
如果您尝试自己实现它,那么您就走在正确的道路上。但是,您有一个问题:您到底想如何处理每个注册函数返回的值?您只能从
operator()
返回一个值,因此您必须决定是不返回任何内容,还是返回结果之一,或者以某种方式聚合结果。假设我们想忽略结果,实现起来非常简单,但是如果将每个参数类型作为单独的模板类型参数,会更容易一些(或者,您可以使用 Boost.TypeTraits 之类的东西,它允许您轻松剖析函数类型):
这要求注册的函数具有
void
返回类型。为了能够接受具有任何返回类型的函数,您可以将FuncPtr
更改为(或使用
boost::function
或std::tr1::function< /code> 如果您没有可用的 C++0x 版本)。如果您确实想对返回值执行某些操作,则可以将返回类型作为
MyEvent
的另一个模板参数。这应该是相对简单的事情。通过上述实现,以下内容应该可行:
另一种允许您支持不同数量的事件的方法是对函数类型使用单个类型参数,并具有多个重载的
operator()
重载,每个都采用不同数量的参数。这些重载必须是模板,否则您将因任何与事件的实际数量不匹配的重载而收到编译错误。这是一个可行的例子:(我在这里使用了 C++0x 中的
std::add_pointer
,但是这个类型修饰符也可以在 Boost 和 C++ TR1 中找到;它只是让使用函数模板,因为您可以直接使用函数类型;您不必使用函数指针类型。)这是一个用法示例:If you can use Boost, consider using Boost.Signals2, which provides signals-slots/events/observers functionality. It's straightforward and easy to use and is quite flexible. Boost.Signals2 also allows you to register arbitrary callable objects (like functors or bound member functions), so it's more flexible, and it has a lot of functionality to help you manage object lifetimes correctly.
If you are trying to implement it yourself, you are on the right track. You have a problem, though: what, exactly, do you want to do with the values returned from each of the registered functions? You can only return one value from
operator()
, so you have to decide whether you want to return nothing, or one of the results, or somehow aggregate the results.Assuming we want to ignore the results, it's quite straightforward to implement this, but it's a bit easier if you take each of the parameter types as a separate template type parameter (alternatively, you could use something like Boost.TypeTraits, which allows you to easily dissect a function type):
This requires the registered function to have a
void
return type. To be able to accept functions with any return type, you can changeFuncPtr
to be(or use
boost::function
orstd::tr1::function
if you don't have the C++0x version available). If you do want to do something with the return values, you can take the return type as another template parameter toMyEvent
. That should be relatively straightforward to do.With the above implementation, the following should work:
Another approach, which allows you to support different arities of events, would be to use a single type parameter for the function type and have several overloaded
operator()
overloads, each taking a different number of arguments. These overloads have to be templates, otherwise you'll get compilation errors for any overload not matching the actual arity of the event. Here's a workable example:(I've used
std::add_pointer
from C++0x here, but this type modifier can also be found in Boost and C++ TR1; it just makes it a little cleaner to use the function template since you can use a function type directly; you don't have to use a function pointer type.) Here's a usage example:你绝对可以。 James McNellis 已经链接到一个完整的解决方案,但对于您的玩具示例,我们可以执行以下操作:
这里我利用部分特化来梳理函数指针类型的组件以发现参数类型。 boost.signals 可以做到这一点以及更多,利用类型擦除等功能和特征来确定非函数指针类型可调用对象的此信息。
对于 N 个参数,有两种方法。为 C++0x 添加的“简单”方法是利用可变参数模板和一些其他功能。但是,在添加该功能之前我们就一直在这样做,而且我不知道哪些编译器(如果有) ,还支持可变参数模板,所以我们可以用困难的方式来做到这一点,即再次专业化:
这当然会变得乏味,这就是为什么像 boost.signals 这样的库已经成功了(并且那些使用宏等来实现)减轻一些乏味)。
要允许使用
MyEvent
样式语法,您可以使用如下技术。NullEvent
类型用作占位符,我们再次使用部分专业化来计算数量。You absolutely can. James McNellis has already linked to a complete solution, but for your toy example we can do the following:
Here I've made use of partial specialization to tease apart the components of the function pointer type to discover the argument type. boost.signals does this and more, leveraging features such as type erasure, and traits to determine this information for non-function pointer typed callable objects.
For N arguments there are two approaches. The "easy' way, that was added for C++0x, is leveraging variadic templates and a few other features. However, we've been doing this since before that features was added, and I don't know which compilers if any, support variadic templates yet. So we can do it the hard way, which is, specialize again:
THis gets tedious of course which is why have libraries like boost.signals that have already banged it out (and those use macros, etc. to relieve some of the tedium).
To allow for a
MyEvent<int, int>
style syntax you can use a technique like the followingThe
NullEvent
type is used as a placeholder and we again use partial specialization to figure out the arity.编辑:添加了线程安全实现,基于这个答案。许多修复和性能改进
这是我的版本,通过添加以下内容来改进 James McNellis 的版本:
operator-=
、可变参数模板以支持存储的可调用对象的任何种类、方便Bind(func, object )
和Unbind(func, object)
方法轻松绑定对象和实例成员函数、赋值运算符以及与nullptr
的比较。我不再使用 std::add_pointer 而是只使用 std::function ,在我的尝试中它更灵活(接受 lambda 和 std::function )。此外,我转而使用std::vector
来加快迭代速度,并删除了运算符中返回的*this
,因为无论如何它看起来都不是很安全/有用。 C# 语义仍然缺失:C# 事件无法从声明它们的类外部清除(通过状态友谊将其添加到模板化类型很容易)。它遵循代码,欢迎反馈:
我用这个测试了它:
EDIT: Added thread safe implementation, based on this answer. Many fixes and performance improvements
This is my version, improving James McNellis' one by adding:
operator-=
, variadic template to support any ariety of the stored callable objects, convenienceBind(func, object)
andUnbind(func, object)
methods to easily bind objects and instance member functions, assignment operators and comparison withnullptr
. I moved away from usingstd::add_pointer
to just usestd::function
which in my attempts it's more flexible (accepts both lambdas and std::function). Also I moved to usestd::vector
for faster iteration and removed returning*this
in the operators, since it doesn't look to be very safe/useful anyway. Still missing from C# semantics: C# events can't be cleared from outside the class where they are declared (would be easy to add this by state friendship to a templatized type).It follows the code, feedback is welcome:
I tested it with this:
这是可能的,但不适用于您当前的设计。问题在于回调函数签名被锁定到模板参数中。无论如何,我认为您不应该尝试支持这一点,同一列表中的所有回调都应该具有相同的签名,您不认为吗?
That is possible, but not with your current design. The problem lies with the fact that the callback function signature is locked into your template argument. I don't think you should try to support this anyways, all callbacks in the same list should have the same signature, don't you think?