在C/C++項(xiàng)目中合理使用宏詳解
C++項(xiàng)目中常使用宏來(lái)做跨平臺(tái)、功能實(shí)現(xiàn)隔離、變量定義的功能,這篇文章來(lái)討論下是否所有情況下都適合用宏
小D的故事
程序員小D接到一個(gè)任務(wù),需要給同事A提供一個(gè)復(fù)雜公式的實(shí)現(xiàn)。輸入為一組參數(shù),輸出一個(gè)計(jì)算結(jié)果。
大致如下:
double computeSomeThing(double paramA,double paramB,double paramC);
小D很快完成了。過(guò)了幾天同事A又來(lái)找他,說(shuō)現(xiàn)在需要提升該函數(shù)的性能,建議改為在float類(lèi)型上,用一些SIMD指令。且同事A表示不是很愿意修改接口。于是小D在考慮以下兩點(diǎn)后決定用一個(gè)宏把原來(lái)double的實(shí)現(xiàn)和float的實(shí)現(xiàn)分開(kāi)來(lái)。
1、上層需求變動(dòng)性比較大,說(shuō)不定哪天又要用double了。所以還是保留double類(lèi)型的實(shí)現(xiàn)
2、用宏把兩份代碼隔開(kāi)來(lái),互相不影響比較省事
于是代碼就變成了這樣:
double computeSomeThing(double paramA,double paramB,double paramC)
{
#ifdef _USE_DOUBLE_
// do something in double
#else
// convert dobule to float
// do something in float
// convert float to double
#endif
}
同事A很滿(mǎn)意,因?yàn)樗灰鎿Q一下.so或.a即可,代碼層不需要改動(dòng)。于是和小D合作開(kāi)發(fā)了很多這樣的函數(shù),并且都有float和double兩種實(shí)現(xiàn)。在對(duì)性能要求高的時(shí)候要求小D提供float版本;性能要求低,精度要求的時(shí)候要求小D提供double版本。
此時(shí)小D會(huì)在出庫(kù)的時(shí)候感到一絲不方便。第一,版本號(hào)中需要區(qū)分float和double版本。
第二,因?yàn)橛煤旮糸_(kāi),切換兩個(gè)版本的時(shí)候需要重新編譯,而代碼量很多所以編譯時(shí)間很長(zhǎng),但這些都是能克服的。
直到有一天同事小B的模塊也需要這個(gè)庫(kù),并且小A和小B的模塊要組合起來(lái)給小C用,最要命的是小A和小B的模塊分別要用float版本和double版本。所以此時(shí)應(yīng)該提供float版本so還是提供double版本so呢。
問(wèn)題分析
在上面的場(chǎng)景中,小D作為一個(gè)基礎(chǔ)庫(kù)的提供者不應(yīng)該因?yàn)橥虏辉敢庑薷慕涌诨蛘邎D方便用宏去隔離功能,使得一個(gè)接口有了二義性。比較合適的一種做法是,再提供一個(gè)控制選擇變量,來(lái)選擇用哪種實(shí)現(xiàn),即允許運(yùn)行時(shí)決定用float還是double版本。
double computeSomeThing(double paramA,double paramB,double paramC,bool isFast);
或者小A就是不愿意改接口(考慮實(shí)際項(xiàng)目中,接口參數(shù)復(fù)雜且調(diào)用分散在各處),那么也可以通過(guò)增加接口實(shí)現(xiàn)。
double computeSomeThing(double paramA,double paramB,double paramC);
void setFast(bool isFast);
下面的情況用宏做隔離就是比較合理的選擇。
比如一套代碼要分別運(yùn)行在linux和windows上,依賴(lài)的頭文件、部分基礎(chǔ)函數(shù)接口都是有區(qū)別的。此時(shí)用宏去隔離就比較合理。因?yàn)檫@兩個(gè)版本在運(yùn)行時(shí)永遠(yuǎn)不會(huì)同時(shí)出現(xiàn)。除了平臺(tái)差異性外,版本管理也可以用宏來(lái)做隔離。
比如opencl 1.2和opencl 2.0版本相比較的話(huà),2.0版本中新增了SVM相關(guān)的接口。當(dāng)一個(gè)opencl程序未來(lái)可能運(yùn)行在1.2版本的設(shè)備和2.0版本的設(shè)備上時(shí)。
可以用宏來(lái)選擇是否屏蔽掉SVM接口。因?yàn)?.0的接口運(yùn)行在1.2的設(shè)備上時(shí),無(wú)法從環(huán)境中獲取2.0新增的接口實(shí)現(xiàn)導(dǎo)致程序跑不起來(lái)(1.2的相關(guān)so中沒(méi)有SVM函數(shù)實(shí)現(xiàn))。
不過(guò)這個(gè)問(wèn)題用宏來(lái)處理也不是最優(yōu)的,使用dlopen可以有更靈活的實(shí)現(xiàn)。
總結(jié)
對(duì)于做基礎(chǔ)庫(kù)提供給很多人使用的同學(xué),當(dāng)用宏隔開(kāi)的代碼有可能會(huì)同時(shí)運(yùn)行在一個(gè)環(huán)境時(shí)建議改為運(yùn)行時(shí)選擇走哪條分支。但肯定互相不兼容的時(shí)候就放心的用宏吧,比如跨操作系統(tǒng)。
另外提一下,對(duì)于有很多代碼的大項(xiàng)目用宏的時(shí)候也要慎重考慮一下,不要?jiǎng)硬粍?dòng)就用宏去做一些功能開(kāi)關(guān),因?yàn)榫幾g時(shí)間太長(zhǎng)是很影響效率的。
比如有以下宏定義:
#define _OPEN_LOG_ #ifdef _OPEN_LOG_ #define LOG_PRINT(...) printf(...) #else #define LOG_PRINT(...) #endif
開(kāi)發(fā)階段代碼中到處插著LOG_PRINT的使用,發(fā)布時(shí)關(guān)閉打印又是一波整個(gè)項(xiàng)目重新編譯。再多來(lái)幾個(gè)這種功能,每次切換又是整個(gè)項(xiàng)目重新編譯,非常煩人。可以用函數(shù)指針代替:
typedef void (*LogPrint)(const char * pstrMsg);
LogPrint g_LogPrint;
void LogPrint_Imp(const char *pstrMsg)
{
printf("%s\n",pstrMsg);
return;
}
void LogPrint_Empty(const char *pstrMsg)
{
return;
}
int main(int argc,char **argv)
{
// 此處對(duì)日志功能進(jìn)行開(kāi)關(guān)
g_LogPrint = LogPrint_Imp ;
//g_LogPrint = LogPrint_Empty ;
// .....
}
void someFun()
{
g_LogPrint("in someFun"); //到底打印還是不打印,運(yùn)行時(shí)決定
}
在這個(gè)例子中,關(guān)閉日志時(shí)編譯器只會(huì)對(duì)main函數(shù)所在的文件進(jìn)行重新編譯,就不用費(fèi)時(shí)費(fèi)力的重新編譯整個(gè)項(xiàng)目了。而且還可以把g_LogPrint的賦值的行為通過(guò)接口開(kāi)放到上層,由調(diào)用者決定是否需要打開(kāi)log。
再舉個(gè)例子,有些人喜歡項(xiàng)目中各個(gè)代碼模塊中用到的參數(shù)提到一個(gè)頭文件中,然后各個(gè).c都包含這個(gè)頭文件。就像這樣:
// GobalParam.h #ifndef XX_XX #define XX_XX #define DETECTION_MAX 100 #define INPUT_WIDTH_MAX 4096 #define INPUT_HEIGHT_MAX 4096 // 諸如此類(lèi)很多宏 #endif
我個(gè)人覺(jué)得下面這種實(shí)現(xiàn)更好
// GobalParam.h #ifndef XX_XX #define XX_XX extern const int DETECTION_MAX; extern const int INPUT_WIDTH_MAX ; extern const int INPUT_HEIGHT_MAX ; // 在某個(gè).c或.cpp中賦值 :const int INPUT_HEIGHT_MAX = 100; #endif
這樣你對(duì)某個(gè)參數(shù)修改的時(shí)候,就不用眼巴巴的等著所有包含此頭文件的編譯模塊重新編譯了。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方歡迎留言討論,望不吝賜教。
相關(guān)文章
C++內(nèi)存池的簡(jiǎn)單實(shí)現(xiàn)
內(nèi)存池是一種動(dòng)態(tài)內(nèi)存分配與管理技術(shù)。本文主要介紹了C++內(nèi)存池的簡(jiǎn)單實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
C語(yǔ)言中字符串庫(kù)函數(shù)的實(shí)現(xiàn)及模擬
C語(yǔ)言中有很多數(shù)據(jù)類(lèi)型,比如int(整數(shù)類(lèi)型)、char(字符類(lèi)型)、以及浮點(diǎn)型的double(雙精度)等。但是有一點(diǎn)就是我們發(fā)現(xiàn)這里并沒(méi)有提到我們常見(jiàn)的有關(guān)字符串的類(lèi)型。本文為大家介紹了C語(yǔ)言中字符串庫(kù)函數(shù)的實(shí)現(xiàn)及模擬,需要的可以參考一下2022-11-11
C++實(shí)現(xiàn)十六進(jìn)制字符串轉(zhuǎn)換為十進(jìn)制整數(shù)的方法
這篇文章主要介紹了C++實(shí)現(xiàn)十六進(jìn)制字符串轉(zhuǎn)換為十進(jìn)制整數(shù)的方法,涉及C++字符串與數(shù)制轉(zhuǎn)換的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
C++算法計(jì)時(shí)器的實(shí)現(xiàn)示例
本文主要介紹了C++算法計(jì)時(shí)器的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
解析C++無(wú)鎖隊(duì)列的實(shí)現(xiàn)代碼
本篇文章是對(duì)C++無(wú)鎖隊(duì)列的實(shí)現(xiàn)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05

