Почему виртуальное присвоение ведет себя иначе, чем другие виртуальные функции с той же сигнатурой?

StackOverflow https://stackoverflow.com/questions/969232

Вопрос

Играя с внедрением виртуального оператора присваивания, я закончил с забавным поведением.Это не сбой компилятора, поскольку g ++ 4.1, 4.3 и VS 2005 ведут себя одинаково.

По сути, виртуальный 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 &)
}

В результате виртуальный operator= ведет себя иначе, чем любая другая виртуальная функция с той же сигнатурой ([0] по сравнению с [1]), вызывая базовую версию оператора при вызове через реальные производные объекты ([1]) или производные ссылки ([3]), в то время как он работает как обычная виртуальная функция при вызове через базовые ссылки ([2]), или когда либо lvalue, либо rvalue являются базовыми ссылками, а другая - производной ссылкой ([4],[5]).

Есть ли какое-нибудь разумное объяснение этому странному поведению?

Это было полезно?

Решение

Вот как это происходит:

Если я изменю [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 и b относятся к типу Derived и поэтому они предпочитают использовать автоматически сгенерированный выше оператор, который звонит вашему.Вот как вы получили [1].Мое приведение указателя заставляет компиляторы делать это по-вашему, потому что я говорю компилятору "забыть", что b относится к типу Derived и поэтому он использует Base.

Другие результаты могут быть объяснены таким же образом.

Другие советы

В этом случае существует три operator=:

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

Это объясняет, почему это выглядит так, как будто Base::operator=(Base const&) вызывается "виртуально" в случае [1].Он вызывается из версии, сгенерированной компилятором.То же самое относится и к случаю [3].В случае 2 аргумент правой части 'bb' имеет тип Base& , поэтому Derived::operator=(Производный&) не может быть вызван.

Для производного класса не определен пользовательский оператор присваивания.Следовательно, компилятор синтезирует один и вызывается внутренний оператор присваивания базового класса из этого синтезированного оператора присваивания для производного класса.

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 класса, к которому принадлежит объект, и вызовите метод.

Если вы не предоставите соответствующий operator= (т.е.правильные возвращаемые значения и типы аргументов), по умолчанию operator= предоставляется компилятором, который перегружает любой пользовательский.В вашем случае это вызовет Base::operator= (Base const& ) перед копированием производных элементов.

Проверьте это Ссылка подробнее о том, как operator= становится виртуальным.

Причина в том, что компилятор предоставляет назначение по умолчанию operator=.Который вызывается в сценарии a = b и, как мы знаем, default внутренне вызывает базовый оператор присваивания.

Более подробное объяснение о виртуальном назначении можно найти по адресу : https://stackoverflow.com/a/26906275/3235055

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top