2012-05-11 5 views
1

次のように私は現在、C++インタフェース変換スイッチ

void foo() ... 

とimplementionsたくさんの

void A::foo(); 
void B::foo(); 

現在のfooが定義されている

struct Wrapper 
{ 
    void foo() 
    { 
     if(state == STATE_A) a.foo(); 
     else if(state == STATE_B) b.foo(); 
    } 
    union { 
     A a; 
     B b; 
    }; 
    State state; 
} 
//.... 
Wrapper a(STATE_A, existingA); 
Wrapper b(STATE_B, existingB); 
a.foo(); b.foo(); 

がありますこれを行うクリーナーの方法?私は関数のような複数のfoo()とクラスのような複数のA/Bを持っています。すべてのケースを記述するのが面倒です。私は仮想関数を使用することはできません

注(これは千万+処刑/秒で... N^5ループ内で実行されます)。私はコンパイラがこれをインライン化して欲しい。

私は一緒にAさん、Bさん、などを収集し、データ指向のファッションでそれらを計算すると考えているが、残念ながら私は(これはアルゴリズムの懸念に)

+1

「すべてのケースを記述するのが面倒だ/エラーが発生しやすい」ということです。言語機能を破棄して、自分で直接コーディングすることを選択すると、そのようになります。だからこそ、これらの言語機能が最初に存在するのです。誰かが定型文を書いてうんざりしていたからです。 –

+1

追加するには、これは仮想ディスパッチを使用するよりもどのように優れていますか?これがif-elseチェーンではなくインデックス(配列参照など)で最適化されていても、これは(ほとんどのコンパイラの実装に関して)仮想ディスパッチを使用するのと同じです。つまり、STATE_Xはvtableを表し、Wrapper :: fooはvtableのlookupと似ています... –

+3

実際に仮想関数を使用して簡単な実装を測定しましたか? 3-5「if」条件が仮想ファンクションコールのコストと容易に一致する可能性があります。そして、関数の成長が十分に大きい場合、インラインが実際に起こるという保証はありません。 –

答えて

2

私はコンパイラがインライン化したいことを行うことはできませんこれは、ハード。起こることはないだろう

あなたはランタイムポリモーフィズムを使用しています。定義では、コンパイラは、呼び出し時に呼び出されますどの機能を知ることができません。あなたは、それを手動で行うか、またはコンパイラにそれをさせるかにかかわらず、仮想ディスパッチのために支払うつもりです。

アブソリュートは、メンバー関数の呼び出し時に得られます。それはまだ「インライン」の部分に到達するために、メモリアクセス(「タイプ」をフェッチ)に基づいて条件分岐を行う必要があります。そして、あなたが追加したすべての新しい「状態」は、そのブランチに条件を追加します。せいぜい、これは状態テーブルになります。これは仮想関数ポインタと同じでもありません。メモリアドレスからフェッチし、それを使用して特定のコードに分岐します。

ちょうどvtableポインタのように、あなただけがあなたの時間を無駄にして、コンパイラがあなたのためにできることを実装しました。

profile私はあなたの手書きの方法がコンパイラに勝つことができると仮定するのではなく、これを強くお勧めします。


あなたがあなたの代わりにboost.variantし、適切な訪問者を使用する必要があり、言語レベルの多型を放棄することを決定した場合。あなたのコードは次のようになります。

typedef boost::variant<A, B> Wrapper; 

struct FooVisitor : public boost::static_visitor<> 
{ 
    template <typename T> void operator()(T &t) {t.foo()}; 
}; 

あなたが呼び出したい機能毎FooVisitorを行う必要があります。それを呼び出すには、次の操作を行います。

Wrapper a = existingA; 
boost::apply_visitor(FooVisitor(), a); 

を明らかに、あなたは簡単な関数でそれをラップすることができます

template<typename Visitor> 
void Call(Wrapper &a) {boost::apply_visitor(Visitor(), a);} 

void CallFoo(Wrapper &a) {boost::apply_visitor(FooVisitor(), a);} 

確かに、あなたはこれらのテンプレート全体の家族を作ることができます

パラメータの受け渡しは許可されていません(訪問者自身にパラメータを格納する必要があります)が、戻り値を持つことができます(戻り値のタイプはboost::static_visitor<Typename_Here> visiの宣言に入れなければなりません)。トル)。

また、boost::variantオブジェクトには値のセマンティクスがあるため、コピーは内部オブジェクトをコピーします。また、boost::get()の構文を使用して実際のタイプを取得することもできますが、が本当にに必要な場合を除き、私はそれを提案しません。ただ訪問者を使用してください。

+0

これは間違いなくプロファイルする必要があります。 – jameszhao00

+0

また、私は現在、連続して割り当てられたメモリにWrapperを保持しています。私は新しいプレースメントを使用し、多相オブジェクトを保持しているmax(sizeof(A)、sizeof(B))サイズのものを持っていますが、その後も少し醜いものになります。 – jameszhao00

1

2つの選択肢があります。コンパイル時に関数の選択を行うことも、実行時に行うこともできます。実行時には、既存の仮想メカニズムよりもうまく行かないでしょう。コンパイル時には、使用するタイプごとに異なるコードが必要ですが、テンプレートを使用してプロセスを自動化することができます。もちろん

template<typename T> 
struct Wrapper 
{ 
    void foo() 
    { 
     t.foo(); 
    } 
    T t; 
}; 

この例では、高度に抽象化されて、私は直接Wrapperクラスとテンプレートの種類を使用しての間に違いを見ることができません。より良い答えを得るには、あなたの例を少しでも強調しなければなりません。

関連する問題