我正在尝试创建一个类似于c#系统的c ++事件系统。我需要能够存储任何类型的函数,并在适当的时候使用正确的参数调用它们。
我能得到的最接近的是std :: function,bind和placeholders,但这是我的问题。
void Func()
{
std::cout << "Notified" << std::endl;
}
void FuncWithParam(const std::string& str)
{
std::cout << str << std::endl;
}
std::function<void()> fn = std::bind(Func); // this works
std::function<void()> fn = std::bind(FuncWithParam, "Hello there"); // this works also
std::function<void()> fn = std::bind(FuncWithParam, _1); // but this doesn't
实际上是否可以在单个std :: function中存储任何类型的签名?或者我是否需要求助于更复杂的解决方案。
这是一个简单的C ++广播公司:
using token = std::shared_ptr<void>;
template<class...Ts>
struct broadcaster {
using listen = std::function<void(Ts...)>;
using sp_listen = std::shared_ptr<listen>;
using wp_listen = std::weak_ptr<listen>;
token attach( listen l ) {
return attach( std::make_shared<listen>(std::move(l)) );
}
token attach( sp_listen sp ) {
listeners.push_back(sp);
return sp;
}
void operator()(Ts...ts)const {
listeners.erase(
std::remove_if( begin(listeners), end(listeners),
[](auto&& wp){return !(bool)wp.lock();}
),
end(listeners)
);
auto tmp = listeners;
for (auto&& l : tmp) {
if (auto pf = l.lock()) {
(*pf)(ts...);
}
}
}
private:
mutable std::vector<wp_listen> listeners;
};
要听它,你.attach
并传递一个函数来调用。 attach
返回一个token
,只要该令牌(或其副本)继续存在,就会调用该函数。
要调用该消息,请在()
上调用broadcaster
。
下次你调用广播公司时会回收死回调的记忆;间接拥有的资源得到更快的清理。
如果您在当前正在广播时注册一个监听器,它将无法获得当前广播。
您可以添加std::mutex
以使其可以同时从多个线程使用,或者从外部同步。如果你在内部同步,我不会在for(auto&&
中运行()
循环时保持互斥锁,以避免重入问题。
使用示例:
struct location {
int x, y;
};
struct button {
broadcaster< location > mouse_click;
broadcaster<> mouse_enter;
broadcaster<> mouse_leave;
};
struct dancer {
std::vector<token> listen_tokens;
dancer( button& b ) {
listen_tokens.push_back( b.mouse_enter.attach([this]{ dance(); } ) );
listen_tokens.push_back( b.mouse_leave.attach([this]{ end_dance(); } ) );
listen_tokens.push_back( b.mouse_click.attach(
[this](location l){
pose(l.x, l.y);
}
) );
}
void dance() const {
std::cout << "start dancing\n";
}
void pose( int x, int y ) const {
std::cout << "struck a pose at " << x << ", " << y << "\n";
}
void end_dance() const {
std::cout << "end dancing\n";
}
};
请注意,没有使用virtual
方法。唯一的多态性是基于std::function
的类型擦除。
听力对象必须跟踪明确监听的生命周期(通过保持token
活着),如果他们希望能够注册到特定的广播公司,他们必须自己保持这种关联。
如果广播公司首先离开,那就没问题了。如果听众离开,只要他们的令牌在他们之前,一切都可以。附加确实会导致动态分配以存储令牌(以及移动到侦听器的副本中),但只有一个。
这是一种与在C#中使用的方法不同的方法,因为它依赖于RAII,因为它的核心不是OO,但仍然是多态的。