2009-06-09 15 views
21

仮想代入演算子を実装して遊んでいるうちに、面白い振る舞いで終了しました。 g ++ 4.1,4.3およびVS 2005は同じ動作を共有しているため、コンパイラの不具合ではありません。仮想割り当てが同じ署名の他の仮想関数と異なる動作をするのはなぜですか?

基本的に、仮想演算子=は、実際に実行されているコードに関して他の仮想関数とは異なる動作をします。

struct Base { 
    virtual Base& f(Base const &) { 
     std::cout << "Base::f(Base const &)" << std::endl; 
     return *this; 
    } 
    virtual Base& operator=(Base const &) { 
     std::cout << "Base::operator=(Base const &)" << std::endl; 
     return *this; 
    } 
}; 
struct Derived : public Base { 
    virtual Base& f(Base const &) { 
     std::cout << "Derived::f(Base const &)" << std::endl; 
     return *this; 
    } 
    virtual Base& operator=(Base const &) { 
     std::cout << "Derived::operator=(Base const &)" << std::endl; 
     return *this; 
    } 
}; 
int main() { 
    Derived a, b; 

    a.f(b); // [0] outputs: Derived::f(Base const &) (expected result) 
    a = b; // [1] outputs: Base::operator=(Base const &) 

    Base & ba = a; 
    Base & bb = b; 
    ba = bb; // [2] outputs: Derived::operator=(Base const &) 

    Derived & da = a; 
    Derived & db = b; 
    da = db; // [3] outputs: Base::operator=(Base const &) 

    ba = da; // [4] outputs: Derived::operator=(Base const &) 
    da = ba; // [5] outputs: Derived::operator=(Base const &) 
} 

効果は、仮想オペレータが=介して呼び出されたときにオペレータの基本バージョンを呼び出すことにより、同じシグネチャ([0] [1]と比較して)を有する任意の他の仮想関数とは異なる挙動を有することですbase参照([2])を介して呼び出されたとき、またはlvalueまたはrvalueのいずれかがBase参照であり、もう一方がBase参照であるときに、正規の仮想関数として実行されている間に、実際の派生オブジェクト([1])または派生参照派生参照([4]、[5])。

この奇妙な動作には分かりやすい説明がありますか?ここで

答えて

13

はそれが行く方法は次のとおりです。私は

a = *((Base*)&b); 

に[1]を変更した場合

、その後の事は、あなたが期待するように動作します。

あなたの例コンパイラで
Derived& operator=(Derived const & that) { 
    Base::operator=(that); 
    // rewrite all Derived members by using their assignment operator, for example 
    foo = that.foo; 
    bar = that.bar; 
    return *this; 
} 

abはタイプDerivedであることを推測するのに十分な情報を持っているので、彼らはそれを呼び出す上で自動的に生成された演算子を使用することを選択:次のようになりますDerivedで自動的に生成された代入演算子はありますあなた。それはあなたが[1]を得た方法です。 bはタイプDerivedであり、Baseを使用していることを「忘れる」とコンパイラーに指示するので、私のポインターキャスティングはあなたのやり方を強制します。

他の結果も同様に説明できます。

+3

にあります。ここでの推測はありません。ルールは非常に厳しいものです。 – MSalters

+0

ありがとう、実際の答えは(すでに3人が投稿したように)コンパイラ生成演算子= Derivedクラスのために暗黙的にBase :: operator =を呼び出すということです。私はこれを「受け入れられた答え」として最初のものとしてマークしています。 –

+0

'a = static_cast (b);'は、Cスタイルのキャストを避ける方法です(誤って再解釈キャストを行う危険があります) –

4

派生クラスに対してユーザー定義の代入演算子が定義されていません。したがって、コンパイラは、合成クラスの合成代入演算子から呼び出された、内部的に基本クラスの代入演算子を合成します。したがって

virtual Base& operator=(Base const &) //is not assignment operator for Derived 

、派生クラスでa = b; // [1] outputs: Base::operator=(Base const &)

、基本クラスの代入演算子はオーバーライドされており、従って、オーバーライドされたメソッドは、派生クラスの仮想テーブル内のエントリを取得します。メソッドが参照またはポインタ経由で呼び出されると、実行時にVTableのエントリ解決のために派生クラスのオーバーライドされたメソッドが呼び出されます。

ba = bb; // [2] outputs: Derived::operator=(Base const &) 

==>内部==>(オブジェクト - >仮想テーブル[Assignementオペレータ]) オブジェクトが属するクラスの仮想テーブルに代入演算子のエントリを取得し、メソッドを呼び出します。

3

適切なoperator=(つまり正しい戻り値と引数の型)を指定しなかった場合、デフォルトのoperator=がコンパイラによって提供され、ユーザー定義のものがオーバーロードされます。あなたのケースでは、Derivedメンバーをコピーする前にBase::operator= (Base const&)が呼び出されます。

オペレータ=仮想化の詳細については、linkをご確認ください。

5

は、この場合には= 3人のオペレータがあります。それは基本::演算子=(ベースconstの&)のように見える理由

Base::operator=(Base const&) // virtual 
Derived::operator=(Base const&) // virtual 
Derived::operator=(Derived const&) // Compiler generated, calls Base::operator=(Base const&) directly 

これは呼ばれて説明して「事実上」の場合では、[1]。これは、コンパイラで生成されたバージョンから呼び出されます。ケース[3]についても同様である。ケース2では、右側の引数 'bb'の型がBase &であるため、Derived :: operator =(Derived &)は呼び出すことができません。

2

コンパイラがデフォルトの割り当てoperator=を提供している理由があります。 シナリオではa = bと呼ばれ、デフォルトで内部コールベース割り当て演算子がわかっています。

仮想割り当ての詳細については、https://stackoverflow.com/a/26906275/3235055

関連する問題