有几件事我会改变。
首先,让我们暂时忽略 return 并关注与 thatboost::any
混合的内容std::string
。这对于事件系统来说确实是个坏主意,因为它允许潜在的运行时类型不匹配。您只是不想要一种可能会犯此类错误的设计(因为最终会犯这些错误,并且会出现需要修复的错误,从而浪费未来的时间)。
通常,您真正想要的是:
void Module::trigger_event(shared_ptr<Event> const& event)
{
// Event is a base interface
// pull off event->ID() to get identifier and lookup
// dispatch to something that has the signature
// void (shared_ptr<SpecificEvent>)
}
做类似调度员的事情,你通常有
map<IDType, shared_ptr<Dispatcher>> dispatchDictionary_;
和
template <typename SpecificEvent>
class SpecificDispatcher : public Dispatcher
{
public:
SpecificDispatcher(function<void (shared_ptr<SpecificEvent>)> handler)
handler_(handler)
{}
virtual void dispatch(shared_ptr<Event> event)
{
auto specificEvent(static_ptr_cast<SpecificEvent>(event));
handler_(specificEvent);
}
};
(使用以明显方式定义的接口类和注册/注销方法来关联映射中的事件类型 ID)。关键是将 ID 与类中的 EventType 相关联,以确保它始终是相同的关联,并以处理程序本身不需要重新解释数据的方式进行调度(容易出错)。做可以在你的图书馆内自动化的工作,这样就不会在外面做错了。
好的,现在你用你的方法返回什么?返回一个对象,而不是一个函数。如果他们不需要显式调用它,那么您已经保存了另一个潜在的错误来源。就像所有 RAII 一样 - 您不希望人们必须记住调用删除、解锁或......类似地,使用“unregister_event”(或任何你称之为的)。
人们必须在程序中关注四个生命周期:表达式、函数范围、状态和程序。这些对应于它们可以存储您返回的对象的四种类型的东西:匿名(让它掉在地板上 - 它可以在表达式中使用但永远不会分配给命名对象),自动范围对象,对象它是存在于两个异步转换事件之间的 State 类(在 State 模式中)的成员,或者是在退出之前一直存在的全局范围对象。
所有生命周期管理都应使用 RAII 进行管理。这是析构函数的重点,也是面对异常时应该如何管理资源清理。
编辑:我有点留下一堆未说明的部分,指出你会“以明显的方式”连接这些点。但我想我会填写更多的部分(因为我正在构建和安装操作系统,我的错误现在已经下降到最后一个,正在等待安装......)
关键是有人应该能够打字
callbackLifetimeObject = module->set_event<StartEvent>([](shared_ptr<StartEvent> event){
cout << "Module started: " << event->someInfo() << endl;
});
所以 set_event 需要有那种签名来接受这个,它应该将适当的调度程序插入到字典中。有几种方法可以从此处的类型中获取 ID。“显而易见”的方式是创建一个临时的并将其称为“ID”成员——但这会产生对象创建开销。另一种方法是将其转换为“虚拟静态”,然后获取静态(这也是虚拟方法所做的所有事情)。每当我有虚拟静态时,我倾向于将它们转化为特征——同样的东西,但封装稍微好一些,并且还能够修改“已经关闭的类”。然后虚方法也只是调用特征类来返回相同的东西。
所以...
template <typename EventType>
struct EventTrait
{
// typedef your IDType from whatever - looks like you want std::string
static IDType eventID()
{ /* default impl */ }
};
template <>
struct EventTrait<StartEvent>
{
// same as above
static IDType eventID()
{ return "Start"; }
};
那么你就可以
template <typename EventType>
EventRegistration set_event(function<void (shared_ptr<EventType>)> handler)
{
auto id(EventTrait<EventType>::eventID());
dispatchDictionary_.insert(make_pair(id,
make_shared<SpecificDispatcher<EventType>>(handler)));
return EventRegistration(bind(& Module::unset_event, this, id));
}
这应该更好地说明如何将这些部分组合在一起以及您拥有的一些选项。其中一些说明了可能随每个事件重新编码的样板代码。这也是您可能想要自动化的类型。有很多先进的技术可以对这种生成进行元编程,从使用另一个需要规范的构建步骤到在 c++ 元编程系统内部工作。同样,与前面的几点一样,您可以自动化的越多,您遇到的错误就越少。