Есть ли какой-нибудь пример эффекта нарезки объектов C ++, который может вызвать неопределенное поведение, утечку памяти или сбой в другом правильном наборе кода? Например, когда класс A
а также B
(наследуется от A
) верны и здравы, но зовут void f(A a)
явно вызывает неприятные вещи.
Это необходимо для формирования тестового вопроса. Цель состоит в том, чтобы узнать, знает ли участник о явлении нарезки или нет, используя пример фрагмента кода, правильность которого не должна зависеть от мнения.
Если A
действительно «правильно и правильно», то срезы (копирование базового подобъекта) четко определены и не вызовут проблем, о которых вы упомянули. Единственная проблема, которую это вызовет, — это непредвиденное поведение, если вы ожидаете, что копия будет вести себя как B
,
Если A
не правильно копируется, тогда нарезка будет вызывать любые проблемы, возникающие при копировании объектов этого типа. Например, если у него есть деструктор, который удаляет указатель, удерживаемый объектом, и копирование создает новый указатель на одну и ту же вещь, тогда вы получите неопределенное поведение, когда оба деструктора удалят один и тот же указатель. Это не проблема с нарезкой как таковой, но с неверной семантикой копирования нарезанного объекта.
Вы всегда можете построить такой пример
struct A {
A() : invariant(true) {}
virtual void do_sth() { assert(invariant); }
protected:
bool invariant;
};
struct B : A {
B() { invariant=false; }
virtual void do_sth() { }
};
void f(A a)
{
a.do_sth();
}
Конечно, это можно предотвратить внутри A
когда конструктор копирования / оператор присваивания не проверяет, является ли инвариант истинным.
Если инвариант является более неявным, чем мое логическое значение, эти вещи может быть очень сложно увидеть.
Нарезка объектов на самом деле является проблемой, только если вы манипулируете производными классами с помощью указателей или ссылок на их базовый класс. Затем дополнительные данные производного класса остаются неизменными, в то время как данные в базовой части могут быть изменены. Это может нарушить инварианты производного класса. Для простого примера см. http://en.wikipedia.org/wiki/Object_slicing
Тем не менее, я бы посчитал это недостаток дизайна (производного класса). Если доступ к классу любым легальным методом, в том числе указателем или аргументом ссылки на базовый класс, может нарушить его инварианты, класс плохо спроектирован. Один из способов избежать этого — объявить базу private
, так что производный класс не может быть юридически доступен через его базу.
Примером может быть:
class A
{
int X;
A(int x) : X(x) {}
void doubleX() { X+=X; }
/* ... */
};
class B : public A
{
int X_square;
B(int x) : A(x), X_square(x*x) {}
/* ... */
};
B b(3);
B.doubleX(); /// B.X = 6 but B.X_square=9
Из этого примера также очевидно, что это простой недостаток дизайна в B. В этом примере
void B::doubleX() { A::doubleX(); X_squared=X*X; }
не решает проблему, так как
A&a=b;
a.doubleX();
все еще ломает инвариант. Единственное решение здесь — объявить базу A
частный или, лучше, сделать A
частный член, а не база, из B
,