我正在构建一个发布-订阅类(称为 SystermInterface
),它负责接收其实例的更新,并将其发布给订阅者。
添加订阅者回调函数很容易且没有问题,但是删除它会产生错误,因为在C++中std::function<()>
不可比较。
std::vector<std::function<void()> subs;
void subscribe(std::function<void()> f)
{
subs.push_back(f);
}
void unsubscribe(std::function<void()> f)
{
std::remove(subs.begin(), subs.end(), f); // Error
}
我已经得出了五个解决此错误的方案:
- 使用weak_ptr注册函数,订阅者必须保持返回的shared_ptr的存活状态。
示例解决方案在此链接。 - 不要在向量中注册,通过一个自定义键将回调函数映射到回调函数特定的唯一值上。
示例解决方案在此链接。 - 使用函数指针向��。例子
- 使回调函数可比较,利用地址。参考。
- 使用接口类(父类)来调用虚函数。
在我的设计中,所有意图类都继承了一个名为ServiceCore
的父类,因此不需要注册回调函数,只需要在向量中注册ServiceCore
引用即可。
考虑到每个实例(ID)都有一个字段属性的SystemInterface
类(由ServiceCore
管理,并通过构造ServiceCore
子实例提供给SystemInterface
)。
在我的看法中,第一种解决方案很清晰且可行,但需要订阅者处理,这是我不喜欢的事情。
第二个解决方案会使我的实现更加复杂,其中我的实现如下:
using namespace std;
enum INFO_SUB_IMPORTANCE : uint8_t
{
INFO_SUB_PRIMARY, // Only gets the important updates.
INFO_SUB_COMPLEMENTARY, // Gets more.
INFO_SUB_ALL // Gets all updates
};
using CBF = function<void(string,string)>;
using INFO_SUBTREE = map<INFO_SUB_IMPORTANCE, vector<CBF>>;
using REQINF_SUBS = map<string, INFO_SUBTREE>; // It's keyed by an iterator, explaining it goes out of the question scope.
using INFSRC_SUBS = map<string, INFO_SUBTREE>;
using WILD_SUBS = INFO_SUBTREE;
REQINF_SUBS infoSubrs;
INFSRC_SUBS sourceSubrs;
WILD_SUBS wildSubrs;
void subscribeInfo(string info, INFO_SUB_IMPORTANCE imp, CBF f) {
infoSubrs[info][imp].push_back(f);
}
void subscribeSource(string source, INFO_SUB_IMPORTANCE imp, CBF f) {
sourceSubrs[source][imp].push_back(f);
}
void subscribeWild(INFO_SUB_IMPORTANCE imp, CBF f) {
wildSubrs[imp].push_back(f);
}
第二种解决方案需要 INFO_SUBTREE 成为一个扩展映射,但可以使用 ID 作为键:
using KEY_T = uint32_t; // or string...
using INFO_SUBTREE = map<INFO_SUB_IMPORTANCE, map<KEY_T,CBF>>;
对于第三种解决方案,我不知道使用函数指针所限制的内容以及第四个解决方案的后果。
第五种解决方案将消除处理CBF的目的,但在订阅者端会更加复杂,需要一个订阅者重写虚函数并在一个地方接收所有更新,进而需要根据消息ID进行过滤,并使用多个if/else块将有效负载定向到预期的例程中,这将随着订阅增加而增加。
我正在寻求关于最佳可用选项的建议。
SystemInterface
实例已经在映射中注册了订阅,因此我将拥有索引作为值,而不是cbf
。明天将尝试并回复任何评论。 - Hamza Hajeirf.target<void()>()
的操作:它的类型将不匹配void()
的类型。之所以这样做的原因之一是 lambda 可能具有与其关联的其他数据(捕获),因此它与简单函数指针不同。 - G. Sliepencbf
重复项(这样回调函数将只被调用一次); 其背后的原因是订阅者可以使用具有“PRIMARY”重要性的整个源(甚至是通配符)进行订阅,并同时使用具有“ALL”重要性的信息分配相同的函数,以确保他每次更新仅接收到一个呼叫,那么什么可以限制我利用回调函数地址 (解决方案 4) 来删除重复项? - Hamza Hajeir