22

仮想代入演算子の実装で遊んでいる間、私はおかしな振る舞いで終わりました。g ++ 4.1、4.3、およびVS 2005は同じ動作を共有するため、これはコンパイラの不具合ではありません。

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

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 &)
}

その結果、仮想演算子=は、実際の派生オブジェクト([1])を介して呼び出されたときに演算子のベースバージョンを呼び出すことにより、同じシグネチャを持つ他の仮想関数とは異なる動作をします([1]と比較して[0])。 )または派生参照([3])は、ベース参照([2])を介して呼び出された場合、または左辺値または右辺値のいずれかがベース参照であり、他の派生参照([4]、 [5])。

この奇妙な振る舞いに対する賢明な説明はありますか?

4

5 に答える 5

14

手順は次のとおりです。

[1] を

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

その後、物事は期待どおりに機能します。Derived次のような、自動生成された代入演算子があります。

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;
}

あなたの例では、コンパイラはそれを推測するのに十分な情報を持っているaのでbDerivedあなたのものを呼び出す上記の自動生成された演算子を使用することを選択します。これで [1] が得られました。b私のポインターキャストは、コンパイラーにあなたのやり方でそれを強制しDerivedますBase.

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

于 2009-06-09T10:58:06.323 に答える
5

この場合、3 つの operator= があります。

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

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

于 2009-06-09T11:18:44.350 に答える
4

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

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 &)

==>内部 ==> (Object->VTable[代入演算子]) オブジェクトが属するクラスの VTable の代入演算子のエントリを取得し、メソッドを呼び出します。

于 2009-06-09T10:58:51.597 に答える
3

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

operator= が仮想化される詳細については、このリンクを確認してください。

于 2009-06-09T11:13:06.450 に答える