TinyFormat 源碼閱讀(一)
慚愧很久沒有更新B站了,博客也很少寫,隨便開個(gè)坑吧,一個(gè)閱讀源碼的系列,主要找一些質(zhì)量不錯(cuò)的小型項(xiàng)目/庫(kù),也同時(shí)學(xué)習(xí)一下。
首先看一下項(xiàng)目簡(jiǎn)介:
tinyformat.h is a type safe printf replacement library in a single C++ header file. If you've ever wanted?
printf("%s", s)
to just work regardless of the type of?s
, tinyformat might be for you.
tinyformat.h 是一個(gè)類型安全的 header only(只包含單個(gè)頭文件) 的 printf 替換庫(kù)。如果你曾經(jīng)想過 printf("%s", s) 可以在無(wú)論 s 的類型如何的情況下都能正常工作,那么 tinyformat 可能就是你要的。
我們可以首先看一下 CMakeLists.txt
來決定我們閱讀源碼的順序:
tinyformat
是一個(gè) header only
的庫(kù)也就是只包含頭文件,使用時(shí)不需要鏈接而只需要 include
的庫(kù),因此我們?cè)?CMakeLists.txt
中看不到一個(gè)庫(kù)相關(guān)的 target
,但是可以先通過項(xiàng)目中的測(cè)試用例也就是 tinyformat_test
來對(duì)整個(gè)庫(kù)建立一個(gè)直觀的印象。
因此我們首先閱讀 tinyformat_test.cpp
文件,沒有使用測(cè)試框架,而是自己定義了一些宏作為輔助:
從名字能看出這個(gè)宏的目的是期待在測(cè)試中出現(xiàn)錯(cuò)誤,整個(gè)執(zhí)行塊被包含在 try/catch
中,首先執(zhí)行傳入的 expression
語(yǔ)句,如果執(zhí)行過程中存在異常拋出,那么接下來的代碼不會(huì)被執(zhí)行到,直接被捕獲并忽略,如果沒有拋出異常那么會(huì)觸發(fā) ++nfailed
表示測(cè)試結(jié)果與行為不符,并在 log 中打印失敗的測(cè)試用例。不過這里只對(duì) std::runtime_error
類型的異常做了處理算是一個(gè)小瑕疵。
這個(gè)也比較簡(jiǎn)單,如果 a
不等于 b
就會(huì)觸發(fā) ++nfailed
,不過這里有一些小細(xì)節(jié)要說一下,第一個(gè)是之所以這里把參數(shù)都用括號(hào)括起來是一個(gè)好的習(xí)慣,有助于我們約束宏的行為防止一些意料之外的錯(cuò)誤,第二個(gè)是即使這樣錯(cuò)誤還是不可避免,比如這里如果我們以下面的兩種方式使用 CHECK_EQUAL
宏:
我們就會(huì)發(fā)現(xiàn) a
在 CHECK_EQUAL
中自增的次數(shù)其實(shí)是不一致的,這很容易導(dǎo)致我們無(wú)法預(yù)測(cè)宏的行為,這也是為什么越來越多的人不推薦使用宏的原因:
甚至可以看到 3 != 3
這樣的神奇現(xiàn)象,如果在一個(gè)復(fù)雜項(xiàng)目中出現(xiàn)這種日志會(huì)讓我們非常頭疼
從名字中的 Wrap
以及注釋能看出這是使用 tfm::format
來包裝和創(chuàng)建一些東西,結(jié)合用例:
可以看出用法還是比較簡(jiǎn)單的,本身庫(kù)的作用就是替換 printf
,因此傳入 std::fmt
的后幾個(gè)參數(shù) "asdf"
等都被拼到 someformat %s:%d:%d
中,最后通過 m_oss.str()
一并返回到 CHECK_EQUAL
中和 "10: someformat asdf:2:4"
進(jìn)行比較,那結(jié)果應(yīng)該是正確的。
接著看一下實(shí)現(xiàn), 首先能看到 TestWrap::error
這個(gè)模板函數(shù)不是直接定義的,而是由 MAKE_ERROR_FUNC
通過 TINYFORMAT_FOREACH_ARGNUM
生成,前者很簡(jiǎn)單就是一個(gè)包含實(shí)現(xiàn)的宏,我們主要看一下后者,在項(xiàng)目頭文件中找到的定義如下:
這個(gè)寫法現(xiàn)在看來很丑很難理解,實(shí)際上考慮到這是一個(gè)基于 c++11
且多年沒有更新的項(xiàng)目是正常的,因?yàn)楫?dāng)時(shí)的 c++ 沒有對(duì)變長(zhǎng)模板參數(shù)的支持,因此這種寫法甚至在舊版本的 boost
實(shí)現(xiàn)中也有用到?;氐?TINYFORMAT_FOREACH_ARGNUM
的定義,實(shí)際就是實(shí)例化出傳入的函數(shù)參數(shù) m
也就是 MAKE_ERROR_FUNC
的 16 個(gè)版本,因此我們需要再次回到 MAKE_ERROR_FUNC
的實(shí)現(xiàn)中。
可以看到對(duì) n
的多次使用,首先是 TINYFORMAT_ARGTYPES(n)
,剛剛說了這種是用來代替變長(zhǎng)模板參數(shù),于是我們直接找一下定義應(yīng)該也是類似的:
沒錯(cuò)就是這么丑,通過 ##
連接 TINYFORMAT_ARGTYPES_
和模板參數(shù)長(zhǎng)度生成宏的名字并返回對(duì)應(yīng)長(zhǎng)度的模板參數(shù),以 template<TINYFORMAT_ARGTYPES(2)>
為例返回的就是 template<class T1, class T2>
,其他兩個(gè)宏 TINYFORMAT_VARARGS
和 TINYFORMAT_PASSARGS
實(shí)現(xiàn)也基本相同這里不再贅述。
那實(shí)現(xiàn)中宏的部分和上面的類似,也是生成了不同參數(shù)長(zhǎng)度版本的構(gòu)造函數(shù)。這里 TestExceptionDef
是一個(gè)自定義的異常類型,繼承自 std::runtime_error
,構(gòu)造時(shí)通過 tfm::format
傳入解釋異常原因的提示信息。
后續(xù)的話就是一些測(cè)試用例了,那么對(duì) tinyformat_test.cpp
測(cè)試代碼部分的閱讀我們就到此為止,下一篇我們將會(huì)正式從 tfm::format
函數(shù)開始窺探整個(gè)庫(kù)的運(yùn)行機(jī)制,感謝大家的閱讀,再會(huì)!