我正在構建一個發布-訂閱類(稱為SystermInterface),它負責從其實體接收更新,并將它們發布給訂閱者。
添加訂閱者回呼函式是微不足道的并且沒有問題,但洗掉它會產生錯誤,因為std::function<()>在 C 中沒有可比性。
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
假設SystemInterface該類每個實體 (ID) 都有一個欄位屬性(由 管理ServiceCore,并SystemInterface通過構造ServiceCore子實體提供)。
在我看來,第一個解決方案很簡潔并且可以作業,但它需要在訂閱者處處理,這是我不太喜歡的。
第二種解決方案會使我的實作更加復雜,我的實作如下所示:
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 塊將有效負載定向到預期的例程,這將隨著訂閱的增加而增加。
我正在尋找的是最佳可用選項的建議。
uj5u.com熱心網友回復:
關于您提出的解決方案:
- 那會奏效。呼叫者可以很容易:
subscribe()創建shared_ptr和相應的weak_ptr物件,并讓它回傳shared_ptr. - 然后呼叫者不能丟失密鑰。在某種程度上,這與上述類似。
- 這當然不那么通用,然后您就不能再擁有(相當于)捕獲。
- 你不能:沒有辦法獲取存盤在a中的函式的地址
std::function。你可以在&f里面做,subscribe()但這只會給你區域變數的地址,f一旦你回傳,它就會超出范圍。 - 這有效,并且在某種程度上類似于 1 和 2,盡管現在“密鑰”是由呼叫者提供的。
選項 1、2 和 5 類似,因為其中存盤了一些其他資料,這些資料subs指的是實際的std::function:a std::shared_ptr、鍵或指向基類的指標。我將在這里介紹選項 6,它在精神上有點相似,但避免存盤任何額外的資料:
- 直接存盤a
std::function<void()>,并回傳存盤它的向量中的索引。洗掉專案時,不要這樣做std::remove(),只需將其設定為std::nullptr. 下次subscribe()呼叫時,它會檢查向量中是否有空元素并重用它:
std::vector<std::function<void()> subs;
std::size_t subscribe(std::function<void()> f) {
if (auto it = std::find(subs.begin(), subs.end(), std::nullptr); it != subs.end()) {
*it = f;
return std::distance(subs.begin(), it);
} else {
subs.push_back(f);
return subs.size() - 1;
}
}
void unsubscribe(std::size_t index) {
subs[index] = std::nullptr;
}
實際呼叫存盤在其中的函式的代碼subs現在當然必須首先檢查std::nullptrs. 上面的作業是因為std::nullptr被視為“空”函式,并且有一個operator==()可以檢查 a 的多載std::function,std::nullptr從而使std::find()作業正常。
如上所示,選項 6 的一個缺點是 astd::size_t是一種相當通用的型別。為了讓它更安全,你可以把它包在一個class SubscriptionHandle或類似的東西里。
至于最佳解決方案:選項 1 是相當重量級的。選項 2 和 5 非常合理,但我認為 6 是最有效的。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/516296.html
標籤:c 11标准函数
