今天講消息分發的一種編譯期實現法。
編程是一門非常依賴邏輯的學科,邏輯分為形式邏輯和非形式邏輯,編程就屬于形式邏輯。形式邏輯指的是用數學的方式去抽象地分析命題,它有一套嚴謹的標準和公理系統,對錯分明;而日常生活中使用的是非形式邏輯,它不存在標準和公理,也沒有絕對的對與錯。
根據哲學家大衛·休謨在《人性論》中對于觀念之間連接的分類,我們能夠把邏輯關系分成三大類:相似關系、因果關系、承接關系。相似關系表示兩個組件結構相同,去掉其中一個組件,也只會使功能不夠全面,并不會影響程序;因果關系表示兩個組件之間依賴性極強,沒有第一個組件,就沒有第二個組件,第二個組件依賴于第一個組件;承接關系表示兩個組件都是局部,只有組合起來,才能構成一個整體。
消息分發就屬于因果關系,我們需要依賴 A,去執行 B,沒有 A 就沒有 B。同樣屬于因果關系的術語還有邏輯分派、模式匹配、定制點的表示方式等等,它們本質都是在描述一類東西,只是有時候側重點不同。
條件關系也屬于因果關系的范疇,是編程中邏輯最重的關系。試想沒有if else
,你還能寫出多少程序?世界是復雜的,問題也是復雜的,因果關系必不可少。
消息分發,或稱邏輯分派,就是一種簡化條件關系表達方式的技術。它適用于存在大量因果的情境,此時若是使用原始的if else
,則無法適應動態發展的世界。
C++ 中,最典型、也非常有用的一種方式就是采用map
,因作為 key,果作為 value,因是標識符,果是回調函數。由于這種方式發生于運行期,所以也稱為動態消息分發。
本文要講的,是 C++20 才得以實現的另外一種方式,發生于編譯期的靜態消息分發技術。
稱為消息分發,一般是在網絡通信的情境下。正常情境下,程序是順序執行的,所以完全可以使用if else
來實現因果邏輯,因為組件與組件之間距離較近,屬于同一模塊;而網絡情境下,一個組件可以瞬間跳躍到距離非常遠的另一個組件,這兩個組件甚至不在同一臺設備上,一臺設備可能在上海,另一臺在北京,此時如何讓這兩個組件進行溝通?也就是說,A 組件里面的某個函數執行條件不滿足,如何簡單地跳到 B、C、D、E…… 這些組件的某個函數中去處理?這種遠距離的程序因果邏輯,通過消息分發組件能夠非常絲滑地表示。
消息分發的標識符一般采用字符串表示,到了 C++20 支持 string literal NTTP 才得以在編譯期實現一套可用的相關組件。
因此首先,我們得實現一個 string literal 以在編譯期使用。
1template<std::size_tN> 2structstring_literal{ 3//strisareferencetoanarrayNofconstantchar 4constexprstring_literal(charconst(&str)[N]){ 5std::copy_n(str,N,value); 6} 7 8charvalue[N]; 9};
通過這種方式,我們定義了編譯期能夠使用的字符串組件,它能夠直接當作模板參數使用。
然后,定義我們的分發器。
1template
代碼非常精簡,分發器可以包含很多「因」,使用可變模板參數
Cs
進行表示。如果得到一個具體的「因」,我們需要找到對應的「果」,因此免不了遍歷 Cs
,借助 Fold expressions,一行代碼優雅地搞定。通過 execute_if
來查找是否存在對應的「因」,也就是對比字符串是否相等,查找到則調用相應的「果」,也就是具體的處理函數 handler()
。接著,需要定義一個默認的因果,即如果沒有定義相應的處理函數時,所調用的一個默認處理函數。
1//defaultimplementation 2template
因為是模板參數,所以它能夠處理所有的「因」。
通過特化,我們能夠在任何地方,定義任何因果。比如:
1//opt-incustomizationpoints 2template<>inlineconstexprautohandler<"cause1">=[]{std::cout<"customizationpointseffect1 ";}; 3template<>inlineconstexprautohandler<"cause2">=[]{std::cout<"customizationpointseffect2 ";};
默認版本和定制版本之間是相似關系,即便不提供定制版本,也會不影響程序的功能。
由于特化更加特殊,所以決議時會首先考慮這些因果對。但是,此時有巨大的重復,我們通過宏來自動生成重復代碼:
1#define_(name)template<>inlineconstexprautohandler<#name> 2 3//opt-incustomizationpoints 4_(cause1)=[]{std::cout<"customizationpointseffect1 ";}; 5_(cause2)=[]{std::cout<"customizationpointseffect2 ";};
現在定制起來就更加方便、簡潔。 最后,具體使用。
1intmain(){ 2constexprstring_literalcause_1{"cause1"}; 3constexprdispatcher
相比動態消息分發,這種方式有兩個巨大的優勢,其一是編譯期,其二是定制時可以在任何地方。動態消息分發一般需要調用
dispatch.add_handler(cause, effect)
,因為是成員函數,所以限制了定制地方,必須得在對象所在模塊,而靜態消息分發這種全局定義特化的方式,則沒有這種限制。目前其實還存在兩個問題,第一是
C == cause
并沒有相應的比較操作符,第二是 dispatch.execute(cause_1)
并不能直接傳遞,因為 char const*
和 string_literal
畢竟不是同一種類型。可以通過添加運算符重載和隱式轉換來解決:1template<std::size_tN> 2structstring_literal{ 3//... 4 5friendbooloperator==(string_literalconst&s,charconst*cause){ 6returnstd::strncmp(s.value,cause,N)==0; 7} 8 9operatorcharconst*()const{ 10returnvalue; 11} 12 13//... 14}; 現在以上靜態消息分發組件就能夠正常使用了。完整的代碼如下:
1template<std::size_tN> 2structstring_literal{ 3constexprstring_literal(charconst(&str)[N]){ 4std::copy_n(str,N,value); 5} 6 7friendbooloperator==(string_literalconst&s,charconst*cause){ 8returnstd::strncmp(s.value,cause,N)==0; 9} 10 11operatorcharconst*()const{ 12returnvalue; 13} 14 15charvalue[N]; 16}; 17 18//defaultimplementation 19template
短短數十行代碼,便實現了一個威力強大的靜態消息分發組件,This is modern C++。
-
編程
+關注
關注
88文章
3639瀏覽量
94031 -
字符串
+關注
關注
1文章
585瀏覽量
20612 -
C++
+關注
關注
22文章
2114瀏覽量
73885 -
代碼
+關注
關注
30文章
4835瀏覽量
69117 -
編譯
+關注
關注
0文章
662瀏覽量
33066
原文標題:編譯期消息分發?C++20 已能優雅實現!
文章出處:【微信號:CPP開發者,微信公眾號:CPP開發者】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論