Во время игры с реализацией виртуального оператора присваивания я закончил смешное поведение. Это не сбой компилятора, поскольку 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 или r являются базовыми, а другое - производной ссылкой ([ 4], [5]).
Есть ли разумное объяснение этому нечетному поведению?
Ответ 1
Вот как это делается:
Если я изменил [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
.
Другие результаты можно объяснить одинаково.
Ответ 2
В этом случае три оператора =:
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 = (Derived &) не может быть вызван.
Ответ 3
Пользовательский оператор присваивания не определен для класса Derived. Следовательно, компилятор синтезирует один и внутренний оператор присваивания базового класса вызывается из этого синтезированного оператора присваивания для класса Derived.
virtual Base& operator=( Base const & ) //is not assignment operator for Derived
Следовательно, a = b; // [1] outputs: Base::operator=(Base const &)
В классе Derived оператор присваивания класса Base был переопределен и, следовательно, переопределенный метод получает запись в виртуальной таблице класса Derived. Когда метод вызывается через ссылку или указатели, тогда метод переопределения класса Derived вызывается из-за разрешения входа VTable во время выполнения.
ba = bb; // [2] outputs: Derived::operator=(Base const &)
== > внутренне == > (Object- > VTable [Assignement operator])
Получить запись для оператора присваивания в VTable класса, к которому принадлежит объект, и вызвать метод.
Ответ 4
Если вы не предоставили подходящий operator=
(то есть правильный тип возврата и аргументов), то по умолчанию operator=
предоставляется компилятор, который перегружает любой пользовательский. В вашем случае это вызовет Base::operator= (Base const& )
перед копированием членов Derived.
Обратитесь к этой ссылке для получения подробной информации о том, что оператор = создается виртуальным.
Ответ 5
Причина наличия компилятора в стандартном назначении operator=
.
Что вызывается в сценарии a = b
, и, поскольку мы знаем, что по умолчанию внутренне вызывает оператор базового присвоения.
Дополнительное объяснение о виртуальном назначении можно найти по адресу: fooobar.com/info/95014/...