std::function
存储可复制的可调用对象。它不要求其内容是 ==
可比较的,而 lambda 表达式不是 ==
可比较的。
您可以提取存储的可调用对象的 typeid
,如果它们不匹配,则假设为 false,添加类型抹除特性,让您可以在各种类型上存储 ==
并在类型相等时进行分派,但这样您就不支持 lambda 表达式,因为 lambda 表达式不支持 ==
。
如果您说“没有 lambda 表达式”,则可以这样做,或者如果您说“没有带状态的 lambda 表达式”。我稍后会介绍这种可能性,但首先建议尝试以下方法:
using std::shared_ptr<void> token;
template<class...Message>
struct broadcaster {
using listener = std::function<void(Message...)>;
using splistener = std::shared_ptr<listener>;
using wplistener = std::weak_ptr<listener>;
token listen( listener f ) {
auto sf = std::make_shared<listener>(std::move(f));
listeners.push_back(sf);
return sf;
}
std::size_t operator()( Message... message ) const {
targets.erase( std::remove_if( begin(targets), end(targets),
[](auto&& ptr) { return !ptr.lock(); }
), end(targets) );
auto tmp = targets;
for (auto wf : tmp) {
if(auto sf = wf.lock()) {
sf( message... );
}
}
}
private:
mutable std::vector<wplistener> targets;
};
在客户端中,跟踪您正在侦听的令牌。当客户端对象消失时,它将自动从其侦听的每个广播器中取消注册。只需使用
std :: vector<token>
并将其标记放入其中即可。
如果您有更复杂的逻辑,其中侦听不应绑定到侦听器的生命周期,则必须单独存储所述令牌。
这假定广播大致与注册/注销同样频繁或更频繁。如果广播非常罕见且注册/注销非常常见(例如百万次以上),则侦听器的弱指针可能会积累。您可以在
listen
中添加一个测试以定期清除陈旧的侦听器。
现在,我们可以进行“无绑定状态的lambda表达式”。然后我们可以单独绑定状态,在那里对
==
操作进行类型擦除,就完成了。
state( some_state... )
([](some_state&&...){
return [&](some_args...){
});
像上面这样的结构将让你返回一个函数对象,它的行为很像lambda,但是在其上有一个明智的
==
操作。
template<class F, class...Args>
struct bound_state {
std::tuple<Args...> state;
F f;
friend bool operator==( bound_state const& lhs, bound_state const& rhs ) {
return lhs.state==rhs.state;
}
template<class...Ts>
decltype(auto) operator()(Ts&&...ts){
return std::apply(f, state)( std::forward<Ts>(ts)... );
}
};
template<class...Args>
auto state( Args&&... args ) {
auto tup = std::make_tuple( std::forward<Args>(args)... );
return [tup=std::move(tup)](auto&& f) {
return bound_state<std::decay_t<decltype(f)>, std::decay_t<Args>...>{
tup, decltype(f)(f)
};
};
}
或者类似的东西。
接下来,我们创建了一个派生类型的 std::function
。当它从一个类型构造时,它将为它存储一个类型擦除的 ==
(可以是全局或本地位置)(来自一对 std::function
)。
它重写了 ==
,首先检查 typeid 是否相同,如果相同,则调用两个元素上的类型擦除的 ==
。
template<class Sig>
struct func_with_equal : std::function<Sig> {
using Base = std::function<Sig>;
using Base::operator();
using equality = bool( func_with_equal const&, func_with_equal const& );
equality* pequal = nullptr;
template<class F>
static equality* erase_equality() {
return [](func_with_equal const& lhs, func_with_equal const&rhs)->bool {
assert( lhs.target_type() == rhs.target_type() );
assert( lhs.target_type() == typeid(F) );
return *lhs.target<F>() == *rhs.target<F>();
};
}
friend bool operator==( func_with_equal const& lhs, func_with_equal const& rhs ) {
if (!lhs && !rhs) return true;
if (!lhs || !rhs) return false;
if (lhs.target_type() != rhs.target_type()) return false;
return lhs.pequal( lhs, rhs );
}
};
这只完成了一半,但我希望你能理解。它很复杂且错综复杂,在每个注册回调点都需要额外的工作。
std::function
是一个“可调用对象”(可以复制的可调用对象),而不是你认为的模型。其次,你的系统似乎需要将单个消息传递给回调函数(或某种动态或不安全的转换),这似乎不太理想。第三,如果你注册了一个回调函数,那么当回调函数所引用的对象生命周期结束时会发生什么? - Yakk - Adam Nevraumont