Почему виртуальное присваивание ведет себя иначе, чем другие виртуальные функции той же подписи?
Играя с реализацией виртуального оператора присваивания, я закончил забавным поведением. Это не ошибка компилятора, так как 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]), вызывая базовую версию оператора при вызове через реальные производные объекты ([1]) или производные ссылки ([3]), в то время как он выполняет как обычная виртуальная функция при вызове через базовые ссылки ([2]), или когда либо lvalue, либо rvalue являются базовыми ссылками, а другая производная Ссылка ([4],[5]).
Есть ли разумное объяснение этому странному поведению?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
.И другие результаты могут быть объяснены таким же образом.
В этом случае существует три оператора=:
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:: = (Derived&) не может быть вызван.
Для производного класса не определен оператор присваивания, предоставляемый пользователем. Следовательно, компилятор синтезирует один и внутренне базовый оператор присваивания класса вызывается из этого синтезированного оператора присваивания для производного класса.
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[Assignment operator]) Получите запись для оператора присваивания в VTable класса, к которому принадлежит объект, и вызовите метод.
Если вы не можете предоставить соответствующий
operator=
(т. е. правильные типы возвращаемых значений и аргументов), то значение по умолчаниюoperator=
предоставляется компилятором, который перегружает любой пользовательский тип. В вашем случае он вызоветBase::operator= (Base const& )
перед копированием производных членов.Проверить это Ссылка подробнее о оператора= делаются виртуальные.
Причина в том, что компилятор предоставил назначение по умолчанию
operator=
. Что называется в сценарииa = b
и, как мы знаем, default внутренне вызывает базовый оператор присваивания.Более подробное описание виртуального назначения можно найти по адресу: https://stackoverflow.com/a/26906275/3235055