マクロは他のツールとまったく同じです。殺人で使用されるハンマーはハンマーであるため、悪ではありません。人がそのようにそれを使う方法でそれは悪です。釘を打ち込みたい場合は、ハンマーが最適なツールです。
マクロを「悪い」ものにするいくつかの側面があります(それぞれについては後で詳しく説明し、代替案を提案します)。
- マクロをデバッグすることはできません。
- マクロの拡張は、奇妙な副作用を引き起こす可能性があります。
- マクロには「名前空間」がないため、他の場所で使用されている名前と衝突するマクロがある場合、不要な場所でマクロが置き換えられ、通常、奇妙なエラーメッセージが表示されます。
- マクロは、あなたが気付いていないことに影響を与える可能性があります。
それでは、ここで少し拡張してみましょう。
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
ます。