在C ++中设计事件机制 [英] Designing an event mechanism in C++

查看:117
本文介绍了在C ++中设计事件机制的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在C ++中设计一个通用的(但是有些用例特定的)事件传递机制,而不会违背关于新风格C ++的纹理,同时不会超过模板。



我的用例有点特别,因为我需要完全控制事件分发的时间。事件系统是世界模拟的基础,其中世界的每个迭代作用于由前一帧产生的事件。因此,我要求所有事件在分派之前排队,以便应用程序可以按特定的时间间隔刷新队列,有点像经典的GUI事件循环。



我的用例在Ruby,Python或甚至C中实现起来很简单,但是使用C ++我会有点短。我已经查看了Boost :: Signal和其他类似的库,但是它们似乎太复杂或不灵活,无法满足我的特定用例。 (Boost,特别是基于模板的方法,常常会导致混淆,特别是boost :: bind或boost :: function。)






这是系统,以大笔画:




  • 消费者通过直接注册自己


  • 事件只是字符串名称,但每个事件可能附加了额外的数据。


  • 侦听器只是方法。如果这是C ++ 11,我会使用lambdas,但我需要广泛的编译器可移植性,所以现在使用方法。


  • ,则事件进入队列,直到将其分派到侦听器列表。


  • 队列按事件触发的严格顺序调度。 (因此,如果生成器A触发事件x,生成器B触发y,生成器B再次触发z,则总顺序为x,y,z。)


  • p>重要的是,无论侦听器产生什么事件都不会被分派,直到下一个迭代 - 因此内部实际上有两个队列。







这是一个消费者的理想伪代码示例:


$ b b

  SpaceshipController :: create(){
spaceship.listen(crash,& on_crash);
}

SpaceshipController :: on_crash(CrashEvent event){
spaceship.unlisten(crash,& on_crash);
spaceship.remove();
add(new ExplosionDebris);
add(new爆炸声);
}

这里是一个制片人:

  Spaceship :: collided_with(CollisionObject object){
trigger(crash,new CrashEvent(object));
}

这一切都很好,但是翻译成现代C ++难度。






问题是,无论是使用旧式C ++还是使用多态实例和丑陋,必须使用编译时定义的类型来处理模板级多态。



我已经尝试使用boost :: bind(),我可以生成一个listen方法像这样:

  class EventManager 
{
template< class ProducerClass,class ListenerClass,class EventClass>
void EventManager :: listen(
shared_ptr< ProducerClass> producer,
string event_name,
shared_ptr< ListenerClass> listener,
void(ListenerClass :: * method) EventClass * event)

{
boost :: function1< void,EventClass *> bound_method = boost :: bind(method,listener,_1);
// ...为地图添加处理程序以供以后执行...
}
}

(注意我如何定义中心事件管理器;这是因为我需要在所有生产者之间维护一个队列。为了方便起见,每个类仍然继承一个mixin,它提供了listen()和trigger )



现在可以通过执行:

  void SpaceshipController :: create()
{
event_manager.listen(spaceship,crash,shared_from_this(),
& SpaceshipController :: on_crash);
}

void SpaceshipController :: on_crash(CrashEvent * event)
{
// ...
}

这很好,虽然它是冗长的。我讨厌迫使每个类继承enable_shared_from_this,而C ++要求方法引用包括类名,这很糟糕,但是这两个问题可能是不可避免的。



看看如何实现listen()这种方式,因为类只在编译时才知道。我需要将侦听器存储在每个生产者的映射中,而映射又包含每个事件名称的映射,例如:

  unordered_map< shared_ptr< ProducerClass>,
unordered_map< string,vector< boost:function1< void,EventClass *> > > >听众;

但是当然C ++不允许我。我可以作弊:

  unordered_map< shared_ptr< void *> ;, 
unordered_map< string,vector< boost:function1< ; void,void *> > > >听众;

但是那样感觉很脏。



所以现在我要模板化EventManager或者东西,并为每个生产者保留一个,或许?但我不知道如何做,而不分割队列,我不能这样做。






注意我是如何明确地试图避免为每种类型的事件,Java风格定义纯接口类:

  class CrashEventListener 
{
virtual void on_crash(CrashEvent * event)= 0;
}

根据我记住的事件数量, 。



这也引发了另一个问题:我想对事件处理程序进行细粒度的控制。例如,有许多生产者提供称为改变的事件。我想能够将生产者A的更改事件挂钩到on_a_change,将生产者的B更改事件挂钩到例如on_b_change。






有了这一切,有人可以请我在

更新:这个答案解释了一个选项,但我认为基于 boost :: any 的此解决方案的修改版本更干净。 p>




