私はいくつかのコードを閲覧していて、その中にいくつかの三項演算子が見つかりました。このコードは、私たちが使用するライブラリであり、非常に高速であることが想定されています。
そこのスペース以外のものを保存するかどうか考えています。
あなたの経験は何ですか?
三項演算子は、適切に記述された同等のif
/else
ステートメントとパフォーマンスに違いがあってはなりません...抽象構文ツリーで同じ表現に解決されたり、同じ最適化を受けたりする場合があります。
定数または参照を初期化する場合、またはメンバー初期化リスト内で使用する値を算出する場合、if
/else
ステートメントは使用できませんが、_?
_ _:
_は次のようになります:
_const int x = f() ? 10 : 2;
X::X() : n_(n > 0 ? 2 * n : 0) { }
_
_?
_ _:
_を使用する主な理由にはローカリゼーションが含まれ、同じステートメント/関数呼び出しの他の部分が重複して繰り返されることを回避します。次に例を示します。
_if (condition)
return x;
else
return y;
_
...よりも好ましい...
_return condition ? x : y;
_
...非常に経験の浅いプログラマを扱う場合、または_?
_ _:
_構造がノイズで失われるほど複雑な用語がある場合は、読みやすさの理由から。次のようなより複雑な場合:
_fn(condition1 ? t1 : f1, condition2 ? t2 : f2, condition3 ? t3 : f3);
_
同等のif
/else
:
_if (condition1)
if (condition2)
if (condition3)
fn(t1, t2, t3);
else
fn(t1, t2, f3);
else if (condition3)
fn(t1, f2, t3);
else
fn(t1, f2, f3);
else
if (condition2)
...etc...
_
これは、コンパイラーが最適化する場合としない場合がある、多くの追加の関数呼び出しです。
_t1
_、_f1
_、_t2
_などの式が冗長すぎて繰り返し入力できない場合は、名前付き一時ファイルを作成すると役立つことがありますが、次のようになります。
_?
_ _:
_に一致するパフォーマンスを得るには、同じ一時変数が呼び出される関数の2つの_std::move
_パラメーターに渡される場合を除いて、_&&
_を使用する必要がある場合があります。 。これはより複雑でエラーが発生しやすくなります。
c _?
_ (x _:
_ yはcを評価します---両方ではなくどちらかxとyなので、ポインタをテストすることは安全ではありません。 t nullptr
を使用する前に、フォールバック値/動作を提供します。コードはxとyのどちらかの副作用のみを取得します=が実際に選択されています。名前付きテンポラリでは、初期化の中でif
/else
を必要とするか、不要なコードを実行するために_?
_ _:
_を実行するか、必要以上にコードを実行する必要があります。
考慮してください:
_void is(int) { std::cout << "int\n"; }
void is(double) { std::cout << "double\n"; }
void f(bool expr)
{
is(expr ? 1 : 2.0);
if (expr)
is(1);
else
is(2.0);
}
_
上記の条件演算子バージョンでは、_1
_はdouble
への標準変換を受け、型は_2.0
_に一致します。つまり、true
/_1
_でもis(double)
オーバーロードが呼び出されます状況。 if
/else
ステートメントはこの変換をトリガーしません。true
/_1
_ブランチはis(int)
を呼び出します。
全体的な型がvoid
の式は、条件演算子でも使用できませんが、if
/else
の下のステートメントでは有効です。
異なる重点があります:
if
/else
ステートメントは最初に分岐を強調し、実行される処理は二次的なものですが、三項演算子はそれを使用する値の選択に対して実行される処理を強調します。
状況によっては、プログラマーの「自然な」見方をコードに反映し、理解、検証、保守を容易にする場合もあります。コードを記述するときにこれらの要素を検討する順序に基づいて、一方を他方から選択する場合があります。「何かを行う」ことを開始した場合は、いくつかの(または少数の)値のいずれかを使用して行う可能性があります。 _?
_ _:
_は、それを表現してコーディングの「フロー」を続行するための最も中断の少ない方法です。
上手...
GCCとこの関数呼び出しでいくつかのテストを行いました。
add(argc, (argc > 1)?(argv[1][0] > 5)?50:10:1, (argc > 2)?(argv[2][0] > 5)?50:10:1, (argc > 3)?(argv[3][0] > 5)?50:10:1);
結果のgcc -O3を使用したアセンブラコードには35の命令が含まれていました。
If/else +中間変数を含む同等のコードは36でした。ネストされたif/elseを使用して、3> 2> 1の事実を使用して、44を取得しました。これを個別の関数呼び出しに拡張することすらしませんでした。
今、私はパフォーマンス分析も、結果のアセンブラコードの品質チェックも行いませんでしたが、ループのないこのような単純なものでは、e.t.c。短いほど良いと思います。
結局のところ、3項演算子には何らかの価値があるようです:-)
もちろん、それはコードの速度が絶対に重要な場合だけです。 if/elseステートメントは、(c1)?(c2)?(c3)?(c4)?: 1:2:3:4のようなものよりもネストされた方がはるかに読みやすくなります。そして、関数の引数として巨大な式を持つことはnot funです。
また、ネストされた3項式を使用すると、コードをリファクタリングしたり、条件に一連の便利なprintfs()を配置してデバッグしたりするのが非常に難しくなります。
私の見解では、プレーンifステートメントよりも三項演算子の唯一の潜在的な利点は、初期化に使用できることです。これは、const
で特に役立ちます。
例えば。
const int foo = (a > b ? b : a - 10);
関数calを使用しないと、if/elseブロックでこれを行うことはできません。このようなconstのケースがたくさんある場合は、if/elseを使用した割り当てよりもconstを適切に初期化することで小さな利益が得られる場合があります。測定してください!たぶん測定できません。私がこれを行う傾向があるのは、constをマークすることにより、後で何かをすると、修正済みと思っていたものを誤って変更する可能性がある/変更する可能性があることをコンパイラが知っているためです。
事実上、私が言っていることは、3項演算子はconst-correctnessにとって重要であり、constの正確さは次のような習慣になることです。
実際には、「if-else」式を優先して「if-else」式を優先する言語が多数ある場合、必須の2つの違いがあると想定します(この場合、3項演算子さえもない可能性があり、これはもう必要ありません)
想像してみてください:
x = if (t) a else b
とにかく、三項演算子は一部の言語(C、C#、C++、Javaなど)の式ですnotには「if-else」式があり、したがって異なる役割を果たします)あります。
パフォーマンスの観点からそれについて心配しているのであれば、2つの間に違いがあったとしても、私は非常に驚きます。
ルックアンドフィールの観点からは、主に個人の好みによるものです。条件が短く、true/falseの部分が短い場合、3項演算子は問題ありませんが、if/elseステートメントの方が長い方が良い傾向があります(私の意見では)。