提问人:Leon Reucher 提问时间:10/12/2022 更新时间:10/12/2022 访问量:186
C++ 成员回调函数列表
C++ List of member callback functions
问:
我正在从C开发到STM32平台上的C++,根本找不到适合我的问题的解决方案。
请查看本文所附的简化示例代码。
#include <iostream>
#include <functional>
#include <list>
using namespace std;
class Pipeline {
public:
std::list<std::function<void(Pipeline*)>> handlers;
//add handler to list --> works fine
void addHandler(std::function<void(Pipeline*)> handler) {
this->handlers.push_front(handler);
}
void ethernetCallback(void) {
//handle received data and notify all callback subscriptions --> still works fine
// this callback function is normally sitting in a child class of Pipeline
int len = handlers.size();
for (auto const &handler : this->handlers) {
handler(this);
}
}
void removeHandler(std::function<void(Pipeline*)> handler) {
// Here starts the problem. I can not use handlers.remove(handler) here to
// unregister the callback function. I understood why I can't do that,
// but I don't know another way of coding the given situation.
}
};
class Engine {
public:
void callback(Pipeline *p) {
// Gets called when new data arrives
cout<<"I've been called.";
}
void assignPipelineToEngine(Pipeline *p) {
p->addHandler(std::bind(&Engine::callback, this, std::placeholders::_1));
}
};
int main()
{
Engine *e = new Engine();
Pipeline *p = new Pipeline();
e->assignPipelineToEngine(p);
// the ethernet callback function would be called by LWIP if new udp data is available
// calling from here for demo purposes only
p->ethernetCallback();
return 0;
}
这个想法是,当类“Pipeline”通过以太网接收新数据时,它会通过调用方法通知所有已注册的回调函数。回调函数存储在 std::list 中。到目前为止一切正常,但这种方法的问题是我无法从列表中删除回调函数,这是项目所必需的。 我知道为什么我不能简单地从列表中删除回调函数指针,但目前我不知道另一种方法。
也许任何人都可以给我一个提示,我可以在哪里寻找解决这个问题。我研究过的所有资源都没有真正显示我的具体情况。
提前感谢大家的支持!:)
答:
0赞
Quimby
10/12/2022
#1
也许您可以为每个处理程序附加一个 ID。非常粗略的变体将只使用地址作为 ID,如果每个实例最多有一个回调。this
#include <functional>
#include <iostream>
#include <list>
using namespace std;
class Pipeline {
public:
using ID_t = void *; // Or use integer-based one...
struct Handler {
std::function<void(Pipeline *)> callback;
ID_t id;
// Not necessary for emplace_front since C++20 due to agreggate ctor
// being considered.
Handler(std::function<void(Pipeline *)> callback, ID_t id)
: callback(std::move(callback)), id(id) {}
};
std::list<Handler> handlers;
// add handler to list --> works fine
void addHandler(std::function<void(Pipeline *)> handler, ID_t id) {
this->handlers.emplace_front(std::move(handler), id);
}
void ethernetCallback(void) {
// handle received data and notify all callback subscriptions --> still
// works fine
// this callback function is normally sitting in a child class of
// Pipeline
int len = handlers.size();
for (auto const &handler : this->handlers) {
handler.callback(this);
}
}
void removeHandler(ID_t id) {
handlers.remove_if([id = id](const Handler &h) { return h.id == id; });
}
};
class Engine {
public:
void callback(Pipeline *p) {
// Gets called when new data arrives
cout << "I've been called.";
}
void assignPipelineToEngine(Pipeline *p) {
//p->addHandler(std::bind(&Engine::callback, this, std::placeholders::_1), this);
//Or with a lambda
p->addHandler([this](Pipeline*p){this->callback(p);},this);
}
void removePipelineFromEngine(Pipeline *p) { p->removeHandler(this); }
};
int main() {
Engine *e = new Engine();
Pipeline *p = new Pipeline();
e->assignPipelineToEngine(p);
// the ethernet callback function would be called by LWIP if new udp data is
// available calling from here for demo purposes only
p->ethernetCallback();
return 0;
}
您也可以考虑而不是列出,不确定您的内存/性能受到的限制程度。std::map<ID_t,std::function<...>>
强制性:尽可能不要使用、使用或更好地使用自动存储。尽管在这种情况下,指针是合适的,因为由于捕获/绑定/ID 而需要稳定的地址。new
std::unique_ptr
e
this
std::functions
没有可比性,因为没有一个好的通用方法来定义这种比较。
评论
0赞
Leon Reucher
10/12/2022
非常感谢您的回答。您能向我解释一下“如果每个实例最多有一个回调”的限制吗?我已经使用多个引擎和管道测试了您的代码,它似乎工作正常。
1赞
Quimby
10/12/2022
@LeonReucher 如果您从单个对象注册了两个回调,它将不起作用。例如,复制行。因为这样就会有两个具有相同 ID(相同指针)的回调,并且只会删除第一个回调。Engine
p->addHandler
this
removeHandler
1赞
Miles Budnek
10/12/2022
#2
一种选择是返回某种标识符,该标识符稍后可以传递给 。例如:addHandler
removeHandler
class Pipeline {
public:
std::map<int, std::function<void(Pipeline*)>> handlers;
int nextId = 0;
//add handler to list --> works fine
void addHandler(std::function<void(Pipeline*)> handler) {
handlers[nextId++] = handler;
}
void ethernetCallback(void) {
for (auto const& entry : handlers) {
entry.second(this);
}
}
void removeHandler(int handlerToken) {
handlers.erase(handlerToken);
}
};
class Engine {
public:
void callback(Pipeline *p) {
// Gets called when new data arrives
cout<<"I've been called.";
}
void assignPipelineToEngine(Pipeline *p) {
handlerToken = p->addHandler(
std::bind(
&Engine::callback,
this,
std::placeholders::_1
)
);
}
void unregisterPipelineFromEngine(Pipeline *p) {
p->removeHandler(handlerToken);
}
private:
int handlerToken;
};
评论
0赞
Leon Reucher
10/12/2022
谢谢你的回答。我应该提到,可以而且将会有多个回调处理程序。所以使用令牌的想法是行不通的 - 对吗?
0赞
Miles Budnek
10/12/2022
@LeonReucher 当然可以,只需要保留一组令牌;他们只是 ;它可以随心所欲地跟踪它们。Engine
int
下一个:让事件不受保护是坏事吗?
评论
std::list
std::vector
vector
list
std::bind
p->addHandler([this](Pipeline *p) {callback(p);});
p->addHandler(std::bind(&Engine::callback, this, std::placeholders::_1));