如何优雅地实现 C 编译期静态反射
部門請來了軟件專家袁英杰咨詢師指導我們軟件開發,從中我也學到了很多姿勢,在此記錄下來寶貴的經驗。蘋果的 mbp 品控真是差勁,寫這個東西把?LShift 鍵?按壞了,真是難受。
反射能做什么
最近和大師聊軟件設計,其中一個點是關于反射,反射最大的作用就是序列化、解序列化一個結構體,然后就能夠在各個模塊之間進行通信交互,不管是跨進程也好,還是跨機器也好,都缺不了反射這個功能,這也是 OO 世界對象交互的載體。
不然就需要人工手寫一堆序列化、反序列代碼,不僅代碼難看,而且工作量大,容易出錯。印象最深的一個例子是,大師在一個電信項目,模塊之間通過 TLV 格式的消息進行通信,而這些 TLV 格式也是內部實現的,還不是標準的,然后大師定義了一套機制,只需要統一聲明一次元數據的信息,然后通過?include?不同頭文件,就能對同一個元數據進行不同的解釋,比如序列化、解序列到數據庫,序列化、解序列到網絡,這也是預編譯多態技術,僅用?C 98?的特性就能做到。
舉一個直觀一點的例子,比如打印一個結構體內容(其實就是把結構體轉換成字符串):
struct?Point?{????double?x;????double?y;};Point?p?{?1,?2?};那么你可能會這樣寫:
printf("Point?x?=?%d?y?=?%d",?p.x,?p.y);如果有成千上百個結構體,對應的打印函數(序列化到字符串)也就成千上百個,如果利用反射手段,只需要寫一次,就能給所有反射對象自動生成打印函數(轉換)代碼。
引子
后來我在 C 社區看到一個討論,說 C 20?在元編程方面提供了很多便利,其中最大的遍歷就是 if-constexpr,再也不用模式匹配寫一堆enable_if 了,然后題主給了一個例子,用 C 20?的模板元求結構體的字段數量,代碼如下:
struct AnyType { ? ?template <typename T> ? ?operator T();}; template <typename T>consteval size_t CountMember(auto&&... Args) { ? ?if constexpr (! requires { T{ Args... }; }) { // (1) ? ? ? ?return sizeof...(Args) - 1; ? ?} else { ? ? ? ?return CountMember(Args..., AnyType{}); // (2) ? ?}} int main(int argc, char** argv) { ? ?struct Test { int a; int b; int c; int d; }; ? ?printf("%zu\n", CountMember());}看到這坨代碼,我愣了一會,然后問大師這個求結構體字段數量是怎么做到呢?C 目前最大缺陷是缺少靜態反射能力(這里指的是語言層面提供的靜態反射信息,C 23估計會落地),應該很難做到的,分析了一會,終于看懂了,太巧妙了:
1. AnyType聲明了類型轉換操作符(《C Modern design》書中的術語是稻草人函數),可以轉換成任意類型。
2. 分支 (2) 通過不斷構造所求類型 T = Test,當無法構造時(1),也就是輸入的參數過多,這時候參數個數 - 1就是字段個數。
那么只能 C 20?才能做到么?這里主要用到了?C 17?的if-constexpr特性,C 11可以通過?enable-if?做到,而最主要的是那個?requires,C 20?才支持?concept,C 17?都無法做到。
然后我思考了一下,類型構造,《C Modern design》這本書講過,用 sizeof 做類型推導,給的一個例子是判斷一個類是否是另一個類的基類,僅通過 C 98 實現。
C 11 編譯期有有兩大神器:sizeof? ?decltype,然后用這兩者就能實現同樣的功能,這里我用?decltype?來解決上述的?concept?問題:
template <typename T, typename = void, typename ...Ts>struct CountMember { ? ?constexpr static size_t value = sizeof...(Ts) - 1;}; template <typename T, typename ...Ts>struct CountMember, Ts...> { ? ?constexpr static size_t value = CountMembervoid, Ts..., AnyType>::value;}; int main(int argc, char** argv) { ? ?struct Test { int a; int b; int c; int d; }; ? ?printf("%zu\n", CountMember::value);}同樣兩種情況,用 decltype(T{Ts{}...})?來判斷是否能夠構造對象 T。
如何求宏的可變參數個數?
其實這個問題價值不大,而且強依賴平凡構造函數,最大價值在后面的討論,大師給我出了一道題,如何求宏的可變參數個數?雖然一時半會寫不出來,但是之前還是看過一些框架代碼的,最終實現方式如下:
#define GET_NTH_ARG( ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?\ ? ?_1, ?_2, ?_3, ?_4, ?_5, ?_6, ?_7, ?_8, ?_9, ?_10, _11, _12, _13, _14, _15, _16, ? ? ? ? \ ? ?_17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, ? ? ? ? \ ? ?_33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, ? ? ? ? \ ? ?_49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, n, ...) n #define GET_ARG_COUNT(...) GET_NTH_ARG(__VA_ARGS__, ? ? ? ? ? ? ? ? ? ? \ ? ? ? ?64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, \ ? ? ? ?48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, \ ? ? ? ?32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, \ ? ? ? ?16, 15, 14, 13, 12, 11, 10, 9, ?8, ?7, ?6, ?5, ?4, ?3, ?2, ?1)GET_ARG_COUNT(a, b, c)展開后,會調用GET_NTH_ARG,然后得到GET_NTH_ARG(a, b, c, 64, 63, ..., 3, 2, 1) 3,從而得到最終長度 3,進一步延伸,這個宏有什么作用呢?那就是對結構體進行反射,用宏提供結構體的元數據信息,從而生成一些類型信息代碼。
結合之前看到的那個框架,與大師進一步交流,發現新世界,解決多年來 cpp 靜態反射問題,一下子讓很多事變成了可能。(后來找到這個實現方法的最早出處:http://pfultz2.com/blog/2012/07/31/reflection-in-under-100-lines/)
來看看大師?actor?框架中的反射例子:
總結
以上是生活随笔為你收集整理的如何优雅地实现 C 编译期静态反射的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最新ddos网页端源码(ddos网页端源
- 下一篇: 濮阳食品备案网址(濮阳食品备案)