Это был вопрос интервью. Рассмотрим следующее:
struct A {};
struct B : A {};
A a;
B b;
a = b;
b = a;
Почему b = a;
выдает ошибку, а a = b;
- отлично?
Это был вопрос интервью. Рассмотрим следующее:
struct A {};
struct B : A {};
A a;
B b;
a = b;
b = a;
Почему b = a;
выдает ошибку, а a = b;
- отлично?
Поскольку неявно объявленный оператор назначения копирования B
скрывает неявно объявленный оператор присваивания копии A
.
Итак, для строки b = a
кандидатом является только operator=
of B
. Но его параметр имеет тип B const&
, который не может быть инициализирован аргументом A
(вам понадобится downcast). Таким образом, вы получаете сообщение об ошибке.
Поскольку каждый 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
, поэтому он может работать. Но компилятор все равно не позволит.
Верно, что 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=;
.
Я изменил имена ваших структур, чтобы сделать очевидную причину:
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 имеют какие-либо элементы вообще, но именно поэтому компилятор допускает одно назначение, а не другое.
Помните, что если не будут явно объявлены операторы присваивания копий, то они будут объявлены и определены неявно для любого класса (а структуры - классы в С++).
Для 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&
.
Если я соберу собеседование, я объясню немного философски.
a = b;
поскольку каждый B
содержит A
в качестве своей части. Поэтому A
может извлекать A
из B
. Однако A
не содержит B
. таким образом B
не может найти B
изнутри A
; почему,
b = a;
неверно.
[Аналогично, a void*
можно найти в любом Type*
, но Type*
не может быть найден в void*
(поэтому нам нужен листинг).]