Проблема с наследованием оператора = в C ++ - PullRequest
16 голосов
/ 07 октября 2010

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

#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;
}

Ответы [ 5 ]

30 голосов
/ 07 октября 2010

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

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

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

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

b = c;

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

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

using A::operator =;

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

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

b.A::operator =(c);
3 голосов
/ 07 октября 2010

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

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

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

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

1 голос
/ 07 октября 2010

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

 (A&)(*(&b)) = (A&)(*(&c))
1 голос
/ 07 октября 2010

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

B& operator=(B const &);

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

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

B& operator=(C const &);
.
1 голос
/ 07 октября 2010

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

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

...