C/C++プロジェクトでマクロを合理的に使用します。

4356 ワード

C++プロジェクトでは、マクロを使用して、プラットフォームを横断し、機能を分離し、変数定義の機能を実現します。この文章では、すべての場合にマクロを使用するのに適しているかどうかを議論します。
Dちゃんの物語
プログラマDは、同僚Aに複雑な公式の実現を提供する必要があります。パラメータセットに入力し、計算結果を出力します。
大体次の通りです
double compute SomeThing(double paramA、double paramB、double paramC);
Dさんはもうすぐ完成します。数日後に同僚のAさんがまた彼を訪ねてきました。今はこの関数の性能を向上させる必要があると言っています。floatタイプに変更して、いくつかのSIDコマンドを使います。また、同僚のAさんはインターフェースを修正するのがあまり好きではないと言いました。Dさんは次の二つの点を考えてから一つのマクロで元のdoubleの実現とfloatの実現を分離することにしました。
1、上層部の需要変動性が大きいので、いつかまたdoubleを使うかもしれません。だからやはりdoubleのタイプの実現を保留します。
2、マクロで二つのコードを分けて、お互いに影響しないほうがいいです。
するとコードはこうなりました。

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はとても満足しています。彼は取り替えさえすればいいです。そして.aでいいです。コード層は変更する必要がありません。そして、Dさんと協力して、このような関数をたくさん開発しました。floatとdoubleの二つの実現があります。性能に対する要求が高い時、小Dにfloatバージョンを提供するように要求します。性能要求が低く、精度が要求される場合は、Dちゃんにdoubleバージョンの提供を要求します。
この時Dさんは出庫する時に不便を感じます。第一に、バージョン番号の中でfloatとdoubleバージョンを区別する必要があります。
二つ目はマクロで区切られていますので、二つのバージョンを切り替える時には再コンパイルが必要です。コード量が多いので、コンパイル時間が長いですが、これらは全部克服できます。
ある日同僚のBさんのモジュールもこのライブラリが必要です。そして、AさんとBさんのモジュールを組み合わせてCさんに使います。一番大変なのはAさんとBさんのモジュールはそれぞれfloatバージョンとdoubleバージョンを使います。だからこの時floatバージョンを提供するべきです。それともdoubleバージョンを提供しますか?
問題の分析
上のシーンでは、小さなDはベースライブラリの提供者として、同僚がインターフェースを変更したり、マクロで隔離したりするのが嫌なので、インターフェースに二義性があるようにするべきではない。適切な方法の一つは、どのような実装を使用するかを選択するための制御選択変数を提供することです。すなわち、実行が許可された時にfloatまたはdoubleのバージョンを決定します。
double compute SomeThing(double paramA、double paramB、double paramC、book isFast);
あるいは、Aさんはインターフェースを変えたくないです。(実際のプロジェクトを考えると、インターフェースパラメータが複雑で、呼び出しが分散しています。)インターフェースを増やすことによっても実現できます。
double compute SomeThing(double paramA、double paramB、double paramC);
void set Fast(book isFast);
次の場合はマクロで隔離するのが合理的です。
例えば、コードのセットはそれぞれlinuxとwindowsで実行されます。依存するヘッダファイル、一部の基礎関数インターフェースは違いがあります。マクロで隔離するのが合理的です。この二つのバージョンは運行時に同時に出現しないからです。プラットフォームの差異性以外に、バージョン管理もマクロで隔離することができます。
opencl 1.2とopencl 2.0バージョンを比較すると、2.0バージョンにSVM関連インターフェースが追加されます。openclプログラムが将来1.2バージョンのデバイスと2.0バージョンのデバイスで動作する可能性がある場合。
マクロを用いてSVMインターフェースを遮断するかどうかを選択することができる。2.0のインターフェースが1.2のデバイスで動作しているため、環境から2.0の新規インターフェースを取得できなくなり、プログラムが実行できなくなりました。
しかし、この問題はマクロで解決しても最適ではないので、dleopenを使ってもっと柔軟に実現できます。
締め括りをつける
ベースライブラリを作って多くの人に提供する学生に対しては、マクロで区切られたコードが同時に実行される可能性があります。しかし、お互いに互換性がないなら、マクロを使って安心してください。
また、多くのコードがある大プロジェクト用マクロについても慎重に検討してください。すぐにマクロで機能スイッチを作らないでください。コンパイル時間が長いので、効率に影響を与えます。
たとえば、次のマクロ定義があります。

#define _OPEN_LOG_
#ifdef _OPEN_LOG_
 #define LOG_PRINT(...) printf(...)
#else
 #define LOG_PRINT(...) 
#endif
開発段階コードにはどこにもLOG_が挿入されています。PRINTの使用は、リリース時に印刷をオフにします。また、1波全体の項目を編集し直します。この機能をもういくつか持ってきて、毎回切り替えたり、プロジェクト全体を作り直したりして、面倒くさいです。関数ポインタで置換できます。

typedef void (*LogPrint)(const char * pstrMsg);
LogPrint g_LogPrint;
void LogPrint_Imp(const char *pstrMsg)
{
 printf("%s
",pstrMsg); return; } void LogPrint_Empty(const char *pstrMsg) { return; } int main(int argc,char **argv) { // g_LogPrint = LogPrint_Imp ; //g_LogPrint = LogPrint_Empty ; // ..... } void someFun() { g_LogPrint("in someFun"); // , }
この例では、ログを閉じるときにコンパイラは、main関数があるファイルを再コンパイルするだけで、時間をかけてプロジェクト全体を再コンパイルする必要はありません。しかもg_もできますLogPrintの割当行為はインターフェースを介して上層に開放され、使用者によってロゴを開く必要があるかどうかを決定する。
もう一つの例を挙げると、プロジェクトの各コードモジュールで使用されるパラメータについて、先頭ファイルに言及して、それぞれの頭ファイルを含んでいる人がいます。このように:

// GobalParam.h
#ifndef XX_XX
#define XX_XX
#define DETECTION_MAX 100
#define INPUT_WIDTH_MAX 4096
#define INPUT_HEIGHT_MAX 4096
//        
#endif
個人的には次のような実現がより良いと思います。

// 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 ;
//    .c .cpp    :const int INPUT_HEIGHT_MAX = 100;
#endif
このように、あるパラメータを修正するときは、このヘッダを含むすべてのコンパイルモジュールを目視せずに再コンパイルします。
以上は個人の経験ですので、参考にしていただければと思います。もし間違いがあったり、完全に考えていないところがあれば、コメント検討を歓迎します。