首先,让我们想象一下如果你不需要在事件管理器中排队事件,解决方案的外观。也就是说,让我们想象一下,所有的宇宙飞船只要有一个事件要报告,就会实时发出信号给适当的监听者。



在这种情况下,最简单的解决方案是每个宇宙飞船都有几个boost ::信号,这些监听器可以连接到。当船要报告事件时,它只是发射相应的信号。 (即,通过operator()调用信号,就像它是一个函数。)



这个系统会碰到几个你的项目符号要求自己直接与事件生成器,并且处理程序只是方法),但它不解决事件队列问题。幸运的是,有一个简单的解决方法。



当一个事件生产者(即宇宙飞船)想通知他的听众一个事件,他不应该信号自己。相反,他应该使用boost :: bind打包信号调用,并将结果函数对象传递给事件处理程序(以boost :: function的形式),将其附加到他的队列。这样,给予事件处理程序的所有事件只是以下类型: boost :: function< void()>



当是刷新队列的时候,事件处理程序只调用队列中的所有打包事件,每个事件本质上是一个回调,调用特定事件的生产者(飞船)信号。 / p>

这是一个完整的示例实现。 main()函数演示了工作系统的简单模拟。我甚至在事件管理器中抛出一些互斥锁,因为我假设他可能被多个线程访问。我没有做同样的控制器或宇宙飞船。显然,在main()函数中提供的简单的单线程测试不会执行事件管理器的线程安全,但是没有复杂的事情。



最后,你会注意到我包括两种不同类型的事件。两个示例事件(crash和mutiny)期望调用具有定制签名的方法(基于与该事件相关联的信息的类型)。其他事件(起飞和着陆)是通用的。侦听器在订阅通用事件时提供一个字符串(事件名称)。



