我們還是來討論c++吧,這幾年在c++里面玩代碼自動(dòng)生成技術(shù),而預(yù)處理是不可避免,也是不可或缺的重要工具。雖然boost pp預(yù)處理庫在宏的運(yùn)用上很是完善,但是代碼也太多了,而且代碼很不好理解,對(duì)此,不免讓人疑惑,有必要搞得那么復(fù)雜,搞那么多代碼嗎?并且,看了boostpp的使用接口后,感覺寫得很不干凈,也不好組合。因此,重新做了一套預(yù)處理的輪子。以下的代碼,假設(shè)在msvc2013以上的版本運(yùn)行,反正很多人用MSVC的,裝逼的自當(dāng)別論,造出來的輪子,傾向于先支持msvc。
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對(duì)這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、網(wǎng)站空間、營銷軟件、網(wǎng)站建設(shè)、衛(wèi)輝網(wǎng)站維護(hù)、網(wǎng)站推廣。
首先,我們定義一個(gè)宏,用來給把入?yún)⒆兂勺址?,咦,這個(gè)事情也太easy了,但是,在此,感覺,還是有必要廢話多解釋一下。以下代碼慣例都是,所有可用的宏函數(shù)都是以PP開頭全部大寫,而以_ZPP開頭的全部都是內(nèi)部實(shí)現(xiàn),其實(shí)還可以做得更難看一點(diǎn)。因?yàn)楹旰瘮?shù)是全局的,沒有作用域的概念,并且只是單純的文本替換,死的時(shí)候,還不知道怎么死,所以,必須謹(jǐn)慎對(duì)待。像是windows.h頭文件那樣,直接用min,max作為宏的名字,雖然用起來很方便,但也不知道制造了多少麻煩,所以,很多時(shí)候,包含windows.h時(shí),第一件事情就是undef min和max。
以下的代碼,可以隨便在某個(gè)工程下,隨便建立一個(gè)cpp后綴名的源文件,然后按CTRL+F7編譯,不需要F5,就可以看到運(yùn)行的效果,如果編譯通過,就說明宏基本上正確,測(cè)試代碼越多,準(zhǔn)確性就越高。當(dāng)然,你們也可以通過設(shè)置源文件的屬性,讓msvc生成預(yù)處理后的文件,然后用記事本打開那個(gè)文件觀看。
#define PP_TEXT(str) _ZPP_TEXT(str) #define _ZPP_TEXT(str) #str
在c++預(yù)處理宏中,操作符#是將后面跟隨的表達(dá)式加上兩個(gè)雙引號(hào),也就是字符串。PP_TEXT(str)不是直接定義成#str,而是通過調(diào)用_ZPP_TEXT(str),然后在那里才將入?yún)⒆兂勺址?,顯得有點(diǎn)輾轉(zhuǎn),有點(diǎn)多此一舉,但,其實(shí)是為了支持宏的全方位展開,也就是入?yún)tr本身也存在宏調(diào)用的時(shí)候,純屬無奈。比如,如果這樣實(shí)現(xiàn)
#define PP_TEXT(str) #str
那么,對(duì)于下面的情況,
#define AAA aaa
PP_TEXT(AAA),結(jié)果將是"AAA",而不是"aaa"。因?yàn)楹瓴僮鞣苯邮菍⑷雲(yún)⒆兂勺址瑳]有讓入?yún)⒂幸稽c(diǎn)點(diǎn)回旋的空間,所以只好引入間接層,讓入?yún)⒂袡C(jī)會(huì)宏展開。后面,很多宏函數(shù)都是這樣實(shí)現(xiàn),不得不間接調(diào)用,以便讓宏全面展開。而msvc的宏展開機(jī)制更加奇葩,更加不人性化,其間接調(diào)用的形式也更丑陋。這都是沒辦法的事情。
然后,為了調(diào)試宏,或者測(cè)試宏,當(dāng)然,很多時(shí)候,調(diào)試宏,還是要打開預(yù)處理的文件來對(duì)比分析。我們對(duì) static_assert作一點(diǎn)點(diǎn)包裝,因?yàn)閟tatic_assert需要兩個(gè)參數(shù),c++11后面的c++版本中,static_assert好像只需要一個(gè)入?yún)?,那時(shí)就不需要這個(gè)包裝了。
#define PP_ASSERT() static_assert((__VA_ARGS__), PP_TEXT(__VA_ARGS__));
PP_ASSERT(...)里面的三個(gè)點(diǎn),是不定參數(shù)的宏,而__VA_ARGS__就代表了...所匹配的所有參數(shù),這條語法很重要,要熟練。這里,就不詳細(xì)解釋其用法了,后面會(huì)有大把大把的宏函數(shù)用到__VA_ARGS__。
好了,我們可以開始用PP_ASSERT(...)做測(cè)試了。
PP_ASSERT(2+3==5)
如果,然后編譯這個(gè)文件,發(fā)現(xiàn)編譯通過了,比如
PP_ASSERT(2+3==4)
編譯的時(shí)候,就會(huì)報(bào)錯(cuò)誤信息,error C2338: 2+3==4
好了,測(cè)試準(zhǔn)備建立起來,就可以開始肆無忌憚的寫代碼了。一步一步地構(gòu)建c預(yù)處理宏的圖靈完備。
顯然,當(dāng)務(wù)之急,最根本的宏就是將兩個(gè)宏參數(shù)的并接,也即是##運(yùn)算符,顯然好比#運(yùn)算那樣子,必須給里面參數(shù)有宏展開的機(jī)會(huì),因此要間接調(diào)用,下面是其實(shí)現(xiàn)
#define PP_JOIN(_A, _B) _ZPP_JOIN_I(_A, _B) #define _ZPP_JOIN_I(_A, _B) _ZPP_JOIN_II(~, _A##_B) #define _ZPP_JOIN_II(p, res) res
竟然不止一層間接,而是兩層,又多此一舉,是因?yàn)榘l(fā)現(xiàn)在做宏遞歸的時(shí)候,一層間接調(diào)用還不能讓宏充分地展開,所以只好又加間接層,也不明白是何原因,也懶得追究了?,F(xiàn)在,接下來,當(dāng)然是測(cè)試PP_JOIN了。各位同學(xué),可以新建立一個(gè)測(cè)試文件,那個(gè)文件include我們的這個(gè)宏函數(shù)。當(dāng)然,也可以在同一個(gè)文件里面寫測(cè)試代碼,注意分成兩段代碼,上一段寫宏函數(shù),下一段寫測(cè)試代碼,目前來看,都可以的,后面再整理。
PP_ASSERT(PP_JOIN(1+2, == 3)) #define A 20 #define B 10 PP_ASSERT(PP_JOIN(A + B, == 30))
有了PP_JOIN,就可以開始做點(diǎn)其他事情了。比如,計(jì)數(shù)器,
#define _ZPP_INC_JOIN(_A, _B) _ZPP_INC_JOIN_IMP1(_A, _B) #define _ZPP_INC_JOIN_IMP1(_A, _B) _ZPP_INC_JOIN_IMP2(~, _A##_B) #define _ZPP_INC_JOIN_IMP2(p, res) res #define PP_INC(x, ) _ZPP_INC_JOIN(_ZPP_INC_, x) #define _ZPP_INC_0 1 #define _ZPP_INC_1 2 #define _ZPP_INC_2 3 #define _ZPP_INC_3 4 #define _ZPP_INC_4 5 #define _ZPP_INC_5 6 #define _ZPP_INC_6 7 #define _ZPP_INC_7 8 #define _ZPP_INC_8 9 #define _ZPP_INC_9 10
這里,我們重新又實(shí)現(xiàn)了一遍PP_JOIN,這也是沒辦法的事情,后面在重重嵌套的時(shí)候,會(huì)出現(xiàn)PP_JOIN里面又包含PP_JOIN的情況,這樣會(huì)導(dǎo)致宏停止展開了,所以,只好對(duì)于每一個(gè)要用到JOIN之處,都用自己版本的JOIN。
這是宏函數(shù)的實(shí)現(xiàn)方式,通過并接,文本替換,一一枚舉,才達(dá)到這樣的效果,也就是說,我們通過JOIN函數(shù),在宏里面構(gòu)造了一個(gè)計(jì)數(shù)器的數(shù)據(jù)類型。如果每個(gè)宏函數(shù)都這樣寫,豈不是很累。好消息是,只需用這種苦逼方式實(shí)現(xiàn)幾個(gè)最基本的函數(shù),然后通過宏的遞歸引擎,其他的宏函數(shù)就不需這樣子一個(gè)一個(gè)苦逼的并接替換了。
PP_ASSERT(PP_INC(9)==10)
PP_ASSERT(PP_INC(PP_INC(9)) == 11)
寫測(cè)試代碼習(xí)慣了,寫起來就很有意思了,測(cè)試通過,也是最激動(dòng)人心的時(shí)刻。
接下來,要處理msvc里面宏的惡心行為,然后就結(jié)束本引言。
#define PAIR_SECOND(x, y) y PP_ASSERT(PAIR_SECOND(10, 20) == 20)
這樣子,還不錯(cuò),下面,再define一個(gè)宏函數(shù),讓其返回一個(gè)pair,也就是兩個(gè)值
#define MAKE_PAIR(x, y) x, y
然后,這樣調(diào)用,
PAIR_SECOND(MAKE_PAIR(10, 20))
編譯器馬上就不高興了,warning C4003: “PAIR_SECOND”宏的實(shí)參不足
好像是編譯器沒有先展開MAKE_PAIR(10, 20),然后再調(diào)用PAIR_SECOND,而是直接把MAKE_PAIR(10, 20)整個(gè)當(dāng)成一個(gè)函數(shù)傳給PAIR_SECOND,然后,PAIR_SECOND就提示實(shí)參不足,然后,硬要測(cè)試,
PP_ASSERT(PAIR_SECOND(MAKE_PAIR(10, 20)) == 20)
顯然,無論如何,編譯器勢(shì)必就龍顏大怒了。對(duì)此,我們只好再引入間接層,想辦法讓MAKE_PAIR(10, 20)先展開,然后再傳給PAIR_SECOND。這樣,就不能直接用這樣的形式了,PAIR_SECOND(MAKE_PAIR(10, 20)) 。只好改成這樣,下面的幾行代碼,很有點(diǎn)驚天地泣鬼神的味道。
#define _ZPP_INVOKE_JOIN(_A, _B) _ZPP_IMP_INVOKE_JOIN_I(_A, _B) #define _ZPP_IMP_INVOKE_JOIN_I(_A, _B) _ZPP_IMP_INVOKE_JOIN_II(~, _A##_B) #define _ZPP_IMP_INVOKE_JOIN_II(p, res) res #define PP_INVOKE(m, args, ) _ZPP_INVOKE_JOIN(m, args)
前面幾行代碼都是PP_INVOKE的JOIN函數(shù)實(shí)現(xiàn),可以直接當(dāng)它們是JOIN函數(shù),關(guān)鍵是PP_INVOKE(m, args, ...)這里,第一個(gè)參數(shù)m是宏函數(shù),第二個(gè)是args,是要傳給第一個(gè)參數(shù)m的參數(shù)列表,用括號(hào)括起來,至于后面的省略號(hào),是有些時(shí)候?yàn)榱巳偩幾g器而添加的,也不知道是什么原因,反正這樣子就可以了,懶得追究。垃圾宏,垃圾預(yù)處理,只要能完成功能就行了,c++中,代碼生成代碼,重頭戲在tmp那里,宏只是小小必要的輔助工具而已。然后,這樣調(diào)用,
PP_ASSERT(PP_INVOKE(PAIR_SECOND, (MAKE_PAIR(10, 20))) == 20)
編譯通過了,好不容易?。?/p>
分享名稱:c++預(yù)處理的圖靈完備之引言
當(dāng)前URL:http://jinyejixie.com/article34/ppicpe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供微信公眾號(hào)、服務(wù)器托管、動(dòng)態(tài)網(wǎng)站、營銷型網(wǎng)站建設(shè)、電子商務(wù)、App開發(fā)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)