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

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

Это был вопрос интервью. Рассмотрим следующее:

struct A {}; 
struct B : A {}; 
A a; 
B b; 
a = b;
b = a; 

Почему b = a; выдает ошибку, а a = b; - отлично?

4b9b3361

Ответ 1

Поскольку неявно объявленный оператор назначения копирования B скрывает неявно объявленный оператор присваивания копии A.

Итак, для строки b = a кандидатом является только operator= of B. Но его параметр имеет тип B const&, который не может быть инициализирован аргументом A (вам понадобится downcast). Таким образом, вы получаете сообщение об ошибке.

Ответ 2

Поскольку каждый B является A, но не каждый A является B.

Отредактированы следующие комментарии, чтобы сделать вещи более ясными (я изменил ваш пример):

struct A {int someInt;}; 
struct B : A {int anotherInt}; 
A a; 
B b; 

/* Compiler thinks: B inherits from A, so I'm going to create
   a new A from b, stripping B-specific fields. Then, I assign it to a.
   Let do this!
 */
a = b;

/* Compiler thinks: I'm missing some information here! If I create a new B
   from a, what do I put in b.anotherInt?
   Let not do this!
 */
b = a;

В вашем примере нет атрибутов someInt и anotherInt, поэтому он может работать. Но компилятор все равно не позволит.

Ответ 3

Верно, что B является A, но A не является B, но этот факт применим только непосредственно, когда вы работаете с указателями или ссылками на A и B 's. Проблема здесь - ваш оператор присваивания.

struct A {}; 
struct B : A {};

Является эквивалентным

struct A {
   A& operator=(const A&);
}; 
struct B : A {
   B& operator=(const B&);
};

Итак, когда вы назначаете ниже:

A a; 
B b; 
a = b;

Оператор присваивания на A может быть вызван с аргументом B, потому что a B является A, поэтому B может быть передан оператору присваивания как A&. Обратите внимание, что оператор присваивания A знает только данные, которые находятся в A, а не материал в B, поэтому любые члены B, которые не являются частью A, теряются - это называется "срезание".

Но когда вы пытаетесь назначить:

b = a; 

A имеет тип A, который не является B, поэтому A не может сопоставлять параметр B& с оператором присваивания B.

Вы могли бы подумать, что b=a должен просто называть унаследованный A& A::operator=(const A&), но это не так. Оператор присваивания B& B::operator=(const B&) скрывает оператор, который будет унаследован от A. Его можно снова восстановить с помощью объявления using A::operator=;.

Ответ 4

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

struct Animal {}; 
struct Bear : Animal {}; 
Animal a; 
Bear b; 
a = b; // line 1 
b = a; // line 2 

Ясно, что любой Медведь также является животным, но не каждое Животное можно считать медведем.

Поскольку каждый B "isa" A, любой экземпляр B также должен быть экземпляром A: по определению он имеет те же элементы в том же порядке, что и любой другой экземпляр A. Копирование b в теряет B-специфический члены, но полностью заполняет члены a, образующие структуру, которая удовлетворяет требованиям A. Копирование a в b, с другой стороны, может оставить b неполным, поскольку B может иметь больше членов, чем A. Это трудно увидеть здесь, потому что ни A или B имеют какие-либо элементы вообще, но именно поэтому компилятор допускает одно назначение, а не другое.

Ответ 5

Помните, что если не будут явно объявлены операторы присваивания копий, то они будут объявлены и определены неявно для любого класса (а структуры - классы в С++).

Для struct A он будет иметь следующую подпись:

A& A::operator=(const A&)

И он просто выполняет по-членное присваивание своих подобъектов.

a = b; ОК, потому что B будет соответствовать параметру const A& для A::operator=(const A&). Так как только члены A "назначаются по назначению" для цели, любые члены B, которые не являются частью A, теряются - это называется "разрезание".

Для struct B оператор присваивания implcit будет иметь следующую подпись:

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

b = a; не в порядке, потому что A не будет соответствовать аргументу const B&.

Ответ 6

Если я соберу собеседование, я объясню немного философски.

a = b;

поскольку каждый B содержит A в качестве своей части. Поэтому A может извлекать A из B. Однако A не содержит B. таким образом B не может найти B изнутри A; почему,

b = a;

неверно.

[Аналогично, a void* можно найти в любом Type*, но Type* не может быть найден в void* (поэтому нам нужен листинг).]