总的来说,此实现满足所有的项目符号。 (使用通用事件示例作为一种方式来满足项目符号#2)。如果你想扩展通用信号类型为EventInfo或一个额外的参数,这可以很容易做到。



请注意,这里只有一个监听器(控制器),但是没有任何实现限制了监听器的数量。您可以添加尽可能多的你喜欢。



还有一件事:由于你对宇宙飞船的继承有所鄙视,所以你必须确保你对生产者(宇宙飞船)从enable_shared_from_this,我绑定了航天对象(通过weak_ptr)在订阅时的信号处理程序。这样,当发射信号时,宇宙飞船不必向听众显式地提供一个句柄。



顺便说一下,BEGIN / END Orbit输出语句在main()中只是为了告诉你事件管理器被触发之前,监听器没有收到事件。



(参考:和boost 1.46,但应该使用旧版本的boost。)

  #include< iostream> 
#include< vector>
#include< string>
#include< set>
#include< map>

#include< boost / bind.hpp>
#include< boost / function.hpp>
#include< boost / signals2.hpp>
#include< boost / foreach.hpp>
#include< boost / thread.hpp>
#include< boost / thread / mutex.hpp>
#include< boost / lexical_cast.hpp>

//转发声明
class Spaceship;
typedef boost :: shared_ptr< Spaceship> SpaceshipPtr;
typedef boost :: weak_ptr< Spaceship>太空船

class EventManager;
typedef boost :: shared_ptr< EventManager> EventManagerPtr;

class EventManager
{
public:
//通知所有最近事件的侦听器
void TriggerAllQueuedEvents()
{
NotificationVec vecNotifications;

//打开受保护的作用域以修改通知列表
{
//一次一个线程
boost :: recursive_mutex :: scoped_lock lock(m_notificationProtection) ;

//将通知向量复制到我们的本地列表并同时清除
std :: swap(vecNotifications,m_vecQueuedNotifications);
}

//现在循环遍历通知回调并调用每个。
//因为我们正在循环我们刚刚创建的副本,新事件不会影响我们。
BOOST_FOREACH(const EventNotificationFn& fn,vecNotifications)
{
fn();
}
}

//回调签名
typedef void EventNotificationFnSignature();
typedef boost :: function< EventNotificationFnSignature> EventNotificationFn;

//!使用事件管理器为事件排队
void QueueEvent(const EventNotificationFn& event)
{
//一次一个线程。
boost :: recursive_mutex :: scoped_lock lock(m_notificationProtection);

m_vecQueuedNotifications.push_back(event);
}

private:
//事件队列
typedef std :: vector< EventNotificationFn> NotificationVec;
NotificationVec m_vecQueued通知;

//这个mutex用于确保一次一个地访问通知列表
boost :: recursive_mutex m_notificationProtection;
};


class Spaceship
{
public:
Spaceship(const std :: string& name,const EventManagerPtr& pEventManager)
:m_name(name)
,m_pEventManager(pEventManager)
{
}

const std :: string& name()
{
return m_name;
}

//定义碰撞事件的处理程序必须是什么样子
typedef void CrashEventHandlerFnSignature(const std :: string& sound);
typedef boost :: function< CrashEventHandlerFnSignature> CrashEventHandlerFn;

//调用此函数以通知崩溃事件
boost :: signals2 :: connection subscribeToCrashEvents(const CrashEventHandlerFn& fn)
{
return m_crashSignal。连接(fn);
}

//定义mutiny事件的处理程序必须是什么样子
typedef void MutinyEventHandlerFnSignature(bool mutinyWasSuccessful,int numDeadCrew);
typedef boost :: function< MutinyEventHandlerFnSignature> MutinyEventHandlerFn;

//调用此函数以通知mutiny事件
boost :: signals2 :: connection subscribeToMutinyEvents(const MutinyEventHandlerFn& fn)
{
return m_mutinySignal。连接(fn);
}

//定义通用事件的处理程序必须是什么样子
typedef void GenericEventHandlerFnSignature();
typedef boost :: function< GenericEventHandlerFnSignature> GenericEventHandlerFn;

//调用此函数以获得通用事件的通知
boost :: signals2 :: connection subscribeToGenericEvents(const std :: string& eventType,const GenericEventHandlerFn& fn)
{
if(m_genericEventSignals [eventType] == NULL)
{
m_ge​​nericEventSignals [eventType] .reset(new GenericEventSignal);
}
return m_ge​​nericEventSignals [eventType] - > connect(fn);
}

void原因Crash(const std :: string& sound)
{
//船已经崩溃了。使用事件管理器将事件排队。
m_pEventManager-> QueueEvent(boost :: bind(boost :: ref(m_crashSignal),sound)); //<必须使用boost :: ref,因为信号是不可复制的。
}

void原因Mutiny(bool successful,int numDied)
{
//发生了mutiny。使用事件管理器将事件排队
m_pEventManager-> QueueEvent(boost :: bind(boost :: ref(m_mutinySignal),successful,numDied)); //<必须使用boost :: ref,因为信号是不可复制的。
}

void原因GenericEvent(const std :: string& eventType)
{
//使用事件管理器排队事件
m_pEventManager-> ; QueueEvent(boost :: bind(boost :: ref(* m_ge​​nericEventSignals [eventType]))); //<必须使用boost :: ref,因为信号是不可复制的。
}

private:
std :: string m_name;
EventManagerPtr m_pEventManager;

boost :: signals2 :: signal< CrashEventHandlerFnSignature> m_crashSignal;
boost :: signals2 :: signal< MutinyEventHandlerFnSignature> m_mutinySignal;

//此映射需要使用ptrs,因为std :: map需要一个可复制的值类型
//(boost信号不可复制)
typedef boost :: signals2 :: signal< GenericEventHandlerFnSignature> GenericEventSignal;
typedef boost :: shared_ptr< GenericEventSignal> GenericEventSignalPtr;
std :: map< std :: string,GenericEventSignalPtr> m_genericEventSignals;
};

class Controller
{
public:
Controller(const std :: set< SpaceshipPtr>& ships)
{
//对于每艘船,订阅我们感兴趣的所有事件。
BOOST_FOREACH(const SpaceshipPtr& pSpaceship,ships)
{
m_ships.insert(pSpaceship);

//在处理程序调用中绑定一个weak_ptr(使用shared_ptr会导致内存泄漏)
SpaceshipWPtr wpSpaceship(pSpaceship);

//用飞船注册事件回调函数,以便他能通知我们。
//绑定一个指针到特定的太空船,所以我们知道谁发起了事件。
boost :: signals2 :: connection crashConnection = pSpaceship-> subscribeToCrashEvents(
boost :: bind(& Controller :: HandleCrashEvent,this,wpSpaceship,_1));

boost :: signals2 :: connection mutinyConnection = pSpaceship-> subscribeToMutinyEvents(
boost :: bind(& Controller :: HandleMutinyEvent,this,wpSpaceship,_1,_2));

//通用事件的回调
boost :: signals2 :: connection takeoffConnection =
pSpaceship-> subscribeToGenericEvents(
takeoff,
boost :: bind(& Controller :: HandleGenericEvent,this,wpSpaceship,takeoff));

boost :: signals2 :: connection landingConnection =
pSpaceship-> subscribeToGenericEvents(
landing,
boost :: bind(& Controller :: HandleGenericEvent ,this,wpSpaceship,landing));

//缓存这些连接以确保我们得到通知
m_allConnections [pSpaceship] .push_back(crashConnection);
m_allConnections [pSpaceship] .push_back(mutinyConnection);
m_allConnections [pSpaceship] .push_back(takeoffConnection);
m_allConnections [pSpaceship] .push_back(landingConnection);
}
}

〜Controller()
{
//断开任何信号我们还有
BOOST_FOREACH(const SpaceshipPtr pShip, m_ships)
{
BOOST_FOREACH(boost :: signals2 :: connection& conn,m_allConnections [pShip])
{
conn.disconnect
}
}
}

private:
typedef std :: vector< boost :: signals2 :: connection> ConnectionVec;
std :: map< SpaceshipPtr,ConnectionVec> m_allConnections;
std :: set< SpaceshipPtr> m_ships;

void HandleGenericEvent(SpaceshipWPtr wpSpaceship,const std :: string& eventType)
{
//从弱ptr获取共享ptr
SpaceshipPtr pSpaceship = wpSpaceship 。锁();

std :: cout<< Event on<< pSpaceship-> name()<< :<< eventType<< '\\\
';
}

void HandleCrashEvent(SpaceshipWPtr wpSpaceship,const std :: string& sound)
{
//从弱ptr获取共享ptr
SpaceshipPtr pSpaceship = wpSpaceship.lock();

std :: cout<< pSpaceship-> name()<< 坠落与声音:<声音< '\\\
';

//那艘船已经死了。从我们跟踪的船只列表中删除它。
m_ships.erase(pSpaceship);

//另外,确保我们没有从它获得更多的事件
BOOST_FOREACH(boost :: signals2 :: connection& conn,m_allConnections [pSpaceship])
{
conn.disconnect();
}
m_allConnections.erase(pSpaceship);
}

void HandleMutinyEvent(SpaceshipWPtr wpSpaceship,bool mutinyWasSuccessful,int numDeadCrew)
{
SpaceshipPtr pSpaceship = wpSpaceship.lock
std :: cout<< (mutinyWasSuccessful?Successful:Unsuccessful);
std :: cout<< mutiny on< pSpaceship-> name()<< !(<< numDeadCrew<<dead crew members)\\\
;
}
};

int main()
{
//实例化事件管理器
EventManagerPtr pEventManager(new EventManager);

//创建一些船用于
int numShips = 5;
std :: vector< SpaceshipPtr> vecShips;
for(int shipIndex = 0; shipIndex< numShips; ++ shipIndex)
{
std :: string name =Ship#+ boost :: lexical_cast< std :: string& (shipIndex);
SpaceshipPtr pSpaceship(new Spaceship(name,pEventManager));
vecShips.push_back(pSpaceship);
}

//用我们的船创建控制器
std :: set< SpaceshipPtr> setShips(vecShips.begin(),vecShips.end());
控制器控制器(setShips);

//快速和脏的模拟
//我们会在模拟中发生各种事件,
//定期刷新事件通过触发事件管理器

std :: cout< BEGIN Orbit#1< std :: endl;
vecShips [0] - > CauseGenericEvent(takeoff);
vecShips [0] - > CauseCrash(Kaboom!);
vecShips [1] - > CauseGenericEvent(takeoff);
vecShips [1] - > CauseCrash(Blam!);
vecShips [2] - > CauseGenericEvent(takeoff);
vecShips [2] - > CauseMutiny(false,7);
std :: cout<< END Orbit#1< std :: endl;

pEventManager-> TriggerAllQueuedEvents();

std :: cout<< BEGIN Orbit#2< std :: endl;
vecShips [3] - > CauseGenericEvent(takeoff);
vecShips [3] - > CauseMutiny(true,2);
vecShips [3] - > CauseGenericEvent(takeoff);
vecShips [4] - > CauseCrash(Splat!);
std :: cout<< END Orbit#2< std :: endl;

pEventManager-> TriggerAllQueuedEvents();

std :: cout<< BEGIN Orbit#3< std :: endl;
vecShips [2] - > CauseMutiny(false,15);
vecShips [2] - > CauseMutiny(true,20);
vecShips [2] - > CauseGenericEvent(landing);
vecShips [3] - > CauseCrash(Fizzle);
vecShips [3] - > CauseMutiny(true,0); //<应该不会导致输出,因为这艘船已经崩溃了!
std :: cout<< END Orbit#3< std :: endl;

pEventManager-> TriggerAllQueuedEvents();

return 0;
}

运行时,上述程序产生以下输出:

  BEGIN Orbit#1 
END Orbit#1
Ship#0上的活动:takeoff
船#0撞倒声音:Kaboom!
船上#1的事件:takeoff
船#1坠毁与声音:Blam!
第2船上的事件:起飞
第2船不成功叛变! (7人死亡的船员)
BEGIN轨道#2
结束轨道#2
船舶3上的事件:起飞
船只3的成功叛变! (2个死船员)
船3号事件:起飞
船#4坠毁,声音:Splat!
BEGIN Orbit#3
END Orbit#3
第2船不成功! (15名死船员)
在船2上成功叛变! (20人死亡的船员)
船2上的事件:着陆
船#3坠毁声音:Fizzle


I am trying to design a generic (but somewhat use-case-specific) event-passing mechanism in C++ without going against the grain with regard to "new style" C++, and at the same time without going overboard with templates.

My use case is somewhat particular in that I require complete control over when events are distributed. The event system underlies a world simulation where each iteration of the world acts on the events generated by the previous frame. So I require all events to be queued up before they are dispatched, so that the app can flush the queue at specific intervals, somewhat like the classic GUI event loop.

My use case is trivial to implement in Ruby, Python or even C, but with C++ I am coming up a bit short. I have looked at Boost::Signal and other similar libraries, but they seem too complex or inflexible to suit my particular use case. (Boost, in particular, is templated-based often to the point of utter confusion, especially things like boost::bind or boost::function.)


Here's the system, in broad strokes:

  • Consumers listen to events by registering themselves directly with the objects that produce the events.

  • Events are just string names, but each event may have additional data attached to it.

  • Listeners are just methods. If this were C++11 I would use lambdas, but I need broad compiler portability, so using methods for the moment.

  • When a producer triggers an event, the event goes into the queue until the time comes to dispatch it to the list of listeners.

  • The queue is dispatched in strict order of event triggering. (So if producer A triggers event x, an producer B triggers y, and producer B triggers z again, then the total order is x, y, z.)

  • It's important that whatever events are produced by the listeners during will not be dispatched until the next iteration -- so there are really two queues internally.


Here's an "ideal" pseudo-code example of a consumer:

SpaceshipController::create() {
    spaceship.listen("crash", &on_crash);
}

SpaceshipController::on_crash(CrashEvent event) {
    spaceship.unlisten("crash", &on_crash);
    spaceship.remove();
    add(new ExplosionDebris);
    add(new ExplosionSound);
}

And here is a producer:

Spaceship::collided_with(CollisionObject object) {
    trigger("crash", new CrashEvent(object));
}

All this is well and good, but translating into modern C++ is where I meet difficulty.


The problem is that either one has to go with old-style C++ with casting polymorphic instances and ugliness, or one has to go with template-level polymorphism with compile-time defined typing.

I have experimented with using boost::bind(), and I can produce a listen method like this:

class EventManager
{
    template <class ProducerClass, class ListenerClass, class EventClass>
        void EventManager::listen(
            shared_ptr<ProducerClass> producer,
            string event_name,
            shared_ptr<ListenerClass> listener,
            void (ListenerClass::*method)(EventClass* event)
        )
    {
        boost::function1<void, EventClass*> bound_method = boost::bind(method, listener, _1);
        // ... add handler to a map for later execution ...
    }
}

(Note how I am defining central event manager; that is because I need to maintain a single queue across all producers. For convenience, individual classes still inherit a mixin that provides listen() and trigger() that delegate to the event manager.)

Now it's possible to listen by doing:

void SpaceshipController::create()
{
    event_manager.listen(spaceship, "crash", shared_from_this(),
        &SpaceshipController::on_crash);
}

void SpaceshipController::on_crash(CrashEvent* event)
{
    // ...
}

That's pretty good, although it's verbose. I hate forcing every class to inherit enable_shared_from_this, and C++ requires that method references include the class name, which sucks, but both problems are probably unavoidable.

Unfortunately, I don't see how to implement listen() this way, since the classes are only known at compile time. I need to store the listeners in a per-producer map, which in turn contains a per-event-name map, something like:

unordered_map<shared_ptr<ProducerClass>,
    unordered_map<string, vector<boost:function1<void, EventClass*> > > > listeners;

But of course C++ doesn't let me. I could cheat:

unordered_map<shared_ptr<void*>,
    unordered_map<string, vector<boost:function1<void, void*> > > > listeners;

but then that feels terribly dirty.

So now I have to templatize EventManager or something, and keep one for each producer, perhaps? But I don't see how to do that without splitting up the queue, and I can't do that.


Note how I am explicitly trying to avoid having to define pure interface classes for each type of event, Java-style:

class CrashEventListener
{
    virtual void on_crash(CrashEvent* event) = 0;
}

With the number of events I have in mind, that would get awful, fast.

It also raises another issue: I want to have fine-grained control over event handlers. For example, there are many producers that simply provide an event called "change". I want to be able to hook producer A's "change" event to on_a_change, and producer's B "change" event to on_b_change, for example. Per-event interfaces would make that inconvenient at best.


With all this in mind, could someone please point me in the right direction?

解决方案

Update: This answer explains one option, but I think the modified version of this solution based on boost::any is cleaner.


First, let's imagine how the solution would look if you didn't need to queue up your events in an event manager. That is, let's imagine that all "spaceships" could signal the appropriate listener in real time whenever they have an event to report.

In that case, the simplest solution is to have each spaceship own several boost::signals, which listeners can connect to. When a ship wants to report an event, it simply fires the corresponding signal. (That is, call the signal via the operator() as though it were a function.)

That system would hit a couple of your bullet-point requirements (consumers register themselves directly with the event-producers, and the handlers are just methods), but it doesn't solve the event queue problem. Fortunately, there's an easy fix for that.

When an event producer (i.e. spaceship) wants to notify his listener(s) of an event, he shouldn't fire the signal himself. Instead, he should package up the signal invocation using boost::bind, and pass the resulting function object over to the event handler (in the form of a boost::function), which appends it to his queue. In this way, all events given to the event handler are merely of the following type: boost::function<void ()>

When it is time to flush the queue, the event handler merely calls all of the packaged events in his queue, each of which is essentially a callback that invokes the producer's (spaceship's) signal for a particular event.

Here's a complete sample implementation. The main() function demonstrates a simple "simulation" of the working system. I even threw some mutex locking in the event manager, since I assume he might be accessed by more than one thread. I didn't do the same for the controller or the spaceships. Obviously, the simple single-threaded test supplied in the main() function does not exercise the thread-safety of the event manager, but there's nothing complicated going on there.

Finally, you'll notice that I included two different types of events. Two of the example events (crash and mutiny) expect to be calling methods with customized signatures (based on the type of information associated with that event). The other events (takeoff and landing) are "generic". Listeners provide a string (event name) when subscribing to generic events.

In all, this implementation satisfies all of your bullet points. (With the generic event examples thrown in as a way to satisfy bullet point #2.) If you wanted to augment the "generic" signal type with an extra parameter for "EventInfo" or somesuch, that can easily be done.

Note that there's only one listener here (the controller), but nothing about the implementation restricts the number of listeners. You can add as many as you like. You will, however, have to make sure you manage the lifetime of your producers (spaceships) carefully.

One more thing: Since you expressed some disdain for having the spaceships inherit from enable_shared_from_this, I bound the spaceship object (via a weak_ptr) into the signal handler at the time of subscription. That way the spaceship doesn't have to explicitly provide the listener with a handle to himself when he fires the signal.

By the way, the BEGIN/END Orbit output statements in main() are just there to show you that the events are not being received by the listeners until the event manager is triggered.

(For reference: this compiles using gcc and boost 1.46, but should work with older versions of boost.)

#include <iostream>
#include <vector>
#include <string>
#include <set>
#include <map>

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/signals2.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/lexical_cast.hpp>

// Forward declarations
class Spaceship;
typedef boost::shared_ptr<Spaceship> SpaceshipPtr;
typedef boost::weak_ptr<Spaceship> SpaceshipWPtr;

class EventManager;
typedef boost::shared_ptr<EventManager> EventManagerPtr;

class EventManager
{
public:
    // Notify listeners of all recent events
    void TriggerAllQueuedEvents()
    {
        NotificationVec vecNotifications;

        // Open a protected scope to modify the notification list
        {
            // One thread at a time
            boost::recursive_mutex::scoped_lock lock( m_notificationProtection );

            // Copy the notification vector to our local list and clear it at the same time
            std::swap( vecNotifications, m_vecQueuedNotifications );
        }

        // Now loop over the notification callbacks and call each one.
        // Since we're looping over the copy we just made, new events won't affect us.
        BOOST_FOREACH( const EventNotificationFn & fn, vecNotifications )
        {
            fn() ;
        }
    }

    // Callback signature
    typedef void EventNotificationFnSignature();
    typedef boost::function<EventNotificationFnSignature> EventNotificationFn;

    //! Queue an event with the event manager
    void QueueEvent( const EventNotificationFn & event )
    {
        // One thread at a time.
        boost::recursive_mutex::scoped_lock lock(  m_notificationProtection );

        m_vecQueuedNotifications.push_back(event);
    }

private:
    // Queue of events
    typedef std::vector<EventNotificationFn> NotificationVec ;
    NotificationVec m_vecQueuedNotifications;

    // This mutex is used to ensure one-at-a-time access to the list of notifications
    boost::recursive_mutex m_notificationProtection ;
};


class Spaceship
{
public:
    Spaceship(const std::string & name, const EventManagerPtr & pEventManager)
    : m_name(name)
    , m_pEventManager(pEventManager)
    {
    }

    const std::string& name()
    {
        return m_name;
    }

    // Define what a handler for crash events must look like
    typedef void CrashEventHandlerFnSignature(const std::string & sound);
    typedef boost::function<CrashEventHandlerFnSignature> CrashEventHandlerFn;

    // Call this function to be notified of crash events
    boost::signals2::connection subscribeToCrashEvents( const CrashEventHandlerFn & fn )
    {
        return m_crashSignal.connect(fn);
    }

    // Define what a handler for mutiny events must look like
    typedef void MutinyEventHandlerFnSignature(bool mutinyWasSuccessful, int numDeadCrew);
    typedef boost::function<MutinyEventHandlerFnSignature> MutinyEventHandlerFn;

    // Call this function to be notified of mutiny events
    boost::signals2::connection subscribeToMutinyEvents( const MutinyEventHandlerFn & fn )
    {
        return m_mutinySignal.connect(fn);
    }

    // Define what a handler for generic events must look like
    typedef void GenericEventHandlerFnSignature();
    typedef boost::function<GenericEventHandlerFnSignature> GenericEventHandlerFn;

    // Call this function to be notified of generic events
    boost::signals2::connection subscribeToGenericEvents( const std::string & eventType, const GenericEventHandlerFn & fn )
    {
        if ( m_genericEventSignals[eventType] == NULL )
        {
            m_genericEventSignals[eventType].reset( new GenericEventSignal );
        }
        return m_genericEventSignals[eventType]->connect(fn);
    }

    void CauseCrash( const std::string & sound )
    {
        // The ship has crashed.  Queue the event with the event manager.
        m_pEventManager->QueueEvent( boost::bind( boost::ref(m_crashSignal), sound ) ); //< Must use boost::ref because signal is noncopyable.
    }

    void CauseMutiny( bool successful, int numDied )
    {
        // A mutiny has occurred.  Queue the event with the event manager
        m_pEventManager->QueueEvent( boost::bind( boost::ref(m_mutinySignal), successful, numDied ) ); //< Must use boost::ref because signal is noncopyable.
    }

    void CauseGenericEvent( const std::string & eventType )
    {
        // Queue the event with the event manager
        m_pEventManager->QueueEvent( boost::bind( boost::ref(*m_genericEventSignals[eventType]) ) ); //< Must use boost::ref because signal is noncopyable.
    }

private:
    std::string m_name;
    EventManagerPtr m_pEventManager;

    boost::signals2::signal<CrashEventHandlerFnSignature> m_crashSignal;
    boost::signals2::signal<MutinyEventHandlerFnSignature> m_mutinySignal;

    // This map needs to use ptrs, because std::map needs a value type that is copyable
    // (boost signals are noncopyable)
    typedef boost::signals2::signal<GenericEventHandlerFnSignature> GenericEventSignal;
    typedef boost::shared_ptr<GenericEventSignal> GenericEventSignalPtr;
    std::map<std::string, GenericEventSignalPtr > m_genericEventSignals;
};

class Controller
{
public:
    Controller( const std::set<SpaceshipPtr> & ships )
    {
        // For every ship, subscribe to all of the events we're interested in.
        BOOST_FOREACH( const SpaceshipPtr & pSpaceship, ships )
        {
            m_ships.insert( pSpaceship );

            // Bind up a weak_ptr in the handler calls (using a shared_ptr would cause a memory leak)
            SpaceshipWPtr wpSpaceship(pSpaceship);

            // Register event callback functions with the spaceship so he can notify us.
            // Bind a pointer to the particular spaceship so we know who originated the event.
           boost::signals2::connection crashConnection = pSpaceship->subscribeToCrashEvents(
               boost::bind( &Controller::HandleCrashEvent, this, wpSpaceship, _1 ) );

           boost::signals2::connection mutinyConnection = pSpaceship->subscribeToMutinyEvents(
               boost::bind( &Controller::HandleMutinyEvent, this, wpSpaceship, _1, _2 ) );

           // Callbacks for generic events
           boost::signals2::connection takeoffConnection =
               pSpaceship->subscribeToGenericEvents(
                   "takeoff",
                   boost::bind( &Controller::HandleGenericEvent, this, wpSpaceship, "takeoff" ) );

           boost::signals2::connection landingConnection =
               pSpaceship->subscribeToGenericEvents(
                   "landing",
                   boost::bind( &Controller::HandleGenericEvent, this, wpSpaceship, "landing" ) );

           // Cache these connections to make sure we get notified
           m_allConnections[pSpaceship].push_back( crashConnection );
           m_allConnections[pSpaceship].push_back( mutinyConnection );
           m_allConnections[pSpaceship].push_back( takeoffConnection );
           m_allConnections[pSpaceship].push_back( landingConnection );
        }
    }

    ~Controller()
    {
        // Disconnect from any signals we still have
        BOOST_FOREACH( const SpaceshipPtr pShip, m_ships )
        {
            BOOST_FOREACH( boost::signals2::connection & conn, m_allConnections[pShip] )
            {
                conn.disconnect();
            }
        }
    }

private:
    typedef std::vector<boost::signals2::connection> ConnectionVec;
    std::map<SpaceshipPtr, ConnectionVec> m_allConnections;
    std::set<SpaceshipPtr> m_ships;

    void HandleGenericEvent( SpaceshipWPtr wpSpaceship, const std::string & eventType )
    {
        // Obtain a shared ptr from the weak ptr
        SpaceshipPtr pSpaceship = wpSpaceship.lock();

        std::cout << "Event on " << pSpaceship->name() << ": " << eventType << '\n';
    }

    void HandleCrashEvent(SpaceshipWPtr wpSpaceship, const std::string & sound)
    {
        // Obtain a shared ptr from the weak ptr
        SpaceshipPtr pSpaceship = wpSpaceship.lock();

        std::cout << pSpaceship->name() << " crashed with sound: " << sound << '\n';

        // That ship is dead.  Delete it from the list of ships we track.
        m_ships.erase(pSpaceship);

        // Also, make sure we don't get any more events from it
        BOOST_FOREACH( boost::signals2::connection & conn, m_allConnections[pSpaceship] )
        {
            conn.disconnect();
        }
        m_allConnections.erase(pSpaceship);
    }

    void HandleMutinyEvent(SpaceshipWPtr wpSpaceship, bool mutinyWasSuccessful, int numDeadCrew)
    {
        SpaceshipPtr pSpaceship = wpSpaceship.lock();
        std::cout << (mutinyWasSuccessful ? "Successful" : "Unsuccessful" ) ;
        std::cout << " mutiny on " << pSpaceship->name() << "! (" << numDeadCrew << " dead crew members)\n";
    }
};

int main()
{
    // Instantiate an event manager
    EventManagerPtr pEventManager( new EventManager );

    // Create some ships to play with
    int numShips = 5;
    std::vector<SpaceshipPtr> vecShips;
    for (int shipIndex = 0; shipIndex < numShips; ++shipIndex)
    {
        std::string name = "Ship #" + boost::lexical_cast<std::string>(shipIndex);
        SpaceshipPtr pSpaceship( new Spaceship(name, pEventManager) );
        vecShips.push_back(pSpaceship);
    }

    // Create the controller with our ships
    std::set<SpaceshipPtr> setShips( vecShips.begin(), vecShips.end() );
    Controller controller(setShips);

    // Quick-and-dirty "simulation"
    // We'll cause various events to happen to the ships in the simulation,
    // And periodically flush the events by triggering the event manager

    std::cout << "BEGIN Orbit #1" << std::endl;
    vecShips[0]->CauseGenericEvent("takeoff");
    vecShips[0]->CauseCrash("Kaboom!");
    vecShips[1]->CauseGenericEvent("takeoff");
    vecShips[1]->CauseCrash("Blam!");
    vecShips[2]->CauseGenericEvent("takeoff");
    vecShips[2]->CauseMutiny(false, 7);
    std::cout << "END Orbit #1" << std::endl;

    pEventManager->TriggerAllQueuedEvents();

    std::cout << "BEGIN Orbit #2" << std::endl;
    vecShips[3]->CauseGenericEvent("takeoff");
    vecShips[3]->CauseMutiny(true, 2);
    vecShips[3]->CauseGenericEvent("takeoff");
    vecShips[4]->CauseCrash("Splat!");
    std::cout << "END Orbit #2" << std::endl;

    pEventManager->TriggerAllQueuedEvents();

    std::cout << "BEGIN Orbit #3" << std::endl;
    vecShips[2]->CauseMutiny(false, 15);
    vecShips[2]->CauseMutiny(true, 20);
    vecShips[2]->CauseGenericEvent("landing");
    vecShips[3]->CauseCrash("Fizzle");
    vecShips[3]->CauseMutiny(true, 0); //< Should not cause output, since this ship has already crashed!
    std::cout << "END Orbit #3" << std::endl;

    pEventManager->TriggerAllQueuedEvents();

    return 0;
}

When run, the above program produces the following output:

BEGIN Orbit #1
END Orbit #1
Event on Ship #0: takeoff
Ship #0 crashed with sound: Kaboom!
Event on Ship #1: takeoff
Ship #1 crashed with sound: Blam!
Event on Ship #2: takeoff
Unsuccessful mutiny on Ship #2! (7 dead crew members)
BEGIN Orbit #2
END Orbit #2
Event on Ship #3: takeoff
Successful mutiny on Ship #3! (2 dead crew members)
Event on Ship #3: takeoff
Ship #4 crashed with sound: Splat!
BEGIN Orbit #3
END Orbit #3
Unsuccessful mutiny on Ship #2! (15 dead crew members)
Successful mutiny on Ship #2! (20 dead crew members)
Event on Ship #2: landing
Ship #3 crashed with sound: Fizzle

这篇关于在C ++中设计事件机制的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