#ifdef と #if - コードの特定のセクションのコンパイルを有効または無効にする方法としてはどちらが優れているか、より安全ですか?
-
02-07-2019 - |
質問
これはスタイルの問題かもしれませんが、私たちの開発チーム内では少し意見が分かれていて、この件について他の誰かが何かアイデアを持っているのではないかと思いました...
基本的に、通常の開発中はオフにするデバッグ print ステートメントがいくつかあります。個人的には次のことを行うことを好みます。
//---- SomeSourceFile.cpp ----
#define DEBUG_ENABLED (0)
...
SomeFunction()
{
int someVariable = 5;
#if(DEBUG_ENABLED)
printf("Debugging: someVariable == %d", someVariable);
#endif
}
ただし、チームの中には以下を好む人もいます。
// #define DEBUG_ENABLED
...
SomeFunction()
{
int someVariable = 5;
#ifdef DEBUG_ENABLED
printf("Debugging: someVariable == %d", someVariable);
#endif
}
...これらの方法のうちどれがあなたにとってより良いと思われますか?またその理由は何ですか?私の感覚では、最初の方法の方が安全です。なぜなら、常に何かが定義されており、他の場所で他の定義を破壊する危険がないからです。
解決
私の最初の反応は #ifdef
, 、 もちろん, 、 しかし、私は思います #if
実際、これにはいくつかの重要な利点があります。その理由は次のとおりです。
まず、使用できます DEBUG_ENABLED
プリプロセッサ内 そして コンパイルされたテスト。例 - デバッグが有効になっているときにタイムアウトを長くしたいことが多いので、次のようにします。 #if
, 、これは書けます
DoSomethingSlowWithTimeout(DEBUG_ENABLED? 5000 : 1000);
...の代わりに ...
#ifdef DEBUG_MODE
DoSomethingSlowWithTimeout(5000);
#else
DoSomethingSlowWithTimeout(1000);
#endif
第二に、ある環境から移行したい場合は、より有利な立場にあります。 #define
グローバル定数に変換します。 #define
通常、ほとんどの C++ プログラマは眉をひそめます。
そして第三に、チーム内に亀裂があるとおっしゃっています。私の推測では、これは、さまざまなメンバーがすでにさまざまなアプローチを採用しているため、標準化する必要があることを意味します。その裁定 #if
が推奨される選択肢であることは、コードを使用することを意味します #ifdef
場合でもコンパイルして実行します DEBUG_ENABLED
は誤りです。そしてそれは 多くの 生成されるべきではないときに生成されるデバッグ出力を追跡して削除する方が、その逆よりも簡単です。
ああ、それと、読みやすさに関する小さな点です。0/1 ではなく true/false を使用できるはずです。 #define
, 、値は単一の語彙トークンであるため、値を括弧で囲む必要がないのはこのときだけです。
#define DEBUG_ENABLED true
の代わりに
#define DEBUG_ENABLED (1)
他のヒント
両方ともひどいです。代わりに、次のようにしてください。
#ifdef DEBUG
#define D(x) do { x } while(0)
#else
#define D(x) do { } while(0)
#endif
その後、デバッグ コードが必要なときはいつでも、そのコードを内部に配置します。 D();
. 。そして、あなたのプログラムは恐ろしい迷路で汚染されていません。 #ifdef
.
#ifdef
与えられたトークンが定義されているかどうかを確認するだけです
#define FOO 0
それから
#ifdef FOO // is true
#if FOO // is false, because it evaluates to "#if 0"
複数のファイルで同じ問題が発生しており、「機能フラグ」ファイルをインクルードするのを忘れるという問題が常に発生します (ファイル数が 41,000 を超えるコードベースでは、インクルードするのは簡単です)。
feature.h がある場合:
#ifndef FEATURE_H
#define FEATURE_H
// turn on cool new feature
#define COOL_FEATURE 1
#endif // FEATURE_H
しかし、file.cpp にヘッダー ファイルを含めるのを忘れました。
#if COOL_FEATURE
// definitely awesome stuff here...
#endif
ここで問題が発生します。この場合、コンパイラは COOL_FEATURE が未定義であることを「false」と解釈し、コードをインクルードできません。はい、gcc は未定義のマクロに対してエラーを引き起こすフラグをサポートしています...ただし、ほとんどのサードパーティのコードは機能を定義しているか定義していないため、移植性は高くありません。
このケースを修正し、機能の状態をテストするポータブルな方法を採用しました。関数マクロ。
上記の feature.h を次のように変更した場合:
#ifndef FEATURE_H
#define FEATURE_H
// turn on cool new feature
#define COOL_FEATURE() 1
#endif // FEATURE_H
しかし、またしても file.cpp にヘッダー ファイルを含めるのを忘れてしまいました。
#if COOL_FEATURE()
// definitely awseome stuff here...
#endif
未定義の関数マクロが使用されているため、プリプロセッサがエラーを発生する可能性があります。
条件付きコンパイルを実行する目的で、#if と #ifdef は ほとんど 同じですが、完全ではありません。条件付きコンパイルが 2 つのシンボルに依存している場合、#ifdef はうまく機能しません。たとえば、PRO_VERSION と TRIAL_VERSION という 2 つの条件付きコンパイル シンボルがあるとすると、次のようになります。
#if defined(PRO_VERSION) && !defined(TRIAL_VERSION)
...
#else
...
#endif
#ifdef を使用すると、上記の処理はさらに複雑になり、特に #else 部分が機能するようになります。
私は条件付きコンパイルを広範囲に使用するコードに取り組んでおり、#if と #ifdef が混在しています。単純な場合には #ifdef/#ifndef を使用し、2 つ以上のシンボルが評価される場合には常に #if を使用する傾向があります。
それは完全にスタイルの問題だと思います。どちらも他方に比べて明らかな利点はありません。
どちらを選択するよりも一貫性の方が重要なので、チームで協力して 1 つのスタイルを選択し、それを貫くことをお勧めします。
私自身は次のことを好みます:
#if defined(DEBUG_ENABLED)
これにより、反対の条件を探すコードの作成が容易になるため、見つけやすくなります。
#if !defined(DEBUG_ENABLED)
対
#ifndef(DEBUG_ENABLED)
それはスタイルの問題です。ただし、これを行うには、より簡潔な方法をお勧めします。
#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print
#endif
debug_print("i=%d\n", i);
これを一度実行すると、その後は常に debug_print() を使用して、印刷するか何もしません。(はい、これはどちらの場合でもコンパイルされます。) こうすることで、コードがプリプロセッサ ディレクティブによって文字化けすることはありません。
「式には効果がありません」という警告が表示され、それを削除したい場合は、次の代替方法があります。
void dummy(const char*, ...)
{}
#ifdef USE_DEBUG
#define debug_print printf
#else
#define debug_print dummy
#endif
debug_print("i=%d\n", i);
#if
0 に設定すると、スイッチが存在することを検出しながら機能をオフにするオプションが提供されます。
個人的にはいつも #define DEBUG 1
#if または #ifdef でキャッチできます
#if と #define MY_MACRO (0)
#if を使用するということは、「定義」マクロ、つまりコード内で検索されて「(0)」に置き換えられるマクロを作成したことを意味します。これは、C++ で見たくない「マクロ地獄」です。コードが変更される可能性があり、コードが汚染されるからです。
例えば:
#define MY_MACRO (0)
int doSomething(int p_iValue)
{
return p_iValue + 1 ;
}
int main(int argc, char **argv)
{
int MY_MACRO = 25 ;
doSomething(MY_MACRO) ;
return 0;
}
g++ で次のエラーが発生します。
main.cpp|408|error: lvalue required as left operand of assignment|
||=== Build finished: 1 errors, 0 warnings ===|
のみ 1つ エラー。
これは、マクロが C++ コードと正常に対話したことを意味します。関数の呼び出しは成功しました。この単純なケースでは、面白いです。しかし、自分のコードでマクロが黙って動作するという私自身の経験は、喜びや充実感に満ちたものではありませんでした。
#ifdef と #define MY_MACRO
#ifdef を使用すると、何かを「定義」することになります。価値を与えるわけではありません。これはまだ汚染していますが、少なくとも「何も置き換えられず」、C++ コードからは不適切なコード ステートメントとして認識されなくなります。上記と同じコードを簡単に定義すると、次のようになります。
#define MY_MACRO
int doSomething(int p_iValue)
{
return p_iValue + 1 ;
}
int main(int argc, char **argv)
{
int MY_MACRO = 25 ;
doSomething(MY_MACRO) ;
return 0;
}
次のような警告が表示されます。
main.cpp||In function ‘int main(int, char**)’:|
main.cpp|406|error: expected unqualified-id before ‘=’ token|
main.cpp|399|error: too few arguments to function ‘int doSomething(int)’|
main.cpp|407|error: at this point in file|
||=== Build finished: 3 errors, 0 warnings ===|
それで...
結論
私はコードにマクロを入れずに暮らしたいのですが、複数の理由 (ヘッダー ガードの定義やデバッグ マクロなど) からそれはできません。
ただし、少なくとも、正規の C++ コードとの対話性を可能な限り少なくしたいと考えています。つまり、値を指定せずに #define を使用すること、#ifdef と #ifndef (または Jim Buck が提案した #if 定義) を使用すること、そして何よりも、正気で誰も使用しないような非常に長くて異質な名前を付けることを意味します。これは「偶然」であり、正規の C++ コードには決して影響しません。
ポストスクリプトム
今、自分の投稿を読み直していると、決して正しい C++ ではない値を見つけて定義に追加する必要はないのではないかと思いました。何かのようなもの
#define MY_MACRO @@@@@@@@@@@@@@@@@@
これは #ifdef および #ifndef と一緒に使用できますが、関数内で使用するとコードをコンパイルできません...これを g++ で正常に試してみましたが、次のエラーが発生しました。
main.cpp|410|error: stray ‘@’ in program|
面白い。:-)
前者は私にとってより明確に思えます。定義済み/未定義と比較して、フラグにする方が自然に思えます。
どちらもまったく同等です。慣用的な使用法では、#ifdef は定義されているかどうかを確認するためだけに使用されます (例で使用する内容も同様です)。一方、#if は #if Definition(A) && !define(B) などのより複雑な式で使用されます。
ちょっとしたOTですが、C++ではプリプロセッサによるロギングのオン/オフを切り替えるのは明らかに最適ではありません。Apache のような優れたロギング ツールがあります ログ4cxx これらはオープンソースであり、アプリケーションの配布方法を制限しません。また、再コンパイルせずにログ レベルを変更でき、ログをオフにした場合のオーバーヘッドが非常に低くなり、運用環境でログを完全にオフにする機会が得られます。
それはまったくスタイルの問題ではありません。また、質問は残念ながら間違っています。これらのプリプロセッサ ディレクティブを、より優れているか、より安全であるかという意味で比較することはできません。
#ifdef macro
「マクロが定義されている場合」または「マクロが存在する場合」を意味します。ここではマクロの値は関係ありません。それは何でも構いません。
#if macro
常に値と比較する場合。上の例では、標準的な暗黙的な比較です。
#if macro !=0
#if の使用例
#if CFLAG_EDITION == 0
return EDITION_FREE;
#elif CFLAG_EDITION == 1
return EDITION_BASIC;
#else
return EDITION_PRO;
#endif
CFLAG_EDITION の定義をコードに含めることができるようになりました。
#define CFLAG_EDITION 1
または、マクロをコンパイラ フラグとして設定することもできます。また ここを参照してください.
以前使用していました #ifdef
, 、しかし、ドキュメント化のために Doxygen に切り替えたとき、コメントアウトされたマクロはドキュメント化できないことがわかりました (または、少なくとも Doxygen は警告を生成します)。これは、現在有効になっていない機能切り替えマクロを文書化できないことを意味します。
Doxygen に対してのみマクロを定義することは可能ですが、これは、コードの非アクティブな部分のマクロも文書化されることを意味します。個人的には機能スイッチを表示し、それ以外の場合は現在選択されているもののみを文書化したいと考えています。さらに、Doxygen がファイルを処理するときにのみ定義する必要があるマクロが多数あると、コードが非常に煩雑になります。
したがって、この場合は、常にマクロを定義して使用することをお勧めします。 #if
.
私はこれを定義するために常に #ifdef とコンパイラ フラグを使用してきました...
あるいは、グローバル定数を宣言し、プリプロセッサ #if の代わりに C++ if を使用することもできます。コンパイラーは未使用のブランチを最適化して取り除く必要があり、コードはよりクリーンになります。
これが何です C++ の注意点 スティーブン C.Dewhurst 氏は #if の使用について次のように述べています。
ドライバーに条件定義を指定する方法が異なる場合には違いがあります。
diff <( echo | g++ -DA= -dM -E - ) <( echo | g++ -DA -dM -E - )
出力:
344c344
< #define A
---
> #define A 1
この意味は -DA
の同義語です -DA=1
値を省略すると、次のような場合に問題が発生する可能性があります。 #if A
使用法。