Подтвердить что ты не робот

Как в этом примере работает работа по перегрузке оператора присваивания? Результат для меня неожиданен

Вот код, который я не понимаю:

class Base
{
public:
    Base(){}

    Base operator=(Base ob2)
    {
        std::cout << "Using Base operator=() " << '\n';
        return *this;
    }
};

class Derived : public Base
{
public:
    Derived(){}
    Derived operator=(Base ob2)
    {
        std::cout << "Using Derived operator=() " << '\n';
        return *this;
    }
};

int main()
{
    Derived derived1, derived2;
    Base base1;

    derived1 = derived2;  // Uses base operator=()

    derived1 = base1;  // Uses derived operator=()

    return 0;
}

Каковы языковые правила, которые определяют, что первое присваивание использует оператор базового класса, а второй оператор класса Derived?

Да, и я знаю, что обычно не объявляет оператора присваивания, как это. Вот почему я назвал его академическим.

4b9b3361

Ответ 1

Краткая версия. Разрешение перегрузки не выбрало Base::operator=(Base). Он выбрал неявно объявленный Derived::operator=(const Derived &), который вызывает Base::operator=(Base), чтобы копировать-присваивать подобъект базового класса.

Длинная версия со стандартными кавычками:

Во-первых, оператор присваивания копий определен в стандарте в §12.8 [class.copy]/p17:

Пользовательский оператор присваивания копий X::operator= является нестатической нематричной функцией-членом класса X с точно одним параметром типа X, X&, const X&, volatile X& или const volatile X&.

Во-вторых, если вы не предоставляете оператора присваивания копий, он всегда будет объявлен неявно. Из §12.8 [class.copy]/p18:

Если определение класса явно не объявляет назначение копии оператор, один объявляется неявно. Если определение класса объявляется конструктор перемещения или оператор назначения перемещения, неявно объявленный оператор назначения копирования определяется как удаленный; в противном случае определяется как дефолт (8.4). Последний случай устарел, если класс имеет объявленный пользователем конструктор копирования или объявленный пользователем деструктор. Неявно объявленный оператор назначения копирования для класс X будет иметь вид

X& X::operator=(const X&) 

если

  • каждый прямой базовый класс B of X имеет оператор присваивания копии, параметр которого имеет тип const B&, const volatile B& или B и
  • для всех нестатических членов данных X, которые имеют тип класса M (или его массив), каждый такой тип класса имеет назначение копии оператор которого имеет тип const M&, const volatile M& или M.

В противном случае оператор неявно объявленного копирования будет иметь форма

X& X::operator=(X&)

Обратите внимание, что одним из результатов этих правил является то, что (§12.8 [class.copy]/p24):

Поскольку оператор присваивания копирования/перемещения неявно объявляется для класс, если он не объявлен пользователем, назначение копирования/переноса базового класса оператор всегда скрыт соответствующим оператором присваивания производный класс.

Другими словами, разрешение перегрузки никогда не может выбрать оператор присваивания копий Base для назначения от одного Derived к другому. Он всегда скрыт и даже не входит в набор функций-кандидатов.

Наконец, в §12.8 [class.copy]/p28 предусмотрено, что

Неявно определенный оператор присваивания копий/перемещений для неединичного класса X выполняет поэтапное копирование/перемещение назначения своих подобъектов.

В случае вопроса нет оператора присваивания копии для Derived, поэтому он будет объявлен неявным образом как дефолт (поскольку Derived не имеет объявленного пользователем конструктора перемещения или оператора назначения перемещения). Этот неявный оператор присваивания копии будет выбран с помощью разрешения перегрузки и выполняет назначение копии, среди прочего, подобъекта базового класса, который вызывает оператор присваивания копии, который вы определили для Base.

Ответ 2

Когда вы создаете класс, компилятор неявно генерирует следующие функции (если вы не укажете некоторые из них, явно см. http://en.wikipedia.org/wiki/Special_member_functions):

  • конструктор по умолчанию
  • конструктор копирования
  • move constructor (С++ 11) Оператор присваивания
  • переместить оператор присваивания (с С++ 11)
  • деструктор

В вашем случае подпись оператора присваивания копии:

struct Foo {
   Foo &operator=( const Foo &f ); // either this
   Foo &operator=( Foo f ); // does not make much sense but will work too
};

Когда вы создаете оператор присваивания для класса Derived, вы явно не заменяете оператор неявного копирования, но создаете новый. Чтобы понять проблему, попробуйте изменить код:

class Derived : public Base
{
public:
    Derived(){}
    Derived &operator=(const Base &ob2)
    {
        std::cout << "Using Derived operator=(Base) " << '\n';
        return *this;
    }
    Derived &operator=(const Derived &ob2)
    {
        std::cout << "Using Derived operator=(Derived) " << '\n';
        return *this;
    }
};

Проблема должна стать очевидной.