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