Проблема с наследованием оператора = в С++

У меня возникают проблемы с наследованием оператора =. Почему этот код не работает, и как лучше всего его исправить?

#include <iostream>

class A
{
public:
    A & operator=(const A & a)
    {
        x = a.x;
        return *this;
    }

    bool operator==(const A & a)
    {
        return x == a.x;
    }

    virtual int get() = 0; // Abstract

protected:
    int x;
};

class B : public A
{
public:
    B(int x)
    {
        this->x = x;
    }

    int get()
    {
        return x;
    }
};

class C : public A
{
public:
    C(int x)
    {
        this->x = x;
    }

    int get()
    {
        return x;
    }
};

int main()
{
    B b(3);
    C c(7);
    printf("B: %d C: %d B==C: %d\n", b.get(), c.get(), b==c);

    b = c; // compile error
    // error: no match for 'operator= in 'b = c'
    // note: candidates are B& B::operator=(const B&)

    printf("B: %d C: %d B==C: %d\n", b.get(), c.get(), b==c);
    return 0;
}

Ответы

Ответ 1

Если вы не объявляете оператор присваивания копии в классе, компилятор объявит его для вас неявно. Неявно объявленный оператор копирования-присваивания скроет любые унаследованные операторы присваивания (прочитайте о "скрытии имени" на С++), что означает, что любые унаследованные операторы присваивания станут "невидимыми" для процесса проверки неквалифицированного имени (что происходит, когда вы делаете b = c), если вы не предпримете определенные шаги для их "отображения".

В вашем случае класс B не имеет явно объявленного оператора присваивания копий. Это означает, что компилятор объявит

B& B::operator =(const B&)

неявно. Он скроет оператор, унаследованный от A. Строка

b = c;

не компилируется, потому что единственным кандидатом здесь является неявно объявленный B::operator = (компилятор рассказал об этом уже); все остальные кандидаты скрыты. И поскольку c не конвертируется в B&, указанное присваивание не компилируется.

Если вы хотите, чтобы ваш код компилировался, вы можете использовать use-declaration для отображения наследуемого A::operator = путем добавления

using A::operator =;

к определению класса B. Теперь код будет скомпилирован, хотя он не будет хорошим стилем. Вы должны иметь в виду, что в этом случае назначение b = c вызывает A::operator =, которое назначает только части A объектов. (Но, по-видимому, это ваше намерение.)

В качестве альтернативы, в таких случаях вы всегда можете работать со скрытием имени, используя квалифицированную версию имени

b.A::operator =(c);

Ответ 2

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

Вы хотите, чтобы это произошло, хотя сейчас это досадно. Это предотвращает использование кода, как вы писали от работы. Вы не хотите, чтобы такой код работал, потому что он допускает несвязанные типы (у B и C есть общий предок, но единственными важными отношениями в наследовании являются отношения parent- > child- > grandchild, а не отношения сестры), которые должны быть назначены одному другой.

Подумайте об этом с точки зрения ISA. Если a Car разрешено назначать Boat только потому, что они оба Vehicles?

Чтобы сделать что-то вроде этой работы, вы должны использовать шаблон Конверт/Письмо. Конверт (aka handle) - это специализированный класс, который должен выполнять только экземпляр некоторого класса, который получен из определенного базового класса (буквы). Ручка перенаправляет все операции, но присваивает содержащемуся объекту. Для присваивания он просто заменяет экземпляр внутреннего объекта построенной по копированию (с использованием метода клонирования типа (aka virtual constructor)) назначенного из объекта.

Ответ 3

Вы не можете назначить по иерархии, как это: B и C - разные подклассы A. Вы можете назначить B в B или C на C, но не на C на B или наоборот.

Вы, вероятно, захотите реализовать operator= в B и C, делегируя часть A присваивания A::operator=, прежде чем попробовать это. В противном случае B- и C-специфические части этих классов будут потеряны в задании.

Ответ 4

Обычно оператор = определяется в B как

B& operator=(B const &);

Поскольку B не является однозначной и доступной базой 'C', преобразование из C в B не допускается компилятором.

Если вы действительно хотите присвоить "C" "B", "B" должен поддерживать соответствующий оператор присваивания как

B& operator=(C const &);

Ответ 5

(Возможно, это не исправление и, вероятно, не то, что вы должны делать) НО... там вы можете заставить проблему, если вы действительно должны:

 (A&)(*(&b)) = (A&)(*(&c))