[解決済み] プリプロセッサー・マクロはなぜ悪なのか、そしてその代替手段は?
質問
最初の "Hello World" を書く前に、ほとんどすべてのプログラマーは "macro should never be used" や "macro are evil" などのフレーズに遭遇したと思いますが、私の質問は「なぜですか」というものです。新しい C++11 では、これほど長い年月を経て、真の代替手段があるのでしょうか?
簡単なのは、次のようなマクロです。
#pragma
のように、プラットフォームやコンパイラに依存するもので、ほとんどの場合、重大な欠陥があります。
#pragma once
のように、少なくとも 2 つの重要な状況、つまり、異なるパスで同じ名前を使用する場合、および一部のネットワーク セットアップやファイルシステムでエラーが発生しやすいという重大な欠陥があります。
しかし、一般的に、マクロとその使用に対する代替案はどうでしょうか?
どのように解決するのですか?
マクロは他の道具と同じです。殺人に使われるハンマーは、ハンマーだから悪いというわけではありません。それは、その人がそのように使用することにおいて悪なのです。釘を打ち込みたいなら、ハンマーは完璧なツールです。
マクロには、それらを "悪い" にするいくつかの側面があります (後でそれぞれについて展開し、代替手段を提案します)。
- マクロをデバッグすることはできません。
- マクロの展開は、奇妙な副作用をもたらすことがあります。
- マクロには "namespace" がないため、他の場所で使用されている名前と衝突するマクロがある場合、望んでいない場所でマクロ置換が行われ、これは通常、奇妙なエラー メッセージにつながります。
- マクロは、あなたが気づいていないことに影響を与えることがあります。
では、ここで少し展開してみましょう。
1) マクロはデバッグできない。 数値や文字列に変換するマクロがある場合、ソースコードにはマクロ名が記載され、多くのデバッガはマクロが何に変換されるのかを "見る"ことができません。そのため、実際に何が起こっているのかがわかりません。
置き換え
: 使用方法
enum
または
const T
関数型マクロの場合、デバッガはソース行単位で動作するため、マクロが1文であろうが100文であろうが、1文のように動作します。そのため、何が起こっているのかを把握することが困難です。
置き換え : 関数を使う - 速くする必要があるならインラインにする (ただしインラインにしすぎるのは良くない)
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
で、C++でbeginを使ったコードがあります。
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を引数として渡すか。
マクロを使用することが間違いなく有益である場合があります。例えば、関数をマクロでラップして、ファイルや行の情報を渡すことが挙げられます。
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
ここで
my_debug_malloc
を通常の malloc と同様に使用することができますが、これには追加の引数があり、最後に来て "which memory elements hasn't been freed" をスキャンすると、割り当てが行われた場所を表示して、プログラマがリークを突き止めることができます。
[1] シーケンスポイントにおいて、1つの変数を複数回更新することは、未定義の動作です。シーケンス ポイントはステートメントと完全に同じではありませんが、ほとんどの意図と目的では、それを考慮する必要があります。つまり
x++ * x++
を実行すると
x
を2回更新することになりますが、これは未定義であり、おそらくシステムによって異なる値になり、また
x
の結果値も違ってきます。
関連
-
[解決済み] error: 'if' の前に unqualified-id を期待した。
-
[解決済み】'cout'は型名ではない
-
[解決済み】エラー:strcpyがこのスコープで宣言されていない
-
[解決済み】エラー:free(): 次のサイズが無効です(fast)。
-
[解決済み】Eclipse IDEでC++エラー「nullptrはこのスコープで宣言されていません」が発生する件
-
[解決済み] explicit キーワードの意味は?
-
[解決済み] コピーアンドスワップ慣用句とは?
-
[解決済み] スマートポインターとは何ですか?
-
[解決済み] なぜ、オブジェクトそのものではなく、ポインタを使用しなければならないのですか?
-
[解決済み】C/C++の"-->"演算子とは何ですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】getline()が何らかの入力の後に使用されると動作しない 【重複あり
-
[解決済み] [Solved] Error C1083: Cannot open include file: 'stdafx.h'
-
[解決済み】C++ 式はポインタからオブジェクトへの型を持っている必要があります。
-
[解決済み】'cout'は型名ではない
-
[解決済み】リンカーエラーです。"リンカ入力ファイルはリンクが行われていないため未使用"、そのファイル内の関数への未定義参照
-
[解決済み] 非静的データメンバの無効な使用
-
[解決済み】CMakeエラー at CMakeLists.txt:30 (project)。CMAKE_C_COMPILER が見つかりませんでした。
-
[解決済み】なぜ、サイズ8の初期化されていない値を使用するのでしょうか?
-
[解決済み] Javaソースファイルにマクロを入れることはできますか
-
[解決済み] 未定義の動作とシーケンスポイント