私は次のようなマクロを使用してC++でそれを読んでいました
#define max(a,b) (a > b ? a : b)
「二重評価」になる可能性があります。二重評価がいつ発生し、なぜそれが悪いのか、例を教えてもらえますか?
P.S .:驚いたことに、 Clojure (理解できない)の例を除いて、グーグルで検索するときに詳細な説明を見つけることができませんでした。
あなたがこれを書いたと想像してください:
#define Max(a,b) (a < b ? b : a)
int x(){ turnLeft(); return 0; }
int y(){ turnRight(); return 1; }
次のように呼び出します:
auto var = Max(x(), y());
turnRight()
が2回実行されることを知っていますか?そのマクロ、Max
は次のように展開されます。
auto var = (x() < y() ? y() : x());
条件x() < y()
を評価した後、プログラムはy() : x()
の間で必要な分岐を取ります:この場合true
は、に対してy()
を呼び出します2回目。ご覧ください Live On Coliru .
簡単に言えば、 式 を引数として 関数のようなマクロ に渡すと、Max
は式が2回評価される可能性があります。マクロのパラメーターは、マクロの定義で使用されます。 マクロは プリプロセッサによって処理されることに注意してください。
つまり、genericにしたいという理由だけで、マクロを使用して関数(実際にはこの場合は式)を定義しないでください。関数テンプレート
PS:C++には std::max
テンプレート関数があります。
a
およびb
は、マクロ定義で2回出現します。したがって、副作用のある引数と一緒に使用すると、副作用が2回実行されます。
max(++i, 4);
呼び出し前にi = 4
の場合、6を返します。期待される動作ではないため、max
などのマクロを置き換えるインライン関数を選択する必要があります。
次の式を検討してください。
x = max(Foo(), Bar());
Foo
とBar
は次のとおりです。
int Foo()
{
// do some complicated code that takes a long time
return result;
}
int Bar()
{
global_var++;
return global_var;
}
次に、元のmax
式で次のように展開されます。
Foo() > Bar() ? Foo() : Bar();
どちらの場合でも、FooまたはBarは2回実行されます。これにより、必要以上に時間がかかったり、予想される回数よりもプログラムの状態が変化したりします。私の単純なBar
の例では、一貫して同じ値を返しません。
CおよびC++のマクロ言語は、「前処理」段階で専用のパーサーによって処理されます。トークンは変換され、出力はパーサー本体の入力ストリームに送られます。 _#define
_および_#include
_トークンは、CまたはC++パーサー自体によってnot認識されます。
これは、マクロが「展開された」と言われたときに、literallyという意味になるため、重要です。与えられた
_#define MAX(A, B) (A > B ? A : B)
int i = 1, j = 2;
MAX(i, j);
_
c ++パーサーが見るものは
_(i > j ? i : j);
_
ただし、マクロをより複雑なもので使用すると、同じ展開が発生します。
_MAX(i++, ++j);
_
に展開されます
_(i++ > ++j ? i++ : ++j);
_
関数呼び出しを行う何かを渡すと:
_MAX(f(), g());
_
これはに展開されます
_(f() > g() ? f() : g());
_
コンパイラ/オプティマイザがf()
に副作用がないことを実証できる場合、これは次のように扱われます。
_auto fret = f();
auto gret = g();
(fret > gret) ? fret : gret;
_
できない場合は、f()とg()を2回呼び出す必要があります。たとえば、次のようになります。
_#include <iostream>
int f() { std::cout << "f()\n"; return 1; }
int g() { std::cout << "g()\n"; return 2; }
#define MAX(A, B) (A > B ? A : B)
int main() {
MAX(f(), g());
}
_
ライブデモ: http://ideone.com/3JBAmF
同様に、extern
関数を呼び出していた場合、オプティマイザーは 関数の2回の呼び出しを避ける ができない場合があります。