Большинство ответов здесь не в состоянии объяснить, в чем проблема нарезки. Они объясняют только доброкачественные случаи нарезки, а не предательские. Предположим, как и другие ответы, что вы имеете дело с двумя классами A
и B
, где B
происходит (публично) от A
.
В этой ситуации C ++ позволяет передавать экземпляр B
оператору присваивания A
(а также конструктору копирования). Это работает, потому что экземпляр B
может быть преобразован в const A&
, то есть операторы присваивания и конструкторы копирования ожидают, что их аргументы будут.
Доброкачественный корпус
B b;
A a = b;
Ничего плохого там не происходит - вы запросили экземпляр A
, который является копией B
, и это именно то, что вы получите. Конечно, a
не будет содержать членов b
, но как это сделать? В конце концов, это A
, а не B
, так что он даже не слышал об этих участниках, не говоря уже о том, чтобы хранить их.
Коварный случай
B b1;
B b2;
A& a_ref = b2;
a_ref = b1;
//b2 now contains a mixture of b1 and b2!
Вы можете подумать, что b2
будет копией b1
впоследствии. Но, увы, это не ! Если вы осмотрите его, вы обнаружите, что b2
является Франкенштейновским существом, сделанным из некоторых кусков b1
(кусков, которые B
наследует от A
), и некоторых кусков b2
(кусков что только B
содержит). Ой!
Что случилось? Ну, C ++ по умолчанию не обрабатывает операторы присваивания как virtual
. Таким образом, строка a_ref = b1
будет вызывать оператор присваивания A
, а не B
. Это связано с тем, что для не виртуальных функций объявленный тип * (который A&
) определяет, какая функция вызывается, в отличие от типа фактического (который будет B
). , поскольку a_ref
ссылается на экземпляр B
). Теперь оператор присваивания A
, очевидно, знает только о членах, объявленных в A
, поэтому он будет копировать только тех, которые будут добавлены в B
, без изменений.
Решение
Назначение только частям объекта обычно не имеет смысла, но, к сожалению, в C ++ нет встроенного способа запретить это. Вы можете, однако, свернуть свое собственное. Первый шаг - сделать оператор присваивания virtual . Это гарантирует, что всегда вызывается оператор присваивания типа фактического , а не тип объявленного . Второй шаг - использовать dynamic_cast
, чтобы убедиться, что назначенный объект имеет совместимый тип. Третий шаг - выполнить фактическое назначение (защищенного!) Члена assign()
, поскольку B
assign()
, возможно, захочет использовать A
* assign()
для копирования A
членов. .
class A {
public:
virtual A& operator= (const A& a) {
assign(a);
return *this;
}
protected:
void assign(const A& a) {
// copy members of A from a to this
}
};
class B : public A {
public:
virtual B& operator= (const A& a) {
if (const B* b = dynamic_cast<const B*>(&a))
assign(*b);
else
throw bad_assignment();
return *this;
}
protected:
void assign(const B& b) {
A::assign(b); // Let A's assign() copy members of A from b to this
// copy members of B from b to this
}
};
Обратите внимание, что для удобства B
operator=
переопределяет тип возврата, поскольку знает , что он возвращает экземпляр B
.