我正在使用C ++中的事件守护进程,我想使用成员函数回调。基本上,事件队列将收集守护程序持续服务的事件。有一个带有ID的基类Event结构,所有事件都是从它派生的。我希望为每个事件注册的方法在其签名中使用派生的事件类型。
struct Event
{
unsigned int eventId;
};
struct EventA : public Event
{
unsigned int x;
unsigned int y;
};
// and struct EventB, EventC (use your imagination...)
const unsigned int EVENT_A = 1;
const unsigned int EVENT_B = 2;
const unsigned int EVENT_C = 3;
class Foo
{
public:
void handlerMethod_A(const EventA& e);
void handlerMethod_B(const EventB& e);
};
class Bar
{
public:
void handlerMethod_C(const EventC& e);
};
然后守护进程允许这些类使用他们的'这个'来订阅他们的成员函数。指针。
class EventDaemon
{
public:
void serviceEvents();
template <class CallbackClass, class EventType>
void subscribe(
const unsigned int eventId,
CallbackClass* classInstancePtr,
void (CallbackClass::*funcPtr)(EventType));
private:
Queue<Event*> eventQueue_;
};
所以在这堂课之外你可以做点什么:
EventDaemon* ed = new EventDaemon();
Foo* foo = new Foo();
Bar* bar = new Bar();
ed->subscribe(EVENT_A, foo, Foo::handlerMethod_A);
ed->subscribe(EVENT_B, foo, Foo::handlerMethod_B);
ed->subscribe(EVENT_C, bar, Bar::handlerMethod_C);
EventDaemon循环将沿着
行void EventDaemon::serviceEvents()
{
while (true)
{
if (eventQueue_.empty())
{
// yield to other threads
}
else
{
// pop an event out of the FIFO queue
Event e* = eventQueue_.pop();
// somehow look up the callback info and use it
classInstancePtr->*funcPtr(reinterpret_cast<?*>(e));
}
}
}
所以我的问题是如何存储这个&#39;这个&#39;由事件ID在某种数组中的指针和成员函数指针。这样我就可以查看“类实际”课程。和&#39; funcPtr&#39;通过使用e-&gt; eventId和事件类型以及重新解释演员。
答案 0 :(得分:3)
你工作太辛苦了。使用增强功能:
http://www.boost.org/doc/libs/1_47_0/doc/html/function.html
无论您是否拥有某个对象,这些都有效。它们会增加编译时间。
请注意,每当遇到这些类型的问题,你知道许多人必须遇到同样的问题时,可能有一个简单的选项,如果它不在标准库中,它可能会有所提升。
为了回应尼克,我经常将助推功能对象投射到矢量等等中。
我发现,虽然boost函数对象可以保存对象引用,但是让它们这样做可能会导致对象生命周期的错误,最好让它们保存类对象的副本(但是你遇到了相同的bug)您尝试保存对您不一定控制其生命周期的对象实例的引用。模式:
class Foo
{
struct Member
{
// member variable definitions
};
shared_ptr<Member> m_; // the only real member variable
public:
// etc. including the all-important copy
// constructor and assignment operator and
// don't forget the member function that gets stuck into
// the boost function as a callback!
};
其中所有成员变量都保存在shared_ptr中可以获得良好的性能,并且您不必担心函数对象持有的对象的生命周期,因为您可以按值复制它们。线程代码(我现在似乎总是在编写)需要额外的东西,例如在Member中至少有一个boost mutex元素或其他一些方法来确保值不会被踩到。
答案 1 :(得分:1)
boost::function
[或者,如果您的系统支持它,std::function
]将会很好地保持this
指针,并且如果它不需要实际对象的额外好处没有必要。因此,void (SomeType::*)(EventA)
取代std::function<void(EventA)>
而不是std::bind
,而是根据需要调用subscribe(EVENT_A, std::bind(&foo::handleEventA, &foo, std::placeholders::_1));
。
EventA&
一个简单的包装函数可用于提供与您最初提出的相同的签名,并隐藏讨厌的占位符。
当然,您仍然会遇到每种事件类型都有自己签名的问题,并且需要确保使用正确的事件ID代码。在这两种情况下,您的基本事件类型都可以提供帮助。你的回调不需要接受Event&
;它可以在运行时接受dynamic_cast
和EventA
到struct Event {
virtual void ~Event() { }
virtual int ID() =0;
};
template<typename E>
struct EventHelper : Event {
virtual int ID() { return E::EventID; }
};
struct EventA : EventHelper<EventA> {
static const int EventID = 89;
};
。对于ID,直接查询类型。
Event*
现在,如果你有一个p->ID()
对象[当你去发送你的活动时],你可以EventA
获取相应的ID,如果你有EventA::EventID
类型[当您注册回调时],您可以std::function<void(const Event&)>
。
现在,无论您的实际活动类型是什么,您需要存储的所有内容都是int
和每个回调的关联void subscribe(int id, std::function<void(const Event&)> f) {
callbacks.insert(std::make_pair(id, f));
}
template<typename E>
void subscribe(std::function<void(const Event&)> f) {
subscribe(E::EventID, f);
}
template<typename O, typename E>
void subscribe(O* p, void (O::*f)(const Event&)) {
subscribe<E>(std::bind(f, p, std::placeholders::_1));
}
值。
dynamic_cast
您仍然遇到订阅时用户错误可能导致错误调用函数的问题。如果你在回调中正确使用了dynamic_cast
,这将在运行时被捕获,但编译时检查会很好。那么如果我们自动化template <class CallbackClass, class EventType>
void subscribe(CallbackClass* classInstancePtr, void (CallbackClass::*funcPtr)(EventType)) {
subscribe<EventType::EventID>([&](const Event& e) {
(classInstancePtr->*funcPtr)(dynamic_cast<const EventType&>(e));
});
}
呢?对于这一步,我将使用c ++ 11 lambdas,但它也可以使用各种方法在C ++ 03中实现。
{{1}}
所以现在我们已经完全回到原来的界面,你的回调接受他们将要处理的实际类型,但在内部你已经将它们全部压缩成一个共同的签名。
答案 2 :(得分:0)
好的,所以我完成了原始所需界面的实现。我正在看丹尼斯&#39;回答但最终导致了仿函数,我意识到我在寻找的是一个简单的多态解决方案。之前我没有掌握,我可以创建一个非模板化的基类,用于在矢量/数组中存储模板化类。我想这就是mheyman试图告诉我的......所以我道歉我没有马上得到它。只是为了澄清,虽然我真的在为自己的利益和知识寻找实施解决方案,而不仅仅是第三方图书馆来完成工作。所以我想我会寻找如何 Boost功能,而不仅仅是它们存在且非常棒。
如果有人仍然感兴趣,这里是我最终的重要部分(减去一些无关紧要的东西和错误检查):
class EventDaemon
{
public:
template <class CallbackClass, class EventType>
void subscribe(
const EventId eventId,
CallbackClass* callbackClassInstancePtr,
void (CallbackClass::*funcPtr)(const EventType&));
private:
EventFunctorBase* callbacks_[MAX_NUM_EVENTS];
};
template <class CallbackClass, class EventType>
void EventDaemon::subscribe(
const EventId eventId,
CallbackClass* callbackClassInstancePtr,
void (CallbackClass::*funcPtr)(const EventType&))
{
callbacks_[eventId] = new EventFunctor<CallbackClass,EventType>(callbackClassInstancePtr,funcPtr);
}
class EventFunctorBase
{
public:
EventFunctorBase();
virtual ~EventFunctorBase();
virtual void operator()(const Event& e)=0;
};
template <class CallbackClass, class EventType>
class EventFunctor : public EventFunctorBase
{
public:
EventFunctor(
CallbackClass* callbackClassInstancePtr,
void (CallbackClass::*funcPtr)(const EventType&));
virtual void operator()(const Event& e);
private:
CallbackClass* callbackClassInstancePtr_;
void (CallbackClass::*funcPtr_)(const EventType&);
};
template <class CallbackClass, class EventType>
EventFunctor<CallbackClass,EventType>::EventFunctor(
CallbackClass* callbackClassInstancePtr,
void (CallbackClass::*funcPtr)(const EventType&))
:
callbackClassInstancePtr_(callbackClassInstancePtr),
funcPtr_(funcPtr)
{
}
template <class CallbackClass, class EventType>
/*virtual*/ void EventFunctor<CallbackClass,EventType>::operator()(const Event& e)
{
(callbackClassInstancePtr_->*funcPtr_)(dynamic_cast<const EventType&>(e));
}
EventDaemon循环
while (true_)
{
if (eventQueue_->empty())
{
// yield to other threads
}
else
{
Event* e = eventQueue_.pop();
(*(callbacks_[e->ID]))(*e);
}
}
我在这里的最后一步是尝试删除开发人员为每个事件定义ID的必要性......当然,这可能会在本周晚些时候发布一个新帖子。