web-dev-qa-db-ja.com

C ++コードの複雑な行を書き換える方法(ネストされた3項演算子)

私はデバッグの目的で他の誰かのコードを調べていて、これを見つけました:

!m_seedsfilter ? good=true : m_seedsfilter==1 ? good=newClusters(Sp) : good=newSeed(Sp);  

これは何を意味するのでしょうか?これをよりわかりやすいif/elseステートメントにする自動化ツールはありますか?このような複雑な制御構造を処理するためのヒントはありますか?

編集注記:これは、意見の問題なので、タイトルで「不必要に複雑」から「複雑」に変更しました。これまでのすべての回答に感謝します。

24
invisiblerhino

次のように書き直すと、記述されたステートメントが改善される可能性があります。

_good = m_seedsfilter==0 ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);
_

...しかし、一般的には、3項ステートメントに慣れる必要があります。最初に投稿されたコード、またはxanatosのバージョン、または私のもののいずれについても、本質的に悪はありません。三元ステートメントは悪ではなく、それらは言語の基本的な機能であり、慣れると、このようなコード(元の投稿ではなく、私が投稿したとおり)が実際には簡単であることに気付くでしょうif-elseステートメントのチェーンよりも読み取ります。たとえば、このコードでは、次のようにこのステートメントを単純に読み取ることができます。「変数good等しい... _m_seedsfilter==0_の場合はtrue、それ以外の場合は_m_seedsfilter==1_、次にnewClusters(Sp)、それ以外の場合はnewSeed(Sp)。」

上記の私のバージョンでは、変数goodへの3つの個別の割り当てを回避し、ステートメントの目的がgoodに値を割り当てることであることを明確にしています。また、このように書くと、基本的にこれが「スイッチケース」構造であり、デフォルトのケースがnewSeed(Sp)であることが明確になります。

_m_seedsfilter_の型のoperator!()がオーバーライドされない限り、上記の私の書き換えが適切であることに注意してください。そうであれば、元のバージョンの動作を維持するためにこれを使用する必要があります...

_good = !m_seedsfilter   ? true :
       m_seedsfilter==1 ? newClusters(Sp) :
                          newSeed(Sp);
_

...そして、以下のxanatosのコメントが証明するように、newClusters()メソッドとnewSeed()メソッドが互いに異なる型を返し、これらの型が慎重に作成された無意味な変換演算子で記述されている場合、その後、元のコードとまったく同じ動作を忠実に再現するために、元のコード自体に戻す必要があります(ただし、xanatosの投稿のように、より適切にフォーマットされています)。しかし、現実の世界では、だれもそれを行うつもりはないので、上記の最初のバージョンで問題ありません。


更新、元の投稿/回答から2年半後:@TimothyShieldsと私が時々これについて賛成票を集め続けているのは興味深いことであり、ティムの回答はこの回答の賛成票の約50%で一貫して追跡しているようです(このアップデートの時点で43 vs 22)。

私は、三項ステートメントが賢明に使用されたときに追加できる明快さの別の例を追加したいと思いました。以下の例は、コールスタック使用状況アナライザー(コンパイルされたCコードを分析するツールですが、ツール自体はC#で書かれています)用に作成していたコードの短いスニペットです。 3つすべてのバリアントは、少なくとも外部から見える効果が及ぶ限り、まったく同じ目的を達成します。

1。三項演算子なし:

_Console.Write(new string(' ', backtraceIndentLevel) + fcnName);
if (fcnInfo.callDepth == 0)
{
   Console.Write(" (leaf function");
}
else if (fcnInfo.callDepth == 1)
{
   Console.Write(" (calls 1 level deeper");
}
else
{
   Console.Write(" (calls " + fcnInfo.callDepth + " levels deeper");
}
Console.WriteLine(", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");
_

2。三項演算子を使用して、Console.Write():を個別に呼び出します

_Console.Write(new string(' ', backtraceIndentLevel) + fcnName);
Console.Write((fcnInfo.callDepth == 0) ? (" (leaf function") :
              (fcnInfo.callDepth == 1) ? (" (calls 1 level deeper") :
                                         (" (calls " + fcnInfo.callDepth + " levels deeper"));
Console.WriteLine(", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");
_

3。三項演算子を使用すると、Console.Write():への単一の呼び出しに折りたたまれます。

_Console.WriteLine(
   new string(' ', backtraceIndentLevel) + fcnName +
   ((fcnInfo.callDepth == 0) ? (" (leaf function") :
    (fcnInfo.callDepth == 1) ? (" (calls 1 level deeper") :
                               (" (calls " + fcnInfo.callDepth + " levels deeper")) +
   ", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");
_

上記の3つの例の違いは取るに足らないものであると主張する人もいるかもしれません。それはすべて簡潔であることです。アイデアを「できるだけ少ない単語」で表現することで、リスナー/リーダーは、アイデアの終わりに到達するまでにアイデアの始まりを覚えていることができます。小さな子供たちと話すときは、単純で短い文章を使用しているため、アイデアを表現するのにより多くの文章が必要になります。自分の言語に堪能な大人と話すときは、アイデアをより簡潔に表現する、長くて複雑な文章を使用します。

これらの例では、1行のテキストを標準出力に出力します。それらが実行する操作は単純ですが、それらをより大きなシーケンスのサブセットとして想像するのは簡単です。そのシーケンスのサブセットをより簡潔に表現できるほど、そのシーケンスの多くがエディターの画面に収まるようになります。もちろん、私は容易にその努力を過度に実行することができ、理解するのをより困難にします。目的は、「 スイートスポット 」をわかりやすく簡潔にすることを見つけることです。プログラマーが三項ステートメントに慣れると、それを使用するコードを理解することは、そうでないコードを理解することよりも簡単になると主張します(例2および31)。

経験豊富なプログラマーが三項ステートメントを快適に使用できる最後の理由は、メソッド呼び出しを行うときに不要な一時変数を作成しないようにすることです。その例として、上記の例の4番目のバリアントを示します。ロジックは、Console.WriteLine()への単一の呼び出しに圧縮されています。結果はlessの両方が理解できるless簡潔:

4。三項演算子なしで、Console.Write():への単一の呼び出しに折りたたまれています

_string tempStr;
if (fcnInfo.callDepth == 0)
{
   tempStr = " (leaf function";
}
else if (fcnInfo.callDepth == 1)
{
   tempStr = " (calls 1 level deeper";
}
else
{
   tempStr = " (calls " + fcnInfo.callDepth + " levels deeper";
}
Console.WriteLine(new string(' ', backtraceIndentLevel) + fcnName + tempStr +
                  ", max " + (newStackDepth + fcnInfo.callStackUsage) + " bytes)");
_

「ロジックをConsole.WriteLine()への1回の呼び出しに集約する必要はない」と論じる前に、これは単なる例であると考えてください。他のメソッドへの呼び出しを想像してください。他の変数の状態について。独自の一時変数を作成し、それらの一時変数を使用してメソッドを呼び出すことも、3項演算子を使用してコンパイラーに独自の(名前のない)一時変数を作成させることもできます。繰り返しますが、3項演算子を使用すると、3項演算子を使用しない場合よりもはるかに簡潔でわかりやすいコードを使用できると主張します。しかし、それをわかりやすくするためには、三項演算子が悪であるという先入観を捨てなければなりません。

58
phonetagger

同等の非邪悪なコードはこれです:

if (m_seedsfilter == 0)
{
    good = true;
}
else if (m_seedsfilter == 1)
{
    good = newClusters(Sp);
}
else
{
    good = newSeed(Sp);
}

連鎖三項演算子-つまり、次の

condition1 ? A : condition2 ? B : condition3 ? C : D

-コードを判読不能にする優れた方法です。

@phonetaggerが提案する2番目の提案は、三項演算子に慣れることです。これにより、ネストされた演算子に遭遇したときにそれを排除できます。

27
Timothy Shields

これの方が良い?

_!m_seedsfilter ? good=true 
               : m_seedsfilter==1 ? good=newClusters(Sp) 
                                  : good=newSeed(Sp);  
_

これを追加しますが、理論的にはこの式を簡略化することは可能ですが(理由は明らかです!)、結果として得られる式はすべてのケースで100%になるとは限りません...そして2つの式がC++で本当に同等のものは、非常に非常に非常に非常に複雑な問題です...

私が考案した縮退の例( http://ideone.com/uLpe0L )(非常に縮退ではないことに注意してください...小さなプログラミングエラーに基づいているだけです)は、goodの考慮に基づいていますboolUnixDateTimeSmallUnixDateTimeの2つのクラスを作成し、newClusters()SmallUnixDateTimeを返し、newSeed()UnixDateTimeを返します。どちらも、1970-01-01の真夜中からの秒数の形式でUnix日時を含めるために使用する必要があります。 SmallUnixDateTimeintを使用し、UnixDateTimeは_long long_を使用します。どちらも暗黙的にboolに変換できます(それらの内部値が_!= 0_、「クラシック」である場合に戻ります)が、UnixDateTimeは暗黙的にSmallUnixDateTimeに変換できます(精度が失われる可能性があるため、これは誤りです...これは小さなプログラミングエラーです)。変換に失敗すると、_0_に設定されたSmallUnixDateTimeが返されます。この例のコードでは、常に単一の変換があります。SmallUnixDateTimeからboolの間、またはUnixDateTimeからboolの間...

この似ているが異なる例では:

_good = !m_seedsfilter ? true 
                      : m_seedsfilter==1 ? newClusters(Sp) 
                                         : newSeed(Sp);
_

可能なパスは2つあります。SmallUnixDateTimenewClusters(Sp))はboolに変換されるか、UnixDateTimenewSeed(Sp))は最初にSmallUnixDateTimeに変換され、次にboolに変換されます。明らかに、2つの式は同等ではありません。

これを機能させる(または機能させない)ために、newSeed(Sp)は、SmallUnixTimestd::numeric_limits<int>::max() + 1LL)に含めることができない値を返します。

8
xanatos

あなたの主な質問に答えるために、これは条件式の例です:

条件式論理OR式logical-OR-expression条件式

logical-OR-expressiontrueと評価された場合、式の結果は_?_に続く式になります。それ以外の場合は、_:_に続く式です。例えば、

_x = y > 0 ? 1 : 0;
_

xが0より大きい場合はyに1を割り当て、それ以外の場合は '0'を割り当てます。

例がひどく書かれているので、あなたはこの例についていささか感じるかもしれません。著者は、_?:_演算子を制御構造として使用しようとしていますが、それを目的としていません。

これを書くより良い方法は

_
    good = !m_seedsfilter ? true : 
                            ( m_seedsfilter == 1 ? newClusters(SP) : 
                                                   newSeed(SP) );
_

_m_seedsfilter_が0の場合、goodtrueに設定されます。 _m_seedsfilter_が1の場合、goodnewClusters(SP)の結果に設定されます。それ以外の場合、goodnewSeed(SP)の結果に設定されます。

4
John Bode

ネストされた三項ステートメントにより、コードが読みにくくなります。残りのコードを大幅に簡略化する場合にのみ使用します。引用されたコードは次のように書き直すことができます:

good = !m_seedsfilter ? true : m_seedsfilter==1 ? newClusters(Sp) : newSeed(Sp); 

またはこのように:

if(!m_seedsfilter)
    good = true;
else if(m_seedsfilter==1)
    good = newClusters(Sp);
else
    good = newSeed(Sp);

1つ目の選択肢は簡潔ですが、初心者には読みにくく、デバッグも困難です。

3
Michael
!m_seedsfilter ? good=true : m_seedsfilter==1 ? good=newClusters(Sp) : good=newSeed(Sp);

に翻訳されます

if (!m_seedsfilter)
{
     good = true;
}
else
{
     if (m_seedsfilter == 1)
     {
          good = newClusters(Sp);
     }
     else
     {
          good = new Seed(Sp);
     }
}
1
phyrrus9
_if ( !m_seedsfilter )
  good = true;
else if ( m_seedsfilter == 1 )
  good = newClusters(Sp);
else
  good = newSeed(Sp);
_

_?_が後に続く式は、おおよそif ( expression )に対応しますが、_:_は、else句に似たものを導入します。これはステートメントではなく式です。

_<condition> ? <expression-1> : <expression-2>
_

conditionがtrueの場合、値は_expression-1_の式です。それ以外の場合は_expression-2_です。

1
Nicola Musatti