从向量图中查找并删除 std::function 个对象
Find and delete a std::function object from a map of vectors
我目前正在尝试为我的游戏引擎实现一个消息系统。它使用以下形式的函数回调:
typedef std::function<void(const Message &)> Callback;
它维护着一个消息列表:
mutable std::vector<std::unique_ptr<Message>> messageList;
以及签名的回调字典:
mutable std::map<std::string, std::vector<Callback>> callbackDictionary;
用于调用特定消息类型的所有回调'bound'。调用回调函数时,将传递相应的消息。到目前为止一切顺利。
为了更好地理解,这里是订阅方法,它允许用户添加一个函数方法,该方法会为订阅类型的每条消息调用。
void Messenger::Subscribe(Message::Type type, Callback callbackFunction)
const
{
callbackDictionary[type].push_back(callbackFunction);
}
这是一个如何使用的示例(通过可点击组件 class)
messageBus.Subscribe("Message/Click", [this](const Message & message) {this->OnClick(static_cast<const ClickMessage &>(message)); });
现在我的问题是:
我想实现一个取消订阅方法,该方法在字典的向量中找到 function/method 并将其删除。请注意,该函数可以订阅多种消息类型。我在想这样的事情:
void Messenger::UnSubscribe(Callback callbackFunction)
{
for (auto & pair : callbackDictionary)
{
auto & functionList = pair.second;
for (auto function : functionList)
{
if (function == callbackFunction)
functionList.erase(std::remove(functionList.begin(), functionList.end(), function), functionList.end());
// I am aware of issues that may appear from looping deleting while looping
}
}
}
但是,函数对象的比较运算符 (==) 似乎未定义。我想不出一个简单的方法来解决这个问题。所以任何想法都非常受欢迎。我只是想根据经验避免某种 id 系统原因,这可能是一项繁琐的管理工作。尤其是在整个程序中随处可以添加各种函数和成员的情况下。
std::function
存储可以复制的可调用对象。它不要求其内容 ==
可比较,并且 lambda 不 ==
可比较。
您可以提取存储的可调用对象的 typeid
,如果它们不匹配则假设为 false,添加类型擦除功能,让您可以在各种类型上存储 ==
并在类型出现时分派是相等的,但是你不支持 lambda,因为 lambda 不支持 ==
.
说"no lambdas"就可以,说"no lambdas with bound state"就可以。稍后我会讨论这种可能性,但首先我建议尝试这样做:
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); // as weak ptr
return sf; // as token
}
std::size_t operator()( Message... message ) const {
// remove stale targets:
targets.erase( std::remove_if( begin(targets), end(targets),
[](auto&& ptr) { return !ptr.lock(); }
), end(targets) );
auto tmp = targets; // copy, for reentrancy
for (auto wf : tmp) {
if(auto sf = wf.lock()) {
sf( message... );
}
}
}
private:
mutable std::vector<wplistener> targets;
};
在客户端中,跟踪您正在收听的令牌。当客户端对象消失时,它会自动从它正在收听的每个广播者中注销。只需使用 std::vector<token>
并在其中填充您的代币。
如果您有更复杂的逻辑,其中监听不应绑定到监听器的生命周期,那么您必须单独存储所述标记。
这假设广播发生的频率与 registration/deregistration 大致相同,或者更多。如果广播极其罕见并且 registration/deregistration 非常常见(比常见的多一百万倍),则可以建立指向听众的弱指针。您可以在 listen
中添加一个测试以定期清理陈旧的侦听器。
现在,我们可以做一个 "no lambdas with bound state"。然后我们可以单独绑定state,在那里输入erase==
操作,bob就是你大爷
state( some_state... )
([](some_state&&...){
return [&](some_args...){
/* code */
});
像上面这样的构造会让你 return 一个函数对象,它的行为很像 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
s)。
它会覆盖 ==
,首先检查 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>();
};
}
// on construction, store `erase_equality<F>()` into `pequal`.
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 中没有相等运算符。
可以在旧 tr1::function
N1667:
中找到基本原理
operator==
is unimplementable for tr1::function
within the C++ language, because we do not have a reliable way to detect if a given type T
is Equality Comparable without user assistance
另请注意,您正在按值传递 std::function<void(const Message &)>
。这意味着您也不能只比较它们的地址(这并不是一个好的解决方案,因为 std::function
很容易复制)。
解决方案一:
使用一些用户提供的 key 和 std::function 作为 value
并将它们存储在 map
而不是 vector
.
std::map<std::string, std::map<std::string, Callback>> callbackDictionary;
. . .
void Messenger::Subscribe(Message::Type type, const std::string& cbID, Callback cb);
void Messenger::UnSubscribe(const std::string& cbID);
方案二:
使用 weak_ptr
跟踪回调。
std::map<std::string, std::vector<std::weak_ptr<Callback>> callbackDictionary;
. . .
void Messenger::Subscribe(Message::Type type, std::shared_ptr<Callback> cb);
根本不需要UnSubscribe
!您可以在 weak_ptr
变为 nullptr
后立即自动取消订阅回调。先决条件是 listener 必须通过 shared_ptr
.
使回调保持活动状态
我目前正在尝试为我的游戏引擎实现一个消息系统。它使用以下形式的函数回调:
typedef std::function<void(const Message &)> Callback;
它维护着一个消息列表:
mutable std::vector<std::unique_ptr<Message>> messageList;
以及签名的回调字典:
mutable std::map<std::string, std::vector<Callback>> callbackDictionary;
用于调用特定消息类型的所有回调'bound'。调用回调函数时,将传递相应的消息。到目前为止一切顺利。
为了更好地理解,这里是订阅方法,它允许用户添加一个函数方法,该方法会为订阅类型的每条消息调用。
void Messenger::Subscribe(Message::Type type, Callback callbackFunction)
const
{
callbackDictionary[type].push_back(callbackFunction);
}
这是一个如何使用的示例(通过可点击组件 class)
messageBus.Subscribe("Message/Click", [this](const Message & message) {this->OnClick(static_cast<const ClickMessage &>(message)); });
现在我的问题是:
我想实现一个取消订阅方法,该方法在字典的向量中找到 function/method 并将其删除。请注意,该函数可以订阅多种消息类型。我在想这样的事情:
void Messenger::UnSubscribe(Callback callbackFunction)
{
for (auto & pair : callbackDictionary)
{
auto & functionList = pair.second;
for (auto function : functionList)
{
if (function == callbackFunction)
functionList.erase(std::remove(functionList.begin(), functionList.end(), function), functionList.end());
// I am aware of issues that may appear from looping deleting while looping
}
}
}
但是,函数对象的比较运算符 (==) 似乎未定义。我想不出一个简单的方法来解决这个问题。所以任何想法都非常受欢迎。我只是想根据经验避免某种 id 系统原因,这可能是一项繁琐的管理工作。尤其是在整个程序中随处可以添加各种函数和成员的情况下。
std::function
存储可以复制的可调用对象。它不要求其内容 ==
可比较,并且 lambda 不 ==
可比较。
您可以提取存储的可调用对象的 typeid
,如果它们不匹配则假设为 false,添加类型擦除功能,让您可以在各种类型上存储 ==
并在类型出现时分派是相等的,但是你不支持 lambda,因为 lambda 不支持 ==
.
说"no lambdas"就可以,说"no lambdas with bound state"就可以。稍后我会讨论这种可能性,但首先我建议尝试这样做:
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); // as weak ptr
return sf; // as token
}
std::size_t operator()( Message... message ) const {
// remove stale targets:
targets.erase( std::remove_if( begin(targets), end(targets),
[](auto&& ptr) { return !ptr.lock(); }
), end(targets) );
auto tmp = targets; // copy, for reentrancy
for (auto wf : tmp) {
if(auto sf = wf.lock()) {
sf( message... );
}
}
}
private:
mutable std::vector<wplistener> targets;
};
在客户端中,跟踪您正在收听的令牌。当客户端对象消失时,它会自动从它正在收听的每个广播者中注销。只需使用 std::vector<token>
并在其中填充您的代币。
如果您有更复杂的逻辑,其中监听不应绑定到监听器的生命周期,那么您必须单独存储所述标记。
这假设广播发生的频率与 registration/deregistration 大致相同,或者更多。如果广播极其罕见并且 registration/deregistration 非常常见(比常见的多一百万倍),则可以建立指向听众的弱指针。您可以在 listen
中添加一个测试以定期清理陈旧的侦听器。
现在,我们可以做一个 "no lambdas with bound state"。然后我们可以单独绑定state,在那里输入erase==
操作,bob就是你大爷
state( some_state... )
([](some_state&&...){
return [&](some_args...){
/* code */
});
像上面这样的构造会让你 return 一个函数对象,它的行为很像 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
s)。
它会覆盖 ==
,首先检查 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>();
};
}
// on construction, store `erase_equality<F>()` into `pequal`.
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 中没有相等运算符。
可以在旧 tr1::function
N1667:
operator==
is unimplementable fortr1::function
within the C++ language, because we do not have a reliable way to detect if a given typeT
is Equality Comparable without user assistance
另请注意,您正在按值传递 std::function<void(const Message &)>
。这意味着您也不能只比较它们的地址(这并不是一个好的解决方案,因为 std::function
很容易复制)。
解决方案一:
使用一些用户提供的 key 和 std::function 作为 value
并将它们存储在 map
而不是 vector
.
std::map<std::string, std::map<std::string, Callback>> callbackDictionary;
. . .
void Messenger::Subscribe(Message::Type type, const std::string& cbID, Callback cb);
void Messenger::UnSubscribe(const std::string& cbID);
方案二:
使用 weak_ptr
跟踪回调。
std::map<std::string, std::vector<std::weak_ptr<Callback>> callbackDictionary;
. . .
void Messenger::Subscribe(Message::Type type, std::shared_ptr<Callback> cb);
根本不需要UnSubscribe
!您可以在 weak_ptr
变为 nullptr
后立即自动取消订阅回调。先决条件是 listener 必须通过 shared_ptr
.