C ++ Почему я не могу назначить базовый класс дочернему классу? - PullRequest
7 голосов
/ 11 февраля 2010

У меня есть такой код:

class Base
{
public:
  void operator = (const Base& base_)
  {
  }
};

class Child : public Base
{
public:

};

void func()
{
  const Base base;
  Child child;
  child = base;
}

Мой вопрос таков: поскольку Child наследуется от Base (следовательно, он должен наследовать оператор Base =), как получается, когда выражение

child = base;

выполнено, я получаю ошибку компилятора, такую ​​как:

>.\main.cpp(78) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const Base' (or there is no acceptable conversion)
1>        .\main.cpp(69): could be 'Child &Child::operator =(const Child &)'
1>        while trying to match the argument list '(Child, const Base)'

Мне нужно, чтобы класс Child распознал, что ему назначен базовый класс, и просто "автоматически" вызывает оператор его родителя =.

Как только я добавил этот код в класс Child

void operator = (const Base& base_)
{
  Base::operator=(base_);
}

тогда все собрано нормально. Хотя я не думаю, что это было бы хорошо, потому что, если у меня есть 5 разных классов, которые наследуются от Base, мне придется повторять один и тот же код в каждом производном классе.

ПРИМЕЧАНИЕ: Мое намерение скопировать Base в Child - просто скопировать элементы, которые общие , в оба Base и Child (что быть всеми членами Base). Даже после прочтения всех ответов ниже, я действительно не понимаю, почему C ++ не позволяет этого делать, особенно если в классе Base определен явный operator=.

Ответы [ 8 ]

13 голосов
/ 11 февраля 2010

Стандарт предоставляет причину вашего конкретного вопроса в 12.8 / 10 «Копирование объектов класса» (выделение добавлено):

Поскольку оператор присваивания копии неявно объявляется для класса, если он не объявлен пользователем, оператор присваивания копии базового класса всегда скрыт оператором присвоения копии производного класса (13.5.3) .

Так как существует неявно объявленное operator=(const Child&), когда компилятор выполняет разрешение поиска / перегрузки имени для Child::operator=(), сигнатура функции класса Base никогда даже не рассматривается (она скрыта).

8 голосов
/ 11 февраля 2010

Код ниже - это поведение, которое я хотел с самого начала, и оно компилируется,

class Base
{
public:
  void operator = ( const Base& base_)
  {
  }
};

class Child : public Base
{
};

void func()
{
  const Base base;

  Child child;

  child.Base::operator=(base);
}

Я никогда не знал, что вы можете явно назвать что-то вроде:

  child.Base::operator=(base);

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

6 голосов
/ 11 февраля 2010

Вы не можете назначить Base для Child, потому что Child не может быть Base. Чтобы использовать типичный пример с животным, вот что у вас есть:

const Animal animal;
Dog dog;
dog = animal;

Но животное может быть кошкой, и вы определенно не можете назначить кошку собаке.

Как говорится, вы не могли бы сделать это и наоборот:

const Dog dog;
Animal animal;
animal = dog;

Собака, безусловно, животное, но у вас тут проблема с размером. animal занимает sizeof(Animal) места в стеке, но dog занимает sizeof(Dog) места, и эти две величины могут отличаться.

При работе с полиморфизмом в C ++ вам необходимо работать либо со ссылками, либо с указателями. Например, следующее хорошо:

Dog* const dog;
Animal* animal;
animal = dog;

Здесь указатель имеет одинаковый размер независимо от того, на что он указывает, поэтому назначение возможно. Обратите внимание, что вам все еще нужно поддерживать правильные преобразования иерархии. Даже при использовании указателя вы не можете назначить Базу для Ребенка, по причине, которую я объяснил ранее: База может отличаться от Ребенка.

2 голосов
/ 11 февраля 2010

На самом деле это не ответ, а вопрос с практическими рекомендациями. Это почему-нет.

Классы должны что-то значить. Должны быть полезные вещи, которые вы можете сказать о любом правильно сформированном объекте в классе («инварианты класса»), потому что таким образом вы можете рассуждать о программах на основе того, как работает класс. Если класс - это просто набор элементов данных, вы не можете этого сделать, и вам нужно рассуждать о программе на основе каждого отдельного элемента данных.

Предлагаемый оператор присваивания изменит каждый элемент данных в базовом классе, но не коснется тех, которые определены в дочернем классе. Это означает одну из двух вещей.

Если дочерний объект правильно сформирован после того, как другой базовый объект выгружен в его базовый компонент, это означает, что дополнительные элементы данных не имеют реального соединения с базовыми элементами данных. В этом случае Child на самом деле не является подтипом Base, а публичное наследование - неправильное отношение. Ребенок не имеет отношения «есть» к «Базе», а скорее имеет отношение «имеет». Обычно это выражается композицией (у ребенка есть элемент базовых данных), а также может быть выражено частным наследованием.

Если дочерний объект не правильно сформирован после того, как другой базовый объект выгружен в его базовый компонент, предложенный оператор присваивания является действительно быстрым и удобным методом для изменения переменной.

Для примера давайте возьмем базовый класс млекопитающих и два дочерних класса, китообразные и летучие мыши. У животных есть такие вещи, как размер и вес, а у детских классов есть поля, связанные с тем, что они едят и как они двигаются. Мы назначаем китообразного млекопитающему, что разумно (все специфичные для китообразных поля только что отброшены), и теперь мы назначаем этого млекопитающего летучей мыши. Внезапно у нас летучая мышь весом двадцать тонн.

С хорошо разработанным набором классов я могу представить себе этот оператор присваивания единственно возможным, если у дочерних классов нет дополнительных элементов данных, только дополнительное поведение, и даже тогда это подозрительно.

Поэтому мой единственный совет - переделать дизайн вашего класса. Многие люди, особенно знакомые с Java и другими языками, склонными к глубокой иерархии наследования, слишком часто используют наследование в C ++. C ++ обычно лучше всего работает с большим количеством базовых классов и относительно мелкой иерархией.

2 голосов
/ 11 февраля 2010

Потому что база не ребенок. Рассмотрим:

class Child : public Base 
{ 
public: 
   SomeFunc();
};

void func() 
{ 
  const Base base; 
  Child child; 
  child = base; 
  child.SomeFunc();     // whatcha gonna call?
} 

ОБНОВЛЕНИЕ: Чтобы ответить на вопрос в комментарии, child.SomeFunc(); совершенно законно - это проблема, только если значение child на самом деле не является дочерним объектом. Компилятор не может разрешить child = base;, потому что это создаст ситуацию, когда законный вызов завершится неудачей.

1 голос
/ 11 февраля 2010

Ответ страшнее, чем я думал. Вы можете сделать это. Стандартные события имеют небольшую заметку об этом в разделе 7.3.3. Основная проблема заключается в том, что оператор =, который определяется по умолчанию в дочернем элементе, скрывает оператор = из базы, поэтому вам необходимо импортировать его обратно в область действия класса, используя «using».

Для меня это нормально. Однако я нахожу эту идею очень пугающей и потенциально чреватой неопределенным поведением, если вещи в Child нужно инициализировать.

class Base
{
public:
  void operator = (const Base& base_)
  {
  }
};

class Child : public Base
{
public:
using Base::operator=;

};

int main()
{
  const Base base = Base();
  Child child;
  child = base;
}

РЕДАКТИРОВАТЬ: снова сделал базовое const и избежать ошибки "неинициализированного конста".

1 голос
/ 11 февраля 2010

Если вы не объявляете назначение копирования явно, компилятор C ++ создаст для вас назначение копирования. В вашем случае это будет

class Child : public Base
{
public:
Child& operator=(const Child& rhs) { ... }

};

И вот почему вы получаете эту ошибку компилятора. Кроме того, имеет смысл возвращать ссылку на себя, когда вы предоставляете назначение копирования, поскольку это позволит вам объединить оператор в цепочку.

Не рекомендуется объявлять о назначении копирования следующим образом.

void operator = (const Base& base_)
{
  Base::operator=(base_);
}

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

0 голосов
/ 11 февраля 2010

Проблема в том, что вы не можете присвоить постоянное значение непостоянной переменной. Если бы вы могли, вы могли бы потенциально изменить постоянное значение, изменив непостоянную переменную.

Нужно ли, чтобы база была постоянной? Я спрашиваю, потому что вы действительно сталкиваетесь с двумя проблемами: правильность констант и наследственность / полиморфизм.

РЕДАКТИРОВАТЬ: Теперь я второй догадываюсь о моем первом утверждении. Любое уточнение?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...