109

私はいつもこれを尋ねてきましたが、本当に良い答えを受け取ったことはありません。最初の「Hello World」を書く前でさえ、ほぼすべてのプログラマーが「マクロは決して使用すべきではない」、「マクロは悪である」などのフレーズに遭遇したと思いますが、私の質問は次のとおりです。なぜですか? 新しい C++11 では、これほど長い年月を経て、真の代替手段はありますか?

#pragma簡単な部分は、プラットフォーム固有およびコンパイラー固有の のようなマクロに関するものであり、ほとんどの場合#pragma once、少なくとも 2 つの重要な状況でエラーが発生しやすいような深刻な欠陥があります: 異なるパスで同じ名前を使用し、いくつかのネットワーク設定とファイルシステムを使用する.

しかし、一般的に、マクロとその使用法に代わるものはどうでしょうか?

4

8 に答える 8

187

マクロは他のツールとまったく同じです。殺人で使用されるハンマーはハンマーであるため、悪ではありません。人がそのようにそれを使う方法でそれは悪です。釘を打ち込みたい場合は、ハンマーが最適なツールです。

マクロを「悪い」ものにするいくつかの側面があります(それぞれについては後で詳しく説明し、代替案を提案します)。

  1. マクロをデバッグすることはできません。
  2. マクロの拡張は、奇妙な副作用を引き起こす可能性があります。
  3. マクロには「名前空間」がないため、他の場所で使用されている名前と衝突するマクロがある場合、不要な場所でマクロが置き換えられ、通常、奇妙なエラーメッセージが表示されます。
  4. マクロは、あなたが気付いていないことに影響を与える可能性があります。

それでは、ここで少し拡張してみましょう。

1)マクロはデバッグできません。 数値または文字列に変換されるマクロがある場合、ソースコードにはマクロ名が付けられ、多くのデバッガーはマクロが何に変換されるかを「見る」ことができません。したがって、実際に何が起こっているのかわかりません。

交換:使用enumまたはconst T

「関数のような」マクロの場合、デバッガーは「現在のソース行ごと」のレベルで動作するため、マクロは、ステートメントが1つであろうと100であろうと、単一のステートメントのように機能します。何が起こっているのか理解するのが難しくなります。

置換:関数を使用する-「高速」である必要がある場合はインライン(ただし、インラインが多すぎるのは良いことではないことに注意してください)

2)マクロ拡張は奇妙な副作用をもたらす可能性があります。

有名なのは#define SQUARE(x) ((x) * (x))と用途x2 = SQUARE(x++)です。これはx2 = (x++) * (x++);、有効なコード[1]であっても、プログラマーが望んでいたものではないことはほぼ間違いありません。関数の場合、x ++を実行しても問題はなく、xは1回だけ増分します。

別の例は、マクロの「if else」で、次のようになります。

#define safe_divide(res, x, y)   if (y != 0) res = x/y;

その後

if (something) safe_divide(b, a, x);
else printf("Something is not set...");

それは実際には完全に間違ったものになります....

置換:実際の機能。

3)マクロには名前空間がありません

マクロがある場合:

#define begin() x = 0

そして、beginを使用するC++のコードがいくつかあります。

std::vector<int> v;

... stuff is loaded into v ... 

for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
   std::cout << ' ' << *it;

さて、あなたはどのようなエラーメッセージを受け取ると思いますか、そしてどこでエラーを探しますか[誰かが書いたヘッダーファイルにあるbeginマクロを完全に忘れた-または知らなかったと仮定して-?[インクルードの前にそのマクロをインクルードすると、さらに楽しくなります。コード自体を見るとまったく意味のない奇妙なエラーに溺れてしまいます。

置換:「ルール」ほどの置換はありません。マクロには大文字の名前のみを使用し、他のものにはすべて大文字の名前を使用しないでください。

4)マクロにはあなたが気付かない効果があります

この機能を取る:

#define begin() x = 0
#define end() x = 17
... a few thousand lines of stuff here ... 
void dostuff()
{
    int x = 7;

    begin();

    ... more code using x ... 

    printf("x=%d\n", x);

    end();

}

ここで、マクロを見なくても、beginはxに影響を与えない関数であると考えるでしょう。

この種のこと、そして私ははるかに複雑な例を見てきましたが、本当にあなたの一日を台無しにする可能性があります!

置換:マクロを使用してxを設定しないか、xを引数として渡します。

マクロを使用することが間違いなく有益な場合があります。1つの例は、関数をマクロでラップして、ファイル/行情報を渡すことです。

#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x)  my_debug_free(x, __FILE__, __LINE__)

これで、コードで通常のmallocとして使用できますmy_debug_mallocが、追加の引数があるため、最後に「どのメモリ要素が解放されていないか」をスキャンすると、割り当てが行われた場所を出力できるため、プログラマーはリークを追跡できます。

[1]「シーケンスポイントで」1つの変数を複数回更新することは未定義の動作です。シーケンスポイントはステートメントとまったく同じではありませんが、ほとんどの目的と目的では、それを考慮すべきです。そのため、2回x++ * x++更新xされますが、これは未定義であり、システムごとに値が異なり、結果値も異なる可能性がありxます。

于 2012-12-26T14:18:02.420 に答える
24

「マクロは悪」という言葉は通常、#pragma ではなく #define の使用を指します。

具体的には、式は次の 2 つのケースを指します。

  • マジックナンバーをマクロとして定義

  • マクロを使用して式を置き換える

新しいC++ 11では、何年も経った後に本当の選択肢がありますか?

はい、上記のリストの項目については (マジック ナンバーは const/constexpr で定義する必要があり、式は [normal/inline/template/inline template] 関数で定義する必要があります。

以下は、マジック ナンバーをマクロとして定義し、式をマクロで置き換える (これらの式を評価する関数を定義する代わりに) ことによって生じる問題の一部です。

  • マジック ナンバーのマクロを定義する場合、コンパイラは定義された値の型情報を保持しません。これにより、コンパイルの警告 (およびエラー) が発生し、コードをデバッグする人々が混乱する可能性があります。

  • 関数の代わりにマクロを定義する場合、そのコードを使用するプログラマーはマクロが関数のように機能することを期待しますが、実際には機能しません。

次のコードを検討してください。

#define max(a, b) ( ((a) > (b)) ? (a) : (b) )

int a = 5;
int b = 4;

int c = max(++a, b);

c への割り当て後、a と c は 6 であると予想されます (マクロの代わりに std::max を使用した場合と同様)。代わりに、コードは次を実行します。

int c = ( ((++a) ? (b)) ? (++a) : (b) ); // after this, c = a = 7

さらに、マクロは名前空間をサポートしていません。つまり、コードでマクロを定義すると、クライアント コードで使用できる名前が制限されます。

#include <algorithm>これは、上記のマクロを (max に対して) 定義すると、明示的に記述しない限り、以下のコードのいずれでも実行できなくなることを意味します。

#ifdef max
#undef max
#endif
#include <algorithm>

変数/関数の代わりにマクロを使用すると、それらのアドレスを取得できないことも意味します。

  • 定数としてのマクロがマジック ナンバーに評価される場合、アドレスで渡すことはできません。

  • 関数としてのマクロの場合、それを述語として使用したり、関数のアドレスを取得したり、ファンクターとして扱ったりすることはできません。

編集:例として、#define max上記の正しい代替:

template<typename T>
inline T max(const T& a, const T& b)
{
    return a > b ? a : b;
}

これは、マクロが行うすべてのことを行いますが、1 つの制限があります: 引数の型が異なる場合、テンプレート バージョンは明示的であることを強制します (実際には、より安全でより明示的なコードにつながります)。

int a = 0;
double b = 1.;
max(a, b);

この max がマクロとして定義されている場合、コードは (警告付きで) コンパイルされます。

この max がテンプレート関数として定義されている場合、コンパイラはあいまいさを指摘するので、またはのどちらかを言う必要がありますmax<int>(a, b)(max<double>(a, b)したがって、意図を明示的に述べます)。

于 2012-12-26T13:57:32.120 に答える
14

よくあるトラブルは次のとおりです。

#define DIV(a,b) a / b

printf("25 / (3+2) = %d", DIV(25,3+2));

プリプロセッサが次のように展開するため、5 ではなく 10 が出力されます。

printf("25 / (3+2) = %d", 25 / 3 + 2);

このバージョンはより安全です:

#define DIV(a,b) (a) / (b)
于 2012-12-26T13:49:22.940 に答える
3

マクロは、一般的なコード (マクロのパラメーターは何でもかまいません) を作成する場合に特に価値があり、パラメーターを使用する場合もあります。

さらに、このコードは、マクロが使用されるポイントに配置 (つまり、挿入) されます。

OTOH、同様の結果が以下で達成される可能性があります。

  • オーバーロードされた関数 (異なるパラメーター タイプ)

  • テンプレート、C++ (汎用パラメーターの型と値)

  • インライン関数 (単一ポイントの定義にジャンプするのではなく、呼び出される場所にコードを配置します。ただし、これはむしろコンパイラの推奨事項です)。

編集:マクロが悪い理由について:

1) 引数の型チェックがない (型がない) ため、簡単に誤用される可能性がある 2) 非常に複雑なコードに展開されることがあり、前処理されたファイルでの識別と理解が困難になる場合がある 3) エラーを起こしやすい次のようなマクロ内のコードになりやすい:

#define MULTIPLY(a,b) a*b

そして電話する

MULTIPLY(2+3,4+5)

それはで展開します

2+3*4+5 ((2+3)*(4+5) ではありません)。

後者を使用するには、次を定義する必要があります。

#define MULTIPLY(a,b) ((a)*(b))
于 2012-12-26T13:49:47.590 に答える
3

プリプロセッサの定義やマクロを呼び出したまま使用しても問題はないと思います。

これらは c/c++ に見られる (メタ) 言語の概念であり、他のツールと同様に、自分が何をしているのかを知っていれば、作業が楽になります。マクロの問題は、マクロが c/c++ コードの前に処理され、欠陥のある新しいコードを生成し、明らかなコンパイラ エラーを引き起こすことです。明るい面では、適切に使用すれば、コードをクリーンに保ち、多くの入力を節約するのに役立つため、個人的な好みに依存します.

于 2012-12-26T13:55:54.480 に答える
3

C/C++ のマクロは、バージョン管理の重要なツールとして機能します。マクロのマイナー構成で、同じコードを 2 つのクライアントに配信できます。私は次のようなものを使用します

#define IBM_AS_CLIENT
#ifdef IBM_AS_CLIENT 
  #define SOME_VALUE1 X
  #define SOME_VALUE2 Y
#else
  #define SOME_VALUE1 P
  #define SOME_VALUE2 Q
#endif

この種の機能は、マクロなしでは簡単には実現できません。マクロは実際には優れたソフトウェア構成管理ツールであり、コードを再利用するためのショートカットを作成するだけの手段ではありません。マクロでの再利用を目的として関数を定義すると、間違いなく問題が発生する可能性があります。

于 2015-07-04T19:36:15.180 に答える
1

私の経験では、マクロはプログラムのサイズにとって理想的ではなく、デバッグが難しい場合があります。しかし、慎重に使用すれば問題ありません。

多くの場合、適切な代替手段は、汎用関数および/またはインライン関数です。

于 2012-12-26T13:52:02.017 に答える