2010-10-07 4 views
15

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

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

ответ

30

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

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

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

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

b = c; 

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

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

using A::operator =; 

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

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

b.A::operator =(c); 
+0

Когда вы скажете, что это скроет, я вполне уверен, что оператор присваивания по умолчанию назначит часть «А», используя перегрузку пользователя. Тем не менее, он не позволяет вам назначать что-либо, что происходит от A, поэтому код OP не компилируется. – CashCow

+0

@CashCow: Да, вы правы. Правильный способ сказать, что имя унаследованного 'operator =' становится невидимым для * неквалифицированного поиска имени *. Компилятор, конечно, все еще знает об этом операторе и все еще использует его в других контекстах. – AnT

3

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

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

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

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

+0

Это не совсем неразумно хотите скопировать базу субобъектом из одного производного класса к другому. Подумайте о создании заказа из цитаты. Они не одного типа, но они содержат целую кучу информации о клиенте (имя/адрес/и т. Д.) И заказанные продукты, но ни один из них не является истинным надмножеством другого (у заказа нет даты истечения срока действия котировки, у котировки нет Платежная информация). Конечно, функция для этого должна быть нормальной функцией, а не 'operator =', и тогда не было бы никакой сгенерированной компилятором версии, скрывающей ее от производных классов. –

1

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

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

+0

Это не причина, по которой код не работает. – Omnifarious

1

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

B& operator=(B const &); 

Так как B не является однозначным и доступной базы «C», преобразование из С в Б не допускается компилятором.

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

B& operator=(C const &); 
1

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

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