巨大な「switch」ステートメントの代わりにオブジェクト指向アプローチを使用するのはなぜですか?


59

私は.Net、C#ショップで働いており、より多くのオブジェクト指向のアプローチではなく、多くの「ケース」を使用してコードで巨大なSwitchステートメントを使用することを主張し続ける同僚がいます。彼の主張は一貫して、Switchステートメントが「cpuジャンプテーブル」にコンパイルされ、したがって最速のオプションであるという事実に戻っています(他のことでは、速度については気にしないとチームに言われますが)。

正直なところ、これに反対する議論はありません...彼が何について話しているのかわからないからです。
彼は正しいですか?
彼はただお尻を話しているだけですか?
ここで学ぼうとしています。


7
.NET Reflectorのようなものを使用してアセンブリコードを調べ、「CPUジャンプテーブル」を探すことで、彼が正しいかどうかを確認できます。
FrustratedWithFormsDesigner

5
だから、すべての純粋仮想関数でディスパッチ最悪の場合の方法を行いなし仮想関数は単に直接にリンクされているCPUのジャンプテーブル『「スイッチ文がにコンパイル』あなたは比較する任意のコードをダンプしています。。?
S.Lott

64
コードは、マシンではなくPEOPLE向けに作成する必要があります。そうしないと、アセンブリですべてを実行できます。
maple_shaft

8
彼がそんなにうんざりしているなら、クヌースを引用してください:「私たちは小さな効率を忘れて、時間の約97%を言うべきです:早すぎる最適化はすべての悪の根源です。」
-DaveE

12
保守性。他に1単語の質問があれば、私がお手伝いしますか?
マットエレン

回答:


48

彼はおそらく古いCハッカーであり、はい、彼はお尻から話します。.NetはC ++ではありません。.Netコンパイラは改善を続けており、ほとんどの巧妙なハックは非生産的です。今日ではないにしても、次の.Netバージョンでは。.Net JITは、使用される前にすべての関数を1回実行するため、小さな関数が望ましいです。そのため、プログラムのライフサイクル中にヒットしないケースがある場合、JITコンパイルでコストは発生しません。とにかく、速度が問題にならない場合、最適化はありません。最初にプログラマ用に、次にコンパイラ用に記述します。あなたの同僚は簡単に納得できませんので、コードを整理するほうが実際に速いことを経験的に証明します。私は彼の最も悪い例の1つを選び、それらをより良い方法で書き直して、コードがより高速であることを確認します。必要に応じてチェリーピック。次に、それを数百万回実行し、プロファイルして彼を見せます。

編集

ビル・ワグナーはこう書いている:

項目11:小さな機能の魅力を理解する(効果的なC#第2版)C#コードを機械で実行可能なコードに変換するのは2段階のプロセスであることを忘れないでください。C#コンパイラは、アセンブリで提供されるILを生成します。JITコンパイラーは、必要に応じて、各メソッド(またはインライン化が関係する場合はメソッドのグループ)のマシンコードを生成します。小さな関数を使用すると、JITコンパイラーがそのコストを簡単に償却できます。小さな関数もインライン化の候補になる可能性が高くなります。それは単なる小ささではありません:より単純な制御フローも同様に重要です。関数内の制御ブランチが少ないと、JITコンパイラーが変数を登録しやすくなります。より明確なコードを書くことは、単に良い習慣ではありません。実行時により効率的なコードを作成する方法です。

EDIT2:

だから...どうやらswitchステートメントはif / elseステートメントの束よりも高速で優れているようです。1つの比較は対数で、もう1つの比較は線形であるためです。 http://sequence-points.blogspot.com/2007/10/why-is-switch-statement-faster-than-if.html

巨大なswitchステートメントを置き換えるための私のお気に入りのアプローチは、それらに応じて呼び出される関数に値をマッピングするディクショナリ(または列挙型または小さなintをオンにしている場合は配列)を使用することです。そうすることで、多くの厄介な共有スパゲッティ状態を削除する必要がありますが、それは良いことです。通常、大きなswitchステートメントはメンテナンスの悪夢です。したがって...配列と辞書では、検索に一定の時間がかかり、余分なメモリがほとんど無駄になりません。

switchステートメントの方が優れているとはまだ確信できません。


47
それをより速く証明することを心配しないでください。これは時期尚早な最適化です。節約できるミリ秒は、200ミリ秒かかるデータベースに追加するのを忘れたインデックスと比較しても何もありません。あなたは間違った戦いで戦っています。
ラインヘンリッヒス

27
@ジョブは、彼が実際に正しい場合はどうなりますか?ポイントは、彼が間違っているということではなく、彼が正しいことであり、それは問題ではないということです。
ラインヘンリッヒス

2
たとえ彼が正しかったとしても、彼はまだ私たちの時間を無駄にしています。
ジェレミー

6
あなたがリンクしたページを読んでみようと思います。
AttackingHobo

3
C ++の嫌悪は何ですか?C ++コンパイラも改善されており、C ++でもC#と同じように大きなスイッチは劣っています。これはまったく同じ理由です。あなたが悲しみを与える元のC ++プログラマーに囲まれているのは、彼らがC ++プログラマーであるからではなく、彼らが悪いプログラマーであるからです。
セバスチャンレッド14

39

同僚がこの変更がアプリケーション全体のスケールで実際に測定可能な利点を提供するという証拠を提供できない限り、実際にそのよう利点を提供するアプローチ(つまり、ポリモーフィズム)に劣ります:保守性。

ボトルネックが特定された後にのみ、最適化を行う必要があります。時期尚早の最適化はすべての悪の根源です

速度は定量化できます。「アプローチAはアプローチBよりも高速です」にはほとんど有用な情報がありません。質問は「どれくらい高速ですか?」です。


2
絶対に本当。何かがより高速であると決して主張しないでください、常に測定してください。そして、アプリケーションのその部分がパフォーマンスのボトルネックになっている場合にのみ測定します。
キリアンフォス

6
-1「早すぎる最適化はすべての悪の根源です」クヌースの意見を偏らせる部分だけでなく引用全体を表示してください。
代替

2
@mathepic:私は意図的にこれを引用として提示しませんでした。もちろんこの文は私の個人的な意見ですが、もちろん私の創作ではありません。c2の人たちはその部分だけを核となる知恵と見なしているように思われるかもしれないが
back2dos

8
@alternative Knuthの完全な引用「効率の限界が悪用につながることは間違いありません。プログラマーは、プログラムの重要でない部分の速度を考える、または心配するのに膨大な時間を浪費します。デバッグとメンテナンスを検討する際の強いマイナスの影響。わずかな効率、たとえば約97%を忘れてください。時期尚早な最適化はすべての悪の根源です。」OPの同僚を完全に説明します。私見back2dosは「早すぎる最適化がすべての悪の根源」と引用をうまく要約しました。
MarkJ

2
@MarkJ 97%の時間
代替案

27

それが速い場合、誰が気にしますか?

リアルタイムソフトウェアを書いているのでなければ、完全に異常な方法で何かをすることで得られる非常に小さなスピードアップがクライアントに大きな違いをもたらすことはまずありません。私はこれをスピードの最前線で戦うことすらしません。この男は明らかにこの主題に関する議論を聞かないでしょう。

しかし、保守性はゲームの目的であり、巨大なswitchステートメントは少しでも保守可能ではありません。コードから新しい人へのさまざまなパスをどのように説明しますか?ドキュメントは、コード自体と同じ長さでなければなりません!

さらに、効果的に単体テストを行うことができなくなり(可能なパスが多すぎます。インターフェイスの欠如などは言うまでもありません)、コードの保守性がさらに低下します。

[関心がある側:JITterは小さなメソッドでより良いパフォーマンスを発揮するため、巨大なswitchステートメント(および本質的に大きなメソッド)は、大きなアセンブリIIRCでの速度を損ないます。]


1
+時期尚早な最適化の巨大な例。
シェーン

間違いなくこれ。
DeadMG

「巨大な切り替えステートメントは少しでも保守可能ではありません」の+1
Koreyヒントン

2
巨大なswitchステートメントは、新しい男が理解するのがはるかに簡単です。考えられるすべての動作は、すてきなリストですぐに収集されます。間接的な呼び出しを追跡するのは非常に難しく、最悪の場合(関数ポインター)、正しいシグネチャの関数をコードベース全体で検索する必要があり、仮想呼び出しは少しだけ優れています(正しい名前と署名の関数を検索し、継承に関連する)。しかし、保守性は読み取り専用であることではありません。
ベンフォークト

14

switchステートメントから離れます...

このタイプのswitchステートメントは、Open Closed Principleに違反するため、ペストのように避ける必要があります。新しいコードを追加するだけでなく、新しい機能を追加する必要がある場合、チームは既存のコードを変更する必要があります。


11
それには注意が必要です。操作(関数/メソッド)とタイプがあります。新しい操作を追加する場合、switchステートメントの1か所でコードを変更するだけで(switchステートメントで新しい関数を1つ追加)、オブジェクト指向の場合はそのメソッドをすべてのクラスに追加する必要があります(openに違反します) /閉じた原則)。新しい型を追加する場合、すべてのswitchステートメントに触れる必要がありますが、OOの場合は、もう1つのクラスを追加するだけです。したがって、十分な情報に基づいた決定を下すには、既存の型にさらに操作を追加するのか、それとも型を追加するのかを知る必要があります。
スコットホイットロック

3
OCPに違反することなく、オブジェクト指向パラダイムの既存の型にさらに操作を追加する必要がある場合、それがビジターパターンの目的だと思います。
スコットホイットロック

3
@Martin-名前を呼び出す場合は呼び出しますが、これはよく知られているトレードオフです。RC MartinのClean Codeを紹介します。彼はOCPに関する彼の記事を再訪し、私が上で概説したことを説明します。将来のすべての要件に合わせて同時に設計することはできません。操作を追加する可能性が高いか、タイプを追加する可能性が高いかを選択する必要があります。オブジェクト指向は型の追加を好みます。オペレーションをクラスとしてモデル化する場合、OOを使用してオペレーションを追加できますが、それはビジターパターンに入り込み、独自の問題(特にオーバーヘッド)があるようです。
スコットホイットロック

8
@マーティン:パーサーを書いたことがありますか?先読みバッファの次のトークンをオンにする大きなスイッチケースを使用することは非常に一般的です。これらのスイッチを次のトークンへの仮想関数呼び出しに置き換えることもできますが、これは保守の悪夢です。まれですが、実際にはスイッチケースがより良い選択であることがあります。これは、読み取り/変更を行うコードを近接して保持するためです。
ニキエ

1
@Martin:「never」、「ever」、「Poppycock」などの言葉を使用したので、最も一般的なケースだけでなく、例外なくすべてのケースについて話していると想定していました。(そして、BTW:人々はまだパーサーを手書きで書いています。例えば、CPythonパーサーはまだ手書きで書かれています、IIRC。)
nikie

8

私は、大規模なswitchステートメントによって操作される大規模な有限状態マシンとして知られる悪夢を生き延びました。さらに悪いことに、私の場合、FSMは3つのC ++ DLLにまたがっていて、コードがCに精通した誰かによって書かれたのは非常に単純でした。

気にする必要があるメトリックは次のとおりです。

  • 変更の速度
  • 問題が発生したときの発見の速度

そのDLLのセットに新しい機能を追加するタスクが与えられ、3つのDLLを1つの適切なオブジェクト指向DLLとして書き直すのに時間がかかることを管理者に納得させることができました。 ju審員は、すでに存在していたものにソリューションをリグします。書き換えは、新しい機能をサポートするだけでなく、はるかに簡単に拡張できるため、大成功でした。実際、何かを壊さないように通常1週間かかるタスクは、数時間かかることになります。

では、実行時間はどうでしょうか?速度の増加も減少もありませんでした。公平を期すために、システムドライバーによってパフォーマンスが抑制されたため、オブジェクト指向ソリューションが実際に遅い場合、それを知りません。

オブジェクト指向言語の大規模なswitchステートメントの何が問題になっていますか?

  • プログラム制御フローは、それが属するオブジェクトから取り除かれ、オブジェクトの外部に配置されます
  • 外部制御の多くのポイントは、レビューが必要な多くの場所に変換されます
  • 特にスイッチがループ内にある場合、状態が保存される場所は不明です
  • 最も速い比較は、まったく比較しないことです(優れたオブジェクト指向設計で多くの比較の必要性を回避できます)。
  • オブジェクトを反復処理し、すべてのオブジェクトで常に同じメソッドを呼び出す方が、オブジェクトタイプまたはタイプをエンコードする列挙に基づいてコードを変更するよりも効率的です。

8

私はパフォーマンスの議論を買いません。コードの保守性がすべてです。

しかし、時々、巨大なswitchステートメントは、抽象基本クラスの仮想関数をオーバーライドする小さなクラスの束よりも保守しやすい(コードが少ない)場合があります。たとえば、CPUエミュレーターを実装する場合、各命令の機能を個別のクラスに実装するのではなく、オペコードの巨大なswtichに挿入するだけで、より複雑な命令のヘルパー関数を呼び出すことができます。

経験則:切り替えがTYPEで何らかの形で実行される場合、おそらく継承と仮想関数を使用する必要があります。固定タイプのVALUE(たとえば、上記の命令オペコード)で切り替えが実行される場合、そのままにしておいてかまいません。


5

私を納得させることはできません:

void action1()
{}

void action2()
{}

void action3()
{}

void action4()
{}

void doAction(int action)
{
    switch(action)
    {
        case 1: action1();break;
        case 2: action2();break;
        case 3: action3();break;
        case 4: action4();break;
    }
}

以下よりも大幅に高速です。

struct IAction
{
    virtual ~IAction() {}
    virtual void action() = 0;
}

struct Action1: public IAction
{
    virtual void action()    { }
}

struct Action2: public IAction
{
    virtual void action()    { }
}

struct Action3: public IAction
{
    virtual void action()    { }
}

struct Action4: public IAction
{
    virtual void action()    { }
}

void doAction(IAction& actionObject)
{
    actionObject.action();
}

さらに、OOバージョンは保守性が向上しています。


8
いくつかのことや、より少ない量のアクションのために、オブジェクト指向バージョンはずっとおかしいです。IActionの作成に値を変換するには、何らかの種類のファクトリーが必要です。多くの場合、代わりにその値をオンにするだけで読みやすくなります。
ザンリンクス

@Zan Lynx:あなたの議論はあまりにも一般的です。IActionオブジェクトの作成は、アクション整数を取得するのと同じくらい困難です。したがって、ジェネリックにすることなく、実際の会話を行うことができます。電卓を検討してください。ここでの複雑さの違いは何ですか?答えはゼロです。すべてのアクションが事前に作成されているため。ユーザーからの入力を取得し、そのアクションを既に実行しています。
マーティンヨーク

3
@Martin:GUI計算機アプリを想定しています。代わりに、組み込みシステムでC ++用に作成されたキーボード電卓アプリを見てみましょう。これで、ハードウェアレジスタからのスキャンコード整数が得られました。さて、それほど複雑ではないものは何ですか?
ザンリンクス

2
@Martin:整数->ルックアップテーブル->新しいオブジェクトの作成->仮想関数の呼び出しが整数->スイッチ->関数よりも複雑なのはわかりませんか?どうして見えないの?
ザンリンクス

2
@マーティン:たぶん私は。それまでの間、ルックアップテーブルなしで整数からaction()を呼び出すためにIActionオブジェクトを取得する方法を説明してください。
ザンリンクス

4

彼は、結果のマシンコードがおそらくより効率的であることは正しいです。コンパイラー・エッセンシャルは、switchステートメントを一連のテストとブランチに変換しますが、これは比較的少ない命令です。より抽象化されたアプローチから生じるコードには、より多くの命令が必要になる可能性が高くなります。

ただし、特定のアプリケーションがこの種のマイクロ最適化を心配する必要がない場合、またはそもそも.netを使用しない場合はほぼ間違いありません。非常に制約のある組み込みアプリケーション、またはCPU集中型の作業が不足している場合は、常にコンパイラに最適化を処理させる必要があります。クリーンで保守可能なコードの作成に専念します。これは、ほとんどの場合、実行時間の10分の1ナノ秒よりはるかに大きな価値があります。


3

switchステートメントの代わりにクラスを使用する主な理由の1つは、switchステートメントが多くのロジックを持つ1つの巨大なファイルにつながる傾向があることです。これは、メンテナンスの悪夢であると同時に、ソース管理の問題でもあります。異なる小さなクラスファイルではなく、その巨大なファイルをチェックアウトして編集する必要があるためです。


3

OOPコードのswitchステートメントは、クラスが欠落していることを強く示しています

両方の方法で試して、いくつかの簡単な速度テストを実行してください。違いは重要ではありません。それらがあり、コードがタイムクリティカルである場合、switchステートメントを保持します


3

通常、私は「時期尚早な最適化」という言葉を嫌いますが、これはそれを嫌っています。Knuth goto重要な領域でコードを高速化するためにステートメントを使用するようにプッシュするという文脈でこの有名な引用を使用したことは注目に値します。それが重要です:クリティカルパス。

彼は、gotoコードを高速化するために使用することを提案していましたが、クリティカルではないコードのハンチや迷信に基づいて、この種のことをしたいプログラマーに対して警告していました。

コードベース全体でswitch可能な限り均一にステートメントを支持すること(重い負荷が処理されるかどうか)は、Knuthが「最適化された」 「1ポンド以上の節約を試みた結果、デバッグの悪夢に変わったコード。このようなコードは、そもそも効率的なことは言うまでもなく、めったにメンテナンスできません。

彼は正しいですか?

彼は非常に基本的な効率の観点から正しいです。私の知る限り、コンパイラーはswitchステートメントよりもオブジェクトと動的ディスパッチを含む多態性コードを最適化できません。そのようなコードはコンパイラーのオプティマイザーバリアとして機能する傾向があるため、LUTや多相コードからインラインコードへのテーブルにジャンプすることはありません(動的ディスパッチが実行されるまで、発生します)。

ジャンプテーブルの観点からこのコストを考えるのではなく、最適化の障壁の観点から考えるのがより便利です。ポリモーフィズムの場合、呼び出しBase.method()は、method仮想であり、シールされておらず、オーバーライドできる場合、実際にどの関数が呼び出されるかをコンパイラが認識できないようにします。どの関数が実際に事前に呼び出されるのかがわからないため、どの関数が呼び出されるのか実際にはわからないため、関数呼び出しを最適化して最適化の決定にさらに情報を活用することはできませんコードがコンパイルされている時間。

オプティマイザーは、関数呼び出しを覗き込んで、呼び出し元と呼び出し先を完全にフラット化するか、少なくとも呼び出し元を最適化して呼び出し先と最も効率的に作業できる最適化を行うことができます。どの関数が実際に事前に呼び出されるのかわからない場合、それはできません。

彼はただお尻を話しているだけですか?

このコストは、多くの場合1セントに相当しますが、特に拡張性が必要な場所では、これを一律に適用されるコーディング標準に変えることを正当化するのは一般的に非常に愚かなことです。それが真正の時期尚早なオプティマイザーで注意したい主なものです:彼らはマイナーなパフォーマンスの懸念を、保守性をまったく考慮せずにコードベース全体に均一に適用されるコーディング標準に変えたいと思っています。

私はそのうちの1人であるため、受け入れられた回答で使用されている「古いCハッカー」の引用には少し腹を立てます。非常に限られたハードウェアから何十年もコーディングを行ってきたすべての人が、時期尚早なオプティマイザーになったわけではありません。それでも私はそれらにも出会い、一緒に仕事をしました。しかし、これらのタイプは、分岐の予測ミスやキャッシュミスのようなものを測定することはありません。彼らはより良く知っていると考え、今日では当てはまらず、時には当てはまらない迷信に基づいた複雑な生産コードベースで非効率の概念に基づいています。パフォーマンスが重要な分野で真に働いた人は、効果的な最適化が効果的な優先順位付けであることをよく理解します。そして、保守性を低下させるコーディング標準を一般化してペニーを節約しようとすることは非常に非効率的な優先順位付けです。

非常にタイトでパフォーマンスが重要なループで10億回と呼ばれる、それほど多くの作業を行わない安価な機能がある場合、ペニーは重要です。その場合、1000万ドルの節約になります。体だけで数千ドルかかる2回呼び出される機能がある場合は、1ペニーを剃る価値はありません。車の購入中にペニーをめぐって時間を費やすのは賢明ではありません。製造業者から100万缶のソーダを購入している場合、1セント硬貨以上の値引きをする価値があります。効果的な最適化の鍵は、これらのコストを適切なコンテキストで理解することです。すべての購入でペニーを節約しようとし、他の誰もが購入しているものに関係なくペニーをハグしようとする人は、熟練したオプティマイザーではありません。


2

同僚はパフォーマンスに非常に関心を持っているようです。場合によっては、大規模なケース/スイッチ構造がより高速に実行されることもありますが、OOバージョンとスイッチ/ケースバージョンでタイミングテストを実行して実験を行うことを願っています。OOバージョンはコードが少なく、追跡、理解、保守が簡単だと思います。私は最初にOOバージョンについて議論し(メンテナンス/読みやすさは最初により重要であるため)、OOバージョンに重大なパフォーマンスの問題があり、スイッチ/ケースが著しい向上。


1
タイミングテストに加えて、コードダンプはC ++(およびC#)メソッドのディスパッチがどのように機能するかを示すのに役立ちます。
-S.ロット

2

誰も言及していないポリモーフィズムの保守性の利点の1つは、常に同じケースのリストを切り替えると、継承を使用してコードをよりうまく構造化できることですが、場合によってはいくつかのケースが同じ方法で処理され、じゃない

例えば。あなたが間を切り替えている場合DogCatおよびElephant、時にはDogCat同じケースを持って、あなたはそれらの両方が抽象クラスを継承することができますDomesticAnimalし、抽象クラスでこれらの機能を置きます。

また、多相性を使用しない場所の例として、パーサーを使用している人がいることに驚きました。ツリーのようなパーサーの場合、これは間違いなく間違ったアプローチですが、各行がある程度独立しているアセンブリのようなものがあり、残りの行の解釈方法を示すオペコードで始まる場合、私は完全にポリモーフィズムを使用しますそして工場。各クラスは、ExtractConstantsまたはのような関数を実装できますExtractSymbols。私はこのアプローチをおもちゃのBASICインタープリターに使用しました。


スイッチは、デフォルトのケースを介して、動作も継承できます。"... extends BaseOperationVisitor"が "default:BaseOperation(node)"になります
サミュエルダニエルソン

0

「私たちは小さな効率を忘れて、時間の約97%を言うべきです。時期尚早な最適化はすべての悪の根源です」

ドナルド・クヌース


0

これが保守性に悪いことではなかったとしても、パフォーマンスに良いとは思わない。仮想関数呼び出しは、1つの余分な間接指定(switchステートメントの最適なケースと同じ)にすぎないため、C ++でもパフォーマンスはほぼ同じです。すべての関数呼び出しが仮想であるC#では、両方のバージョンで同じ仮想関数呼び出しのオーバーヘッドがあるため、switchステートメントは悪化するはずです。


1
「ない」が見つからない?C#では、すべての関数呼び出しが仮想であるとは限りません。C#はJavaではありません。
ベンフォークト

0

ジャンプテーブルに関するコメントに関する限り、同僚は彼の裏側から話をしていません。しかし、それを使って悪いコードを書くことを正当化することは、彼が失敗するところです。

C#コンパイラは、わずかなケースのswitchステートメントを一連のif / elseに変換するため、if / elseを使用するよりも高速ではありません。コンパイラは、大きなswitchステートメントを辞書(同僚が参照しているジャンプテーブル)に変換します。詳細については、トピックに関するスタックオーバーフローの質問に対するこの回答を参照してください

大きなswitchステートメントは読みにくく、保守が困難です。「ケース」と関数の辞書は読みやすくなっています。それがスイッチが変わることになるので、あなたとあなたの同僚は辞書を直接使うことを勧められます。


0

彼は必ずしも彼のお尻から話しているわけではありません。少なくともCおよびC ++ switchステートメントでは、テーブルをジャンプするように最適化できますが、ベースポインターにのみアクセスする関数で動的ディスパッチが発生するのを見たことはありません。少なくとも後者では、よりスマートなオプティマイザーが周囲のコードを見て、ベースポインター/参照を介して仮想関数呼び出しから使用されているサブタイプを正確に把握する必要があります。

その上、動的ディスパッチは「最適化の障壁」として機能することが多く、コンパイラーは多くの場合、コードをインライン化できず、レジスターを最適に割り当てて、スタックの流出やその他すべての凝ったものを最小限に抑えることができません。仮想関数は、ベースポインターを介して呼び出され、インライン化され、その最適化マジックがすべて実行されます。オプティマイザを非常に賢くして、間接的な関数呼び出しを最適化しようとするかどうかはわかりません。なぜなら、特定の呼び出しスタック(呼び出しfoo->f()が持つ関数)呼び出すものとはまったく異なるマシンコードを生成するbar->f() ベースポインターを介して、その関数を呼び出す関数は、コードの2つ以上のバージョンを生成する必要があります。生成されるマシンコードの量は爆発的です。おそらく、トレースJITではそれほど悪くありません。ホット実行パスをトレースしているときにコードをその場で生成します)。

しかし、多くの回答が繰り返しswitch述べられているように、それはわずかな量だけ手渡しで速くなったとしても、それが大量のステートメントを支持する悪い理由です。さらに、マイクロ効率に関しては、通常、分岐やインライン化などの優先順位は、メモリアクセスパターンなどの優先順位に比べてかなり低いです。

そうは言っても、私はここに飛び込んで、珍しい答えを出しました。をswitch実行する必要がある場所が1つしかないことを確実に知っている場合にのみ、ポリモーフィックソリューションに対するステートメントの保守性を主張しますswitch

代表的な例は、中央イベントハンドラーです。その場合、通常、イベントを処理する場所は多くなく、1つだけです(「中央」である理由)。これらの場合、多態性ソリューションが提供する拡張性の恩恵は受けません。ポリモーフィックなソリューションは、アナロジー的なswitchステートメントを実行する場所が多数ある場合に役立ちます。確実に1つしかないことを知っているswitch場合、15のケースを持つステートメントは、オーバーライドされた関数とインスタンス化するファクトリーを持つ15のサブタイプによって継承されたベースクラスを設計するよりもはるかに簡単になります。システム全体で。そのような場合、新しいサブタイプを追加することは、case1つの関数にステートメントを追加するよりもずっと面倒です。どちらかといえば、パフォーマンスではなく保守性について議論します。switch 拡張性の恩恵をまったく受けないこの特異なケースのステートメント。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.