Кто-то упомянул это в IRC как проблему нарезки.
c++
inheritance
c++-faq
object-slicing
Frankomania
источник
источник
A a = b;
a
теперь это объект типа, уA
которого есть копияB::foo
. Думаю, будет заброшено возвращать его обратно.B b1; B b2; A& b2_ref = b2; b2 = b1
. Вы можете подумать , как вы скопировалиb1
наb2
, но у вас нет! Вы скопировали часть изb1
кb2
(часть ,b1
котораяB
наследуется отA
), и оставили другие частиb2
неизменными.b2
теперь это франкенштейновское существо, состоящее из несколькихb1
кусков, за которыми следуют несколько кусковb2
. Тьфу! Даунтинг, потому что я думаю, что ответ очень обманчив.B b1; B b2; A& b2_ref = b2; b2_ref = b1
" Настоящая проблема возникает, если вы " ... происходят от класса с не виртуальным оператором присваивания. ЕстьA
даже предназначен для вывода? У него нет виртуальных функций. Если вы наследуете тип, вам приходится иметь дело с тем фактом, что его функции-члены могут быть вызваны!Большинство ответов здесь не в состоянии объяснить, в чем проблема нарезки. Они объясняют только доброкачественные случаи нарезки, а не предательские. Предположим, как и другие ответы, что вы имеете дело с двумя классами
A
иB
откудаB
происходят (публично)A
.В этой ситуации, C ++ позволяет передавать экземпляр
B
дляA
оператора присваивания «s (а также конструктор копирования). Это работает, потому что экземплярB
может быть преобразован в aconst A&
, и это то, что операторы присваивания и конструкторы копирования ожидают от своих аргументов.Доброкачественный случай
Ничего плохого там не происходит - вы попросили экземпляр,
A
который является копиейB
, и это именно то, что вы получите. Конечно,a
не будет содержать некоторые изb
членов, но как это должно? Это, вA
конце концов, неB
, так что он даже не слышал об этих членах, не говоря уже о том, чтобы иметь возможность хранить их.Коварный случай
Вы можете подумать, что
b2
это будет копияb1
потом. Но, увы, это не так ! Если вы осмотрите его, вы обнаружите, чтоb2
это Франкенштейновское существо, сделанное из некоторых кусковb1
(кусков, которыеB
наследуютсяA
) и из некоторых кусковb2
(кусков, которые толькоB
содержат). Ой!Что случилось? Ну, C ++ по умолчанию не рассматривает операторы присваивания как
virtual
. Таким образом, строкаa_ref = b1
будет вызывать оператор присваиванияA
, а не оператораB
. Это связано с тем, что для не виртуальных функций объявленный (формально: статический ) тип (который являетсяA&
) определяет, какая функция вызывается, в отличие от фактического (формально: динамического ) типа (который был быB
, посколькуa_ref
ссылается на экземплярB
) , ТеперьA
оператор присваивания, очевидно, знает только о членах, объявленных вA
, поэтому он будет копировать только тех, которые будут добавленыB
без изменений.Решение
Назначение только частям объекта обычно не имеет смысла, но, к сожалению, в C ++ нет встроенного способа запретить это. Вы можете, однако, свернуть свое собственное. Первый шаг - сделать оператор присваивания виртуальным . Это будет гарантировать, что всегда вызывается действительный оператор присваивания типа, а не объявленный тип. Второй шаг - использовать
dynamic_cast
для проверки того, что назначенный объект имеет совместимый тип. Третий шаг должен сделать фактическое назначение в качестве члена (защищенный!)assign()
, Так какB
«sassign()
, вероятно , захотите использоватьA
» S ,assign()
чтобы скопироватьA
его члены.Обратите внимание , что для чистого удобства,
B
«Soperator=
ковариантно переопределяет тип возвращаемого значения , так как он знает , что это возвращение экземпляраB
.источник
derived
значение может быть дано коду, ожидающемуbase
значение, либо любая производная ссылка может использоваться в качестве базовой ссылки. Я хотел бы видеть язык с системой типов, которая рассматривает обе концепции отдельно. Во многих случаях производная ссылка должна заменять базовую ссылку, но производные экземпляры не должны заменять базовые; Есть также много случаев, когда экземпляры должны быть конвертируемыми, но ссылки не должны заменять.Если у вас есть базовый класс
A
и производный классB
, то вы можете сделать следующее.Теперь метод
wantAnA
нуждается в копииderived
. Тем не менее, объектderived
не может быть скопирован полностью, так как классB
может изобрести дополнительные переменные-члены, которых нет в его базовом классеA
.Следовательно, для вызова
wantAnA
компилятор будет «отрезать» все дополнительные члены производного класса. Результатом может быть объект, который вы не хотите создавать, потому чтоA
-объект (все особое поведение классаB
потеряно).источник
wantAnA
(как следует из названия!) ХочетA
, то это то, что он получает. И примерA
, будет вести себя какA
. Как это удивительно?derived
типA
. Неявное приведение всегда является источником неожиданного поведения в C ++, потому что зачастую трудно понять, просматривая код локально, что произошло приведение.Это все хорошие ответы. Я просто хотел бы добавить пример выполнения при передаче объектов по значению vs по ссылке:
Выход:
источник
Третье совпадение в Google для «нарезки на C ++» дает мне статью в Википедии http://en.wikipedia.org/wiki/Object_slicing и вот это (горячо, но первые несколько постов определяют проблему): http://bytes.com/ форум / thread163565.html
Так что, когда вы назначаете объект подкласса суперклассу. Суперкласс ничего не знает о дополнительной информации в подклассе и не имеет места для ее хранения, поэтому дополнительная информация «обрезается».
Если эти ссылки не дают достаточно информации для «хорошего ответа», пожалуйста, отредактируйте свой вопрос, чтобы сообщить нам, что еще вы ищете.
источник
Проблема нарезки является серьезной, поскольку она может привести к повреждению памяти, и очень трудно гарантировать, что программа не пострадает от этого. Чтобы создать его вне языка, классы, поддерживающие наследование, должны быть доступны только по ссылке (не по значению). Язык программирования D обладает этим свойством.
Рассмотрим класс A и класс B, производные от A. Повреждение памяти может произойти, если часть A имеет указатель p и экземпляр B, который указывает p на дополнительные данные B. Затем, когда дополнительные данные удаляются, p указывает на мусор.
источник
Derived
неявно конвертируются вBase
.) Это явно противоречит принципу Open-Closed и является большой нагрузкой на обслуживание.В C ++ объект производного класса может быть назначен объекту базового класса, но другой путь невозможен.
Разделение объектов происходит, когда объект производного класса назначается объекту базового класса, дополнительные атрибуты объекта производного класса вырезаются для формирования объекта базового класса.
источник
Проблема среза в C ++ возникает из семантики значений его объектов, которая осталась в основном благодаря совместимости со структурами C. Вам необходимо использовать явный синтаксис ссылки или указателя для достижения «нормального» поведения объекта, встречающегося в большинстве других языков, которые делают объекты, то есть объекты всегда передаются по ссылке.
Короткие ответы: вы нарезаете объект, назначая производный объект базовому объекту по значению , то есть оставшийся объект является только частью производного объекта. Чтобы сохранить семантику значений, нарезка является разумным поведением и имеет относительно редкое применение, которого нет в большинстве других языков. Некоторые люди считают, что это особенность C ++, в то время как многие считают это одним из недостатков / недостатков C ++.
источник
struct
, совместимостью или другим бессмысленным смыслом, который вам сказал любой случайный ООП-жрец.Base
должен занимать ровноsizeof(Base)
байты в памяти с возможным выравниванием, может быть, поэтому «назначение» (on-stack-copy) ) не будет копировать производные члены класса, их смещения находятся за пределами sizeof. Для того, чтобы избежать «потери данных», просто указатель использования, как кто - либо другой, так как указатель памяти фиксируется на месте и размера, в то время как стек очень volitileИтак ... Почему потеря производной информации плохо? ... потому что автор производного класса, возможно, изменил представление таким образом, что удаление дополнительной информации изменяет значение, представляемое объектом. Это может произойти, если производный класс используется для кэширования представления, которое более эффективно для определенных операций, но дорогостоящее для преобразования обратно в базовое представление.
Также подумал, что кто-то должен также упомянуть, что вы должны делать, чтобы избежать нарезки ... Получите копию стандартов кодирования C ++, руководств по 101 правилам и лучших практик. Работа с нарезкой # 54.
Он предлагает несколько сложный шаблон для полного решения проблемы: иметь конструктор защищенных копий, защищенный чистый виртуальный DoClone и общедоступный клон с assert, который сообщит вам, если (в дальнейшем) производный класс не смог правильно реализовать DoClone. (Метод Clone делает правильную глубокую копию полиморфного объекта.)
Вы также можете пометить конструктор копирования в явном виде, что позволяет явно разрезать его, если это необходимо.
источник
1. ОПРЕДЕЛЕНИЕ ПРОБЛЕМЫ СЛОЖЕНИЯ
Если D является производным классом базового класса B, вы можете присвоить объект типа Derived переменной (или параметру) типа Base.
ПРИМЕР
Хотя приведенное выше назначение разрешено, значение, присвоенное переменной pet, теряет свое поле породы. Это называется проблемой нарезки .
2. КАК ИСПРАВИТЬ ПРОБЛЕМУ СЛОЖЕНИЯ
Чтобы победить проблему, мы используем указатели на динамические переменные.
ПРИМЕР
В этом случае ни один из членов-данных или функций-членов динамической переменной, на которую указывает ptrD (объект класса-потомка), не будет потерян. Кроме того, если вам нужно использовать функции, функция должна быть виртуальной функцией.
источник
dog
, которое не является частью классаPet
(breed
член данных), не копируется в переменнуюpet
? Код интересует толькоPet
данные членов - по-видимому. Нарезка - определенно «проблема», если она нежелательна, но я не вижу этого здесь.((Dog *)ptrP)
Я предлагаю использоватьstatic_cast<Dog*>(ptrP)
Dog::breed
), никоим образом не является ОШИБКОЙ, связанной с SLICING?Мне кажется, что нарезка не является большой проблемой, за исключением случаев, когда ваши собственные классы и программы плохо спроектированы / спроектированы.
Если я передам объект подкласса в качестве параметра методу, который принимает параметр типа суперкласс, я, безусловно, должен знать об этом и знать внутренне, что вызываемый метод будет работать только с объектом суперкласса (он же базовый класс).
Мне кажется только необоснованным ожиданием, что предоставление подкласса, в котором запрашивается базовый класс, каким-то образом приведет к конкретным результатам для подкласса, вызовет проблемы с нарезкой. Это либо плохой дизайн при использовании метода, либо плохая реализация подкласса. Я предполагаю, что это обычно является результатом того, что жертвует хорошим дизайном ООП в пользу целесообразности или увеличения производительности.
источник
Хорошо, я попробую после прочтения многих постов, объясняющих нарезку объектов, но не то, как это становится проблематичным.
Порочный сценарий, который может привести к повреждению памяти, следующий:
источник
Разделение означает, что данные, добавленные подклассом, отбрасываются, когда объект подкласса передается или возвращается по значению или из функции, ожидающей объект базового класса.
Объяснение: Рассмотрим следующее объявление класса:
Поскольку функции копирования базового класса ничего не знают о производном, копируется только базовая часть производного. Это обычно называют нарезкой.
источник
источник
когда объект производного класса назначается объекту базового класса, дополнительные атрибуты объекта производного класса вырезаются (отбрасываются) из объекта базового класса.
источник
Когда производный класс Object назначается базовому классу Object, все члены объекта производного класса копируются в объект базового класса, кроме членов, которых нет в базовом классе. Эти члены отрезаются компилятором. Это называется нарезкой объектов.
Вот пример:
Это сгенерирует:
источник
Я просто столкнулся с проблемой нарезки и тут же приземлился здесь. Итак, позвольте мне добавить мои два цента к этому.
Давайте возьмем пример из «производственного кода» (или что-то вроде этого):
Допустим, у нас есть что-то, что отправляет действия. Пользовательский интерфейс центра управления, например.
Этот пользовательский интерфейс должен получить список вещей, которые в настоящее время могут быть отправлены. Таким образом, мы определяем класс, который содержит диспетчерскую информацию. Давайте назовем это
Action
. Таким образом, anAction
имеет несколько переменных-членов. Для простоты у нас просто 2, будучи astd::string name
и astd::function<void()> f
. Тогда он имеет,void activate()
который просто выполняетf
член.Таким образом, пользовательский интерфейс получает в
std::vector<Action>
комплекте. Представьте себе некоторые функции, такие как:Теперь мы установили, как это выглядит с точки зрения пользовательского интерфейса. Пока проблем нет. Но какой-то другой парень, который работает над этим проектом, внезапно решает, что существуют специальные действия, которые требуют больше информации в
Action
объекте. По какой причине. Это также можно решить с помощью лямбда-захвата. Этот пример не взят 1-1 из кода.Таким образом, парень происходит от того,
Action
чтобы добавить свой собственный аромат.Он передает экземпляр своего класса домашнего приготовления в,
push_back
но затем программа выходит из строя.Так что случилось?
Как вы , возможно , догадались: объект был кружочками.
Дополнительная информация из экземпляра была потеряна и
f
теперь подвержена неопределенному поведению.Я надеюсь, что этот пример проливает свет на тех людей, которые не могут по-настоящему представить, когда говорят о том, что
A
s иB
s получены каким-либо образом.источник