Я обычно стараюсь избегать типов приведения в максимально возможной степени, поскольку у меня сложилось впечатление, что это плохая практика кодирования и может привести к снижению производительности.
Но если бы меня попросили объяснить, почему именно так, я бы, наверное, посмотрел на них, как на оленя в свете фар.
Так почему и когда плохо кастинг?
Является ли это общим для java, c #, c ++ или каждая другая среда выполнения справляется с этим на своих условиях?
Приветствуются особенности любого языка, например, почему в c ++ плохо?
Ответы:
Вы отметили это тремя языками, и ответы на них действительно сильно различаются. Обсуждение C ++ более или менее подразумевает обсуждение приведений C, что дает (более или менее) четвертый ответ.
Поскольку это тот, который вы не упомянули явно, я начну с C. Приведения C имеют ряд проблем. Во-первых, они могут делать что угодно. В некоторых случаях приведение не делает ничего, кроме как сказать компилятору (по сути): «Заткнись, я знаю, что делаю», т. Е. Гарантирует, что даже когда вы выполняете преобразование, которое может вызвать проблемы, компилятор не будет предупреждать вас об этих потенциальных проблемах. Например
char a=(char)123456;
,. Определен точный результат этой реализации (зависит от размера и подписиchar
), и, за исключением довольно странных ситуаций, вероятно, бесполезен. C-приведения также различаются в зависимости от того, происходит ли это только во время компиляции (то есть вы просто указываете компилятору, как интерпретировать / обрабатывать некоторые данные) или что-то, что происходит во время выполнения (например, фактическое преобразование из двойного в длинный).C ++ пытается справиться с этим, по крайней мере, до некоторой степени, добавляя ряд «новых» операторов приведения, каждый из которых ограничивается только подмножеством возможностей приведения C. Это затрудняет (например) случайное выполнение преобразования, которое вы действительно не планировали - если вы намереваетесь только отбросить константу на объекте, вы можете использовать
const_cast
и быть уверены, что единственное, на что это может повлиять, - это то, объект находитсяconst
,volatile
или нет. И наоборот, astatic_cast
не может влиять на то, является ли объектconst
илиvolatile
. Короче говоря, у вас есть большинство тех же типов возможностей, но они разделены на категории, поэтому одно приведение обычно может выполнять только один вид преобразования, где одно приведение в стиле C может выполнять два или три преобразования за одну операцию. Основным исключением является то, что вы можете использовать adynamic_cast
вместо astatic_cast
по крайней мере в некоторых случаях, и, несмотря на то, что он был написан как adynamic_cast
, он действительно закончится какstatic_cast
. Например, вы можете использовать егоdynamic_cast
для перемещения вверх или вниз по иерархии классов, но приведение иерархии вверх всегда безопасно, поэтому его можно выполнять статически, в то время как приведение иерархии вниз не обязательно безопасно, поэтому сделано динамически.Java и C # намного больше похожи друг на друга. В частности, для них обоих приведение (фактически?) Всегда является операцией времени выполнения. Что касается операторов приведения C ++, он обычно наиболее близок к a
dynamic_cast
с точки зрения того, что на самом деле сделано - то есть, когда вы пытаетесь привести объект к некоторому целевому типу, компилятор вставляет проверку во время выполнения, чтобы увидеть, разрешено ли это преобразование. , и выбросить исключение, если это не так. Точные детали (например, имя, используемое для исключения "плохого приведения") варьируются, но основной принцип остается в основном аналогичным (хотя, если память обслуживает, Java действительно применяет приведение к нескольким типам, не являющимся объектами, например,int
намного ближе к C приведение типов - но эти типы используются достаточно редко, чтобы 1) я этого точно не помню и 2) даже если это правда, это все равно не имеет большого значения).Если смотреть на вещи в более общем плане, ситуация довольно проста (по крайней мере, IMO): приведение (очевидно, достаточно) означает, что вы конвертируете что-то из одного типа в другой. Когда / если вы это делаете, возникает вопрос: «Почему?» Если вы действительно хотите, чтобы что-то было определенным типом, почему вы не определили его для начала? Это не значит, что никогда не было причин для такого преобразования, но в любое время, когда это происходит, это должно вызывать вопрос о том, можно ли изменить дизайн кода, чтобы во всем использовался правильный тип. Даже кажущиеся безобидными преобразования (например, между целым числом и с плавающей запятой) следует исследовать гораздо более внимательно, чем это принято. Несмотря на кажущеесяподобия, целые числа действительно должны использоваться для «подсчитываемых» типов вещей и с плавающей запятой для «измеренных» типов вещей. Игнорирование различия - вот что приводит к некоторым сумасшедшим заявлениям вроде «в средней американской семье 1,8 ребенка». Несмотря на то, что все мы видим, как это происходит, факт остается фактом: ни в одной семье нет 1,8 детей. У них может быть 1, может быть 2 или больше, но никогда не будет 1,8.
источник
Здесь много хороших ответов. Вот как я смотрю на это (с точки зрения C #).
Кастинг обычно означает одно из двух:
Я знаю тип среды выполнения этого выражения, но компилятор этого не знает. Я говорю вам, что компилятор во время выполнения объект, соответствующий этому выражению, действительно будет принадлежать к этому типу. На данный момент вы знаете, что это выражение следует рассматривать как принадлежащее этому типу. Сгенерируйте код, который предполагает, что объект будет заданного типа, или выбросить исключение, если я ошибаюсь.
И компилятор, и разработчик знают тип среды выполнения выражения. Есть еще одно значение другого типа, связанное со значением, которое это выражение будет иметь во время выполнения. Сгенерировать код, который производит значение желаемого типа из значения данного типа; если вы не можете этого сделать, создайте исключение.
Обратите внимание, что это противоположности . Есть два вида слепков! Есть приведения, когда вы даете компилятору подсказку о реальности - эй, этот объект типа на самом деле относится к типу Customer - и есть приведения, когда вы говорите компилятору выполнить отображение от одного типа к другому - эй, Мне нужен int, соответствующий этому двойному.
Оба типа приведений - это красные флаги. При первом типе приведений возникает вопрос: «Почему разработчик знает то, чего не знает компилятор?» Если вы находитесь в такой ситуации , то лучше всего сделать это , как правило , чтобы изменить программу таким образом , что компилятор делает обрабатывают реальности. Тогда вам не нужен гипс; анализ выполняется во время компиляции.
Второй тип приведения поднимает вопрос «почему операция не выполняется в целевом типе данных в первую очередь?» Если вам нужен результат в int, то зачем вам вообще дабл? Разве вы не должны держать int?
Некоторые дополнительные мысли здесь:
http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/
источник
Foo
объект, который наследуется отBar
, и вы храните его в aList<Bar>
, вам потребуются приведения, если вы хотитеFoo
вернуть его. Возможно, это указывает на проблему на архитектурном уровне (почему мы сохраняемBar
s вместоFoo
s?), Но не обязательно. И, если у негоFoo
также есть допустимое приведениеint
, это также касается вашего другого комментария: вы сохраняете, аFoo
неint
, потому чтоint
не всегда подходит.Foo
в aList<Bar>
, но в момент преобразования вы пытаетесь сделать что-то,Foo
что не подходит для aBar
. Это означает, что различное поведение подтипов осуществляется с помощью механизма, отличного от встроенного полиморфизма, обеспечиваемого виртуальными методами. Возможно, это правильное решение, но чаще всего это красный флаг.Tuple<X,Y,...>
кортежи вряд ли увидят широкое применение в C #. Это то место, где язык мог бы лучше подталкивать людей к «яме успеха».Ошибки приведения всегда сообщаются как ошибки времени выполнения в java. Использование универсальных шаблонов или шаблонов превращает эти ошибки в ошибки времени компиляции, что значительно упрощает обнаружение ошибки.
Как я уже сказал выше. Это не значит, что кастинг плохой. Но если этого можно избежать, лучше так и поступить.
источник
Кастинг - это не плохо, просто его часто неправильно используют как средство для достижения чего-то, чего на самом деле не следует делать вообще, или делать более элегантно.
Если бы это было универсально плохо, языки не поддерживали бы его. Как и любая другая языковая функция, у нее есть свое место.
Я бы посоветовал сосредоточиться на своем основном языке и понять все его разновидности и соответствующие передовые практики. Это должно информировать экскурсии на другие языки.
Соответствующие документы C # находятся здесь .
Здесь есть отличное резюме по параметрам C ++ в предыдущем вопросе SO .
источник
Я в основном говорю здесь о C ++ , но большая часть этого, вероятно, применима и к Java и C #:
C ++ - это язык со статической типизацией . Есть некоторые возможности, которые язык позволяет вам в этом (виртуальные функции, неявные преобразования), но в основном компилятор знает тип каждого объекта во время компиляции. Причина использования такого языка в том, что ошибки могут быть обнаружены во время компиляции . Если компилятор знает типы
a
иb
, он поймает вас во время компиляции, когда вы определитеa=b
гдеa
- комплексное число, аb
является строкой.Всякий раз, когда вы выполняете явное приведение, вы говорите компилятору заткнуться, потому что думаете, что знаете лучше . Если вы ошибаетесь, вы обычно узнаете об этом только во время выполнения . И проблема с выяснением во время выполнения, что это может быть у клиента.
источник
Java, C # и C ++ являются строго типизированными языками, хотя строго типизированные языки могут считаться негибкими, они имеют преимущество в том, что они выполняют проверку типов во время компиляции и защищают вас от ошибок времени выполнения, вызванных неправильным типом для определенных операций.
Существует два основных типа приведения типов: приведение к более общему типу или приведение к другим типам (более конкретным). Приведение к более общему типу (приведение к родительскому типу) не затрагивает проверки времени компиляции. Но приведение к другим типам (более конкретным типам) отключит проверку типов во время компиляции и будет заменено компилятором проверкой времени выполнения. Это означает, что у вас меньше уверенности в том, что скомпилированный код будет работать правильно. Он также оказывает незначительное влияние на производительность из-за дополнительной проверки типа во время выполнения (Java API полон приведений!).
источник
dynamic_cast
как правило, медленнее, чемstatic_cast
, поскольку он обычно должен выполнять проверку типа во время выполнения (с некоторыми оговорками: приведение к базовому типу дешево и т. Д.).Некоторые типы литья настолько безопасны и эффективны, что часто даже не рассматриваются как кастинг.
Если вы приводите производный тип к базовому типу, это обычно довольно дешево (часто - в зависимости от языка, реализации и других факторов - это не требует затрат) и безопасно.
Если вы приводите простой тип, такой как int, к более широкому типу, например long int, то опять же это часто довольно дешево (как правило, не намного дороже, чем присвоение того же типа, что и это приведение), и снова безопасно.
Другие типы более опасны и / или дороже. В большинстве языков приведение базового типа к производному типу является дешевым, но имеет высокий риск серьезной ошибки (в C ++, если вы используете static_cast от базового к производному, это будет дешево, но если базовое значение не принадлежит производному типу, поведение не определено и может быть очень странным) или относительно дорого и с риском возникновения исключения (dynamic_cast в C ++, явное преобразование от базы к производному в C # и т. д.). Упаковка в Java и C # - еще один пример этого, и это еще более высокие затраты (учитывая, что они меняют не только то, как обрабатываются базовые значения).
Другие типы приведения могут потерять информацию (от длинного целого типа к короткому целочисленному типу).
Эти случаи риска (будь то исключение или более серьезная ошибка) и расходы - все причины, по которым следует избегать кастинга.
Более концептуальная, но, возможно, более важная причина состоит в том, что каждый случай кастинга - это тот случай, когда ваша способность рассуждать о правильности вашего кода оказывается в тупике: каждый случай - это еще одно место, где что-то может пойти не так, и способы, которыми это может пойти не так, как надо, усложняет вывод о том, выйдет ли из строя система в целом. Даже если гипс каждый раз доказуемо безопасен, доказывать это - дополнительная часть рассуждений.
Наконец, интенсивное использование приведений может указывать на неспособность хорошо рассмотреть объектную модель либо при ее создании, либо при ее использовании, либо и то, и другое: преобразование взад и вперед между одними и теми же несколькими типами часто почти всегда не позволяет учесть отношения между типами. используемый. Здесь дело не столько в том, что слепки плохие, сколько в признаках чего-то плохого.
источник
Существует растущая тенденция программистов придерживаться догматических правил использования языковых функций («никогда не использовать XXX!», «XXX считается вредным» и т. Д.), Где XXX варьируется от
goto
s до указателей наprotected
элементы данных до одиночных элементов и передачи объектов по стоимость.Следование таким правилам, по моему опыту, гарантирует две вещи: вы не будете ужасным программистом и не станете великим программистом.
Гораздо лучший подход - раскопать и раскрыть сущность истины, скрывающуюся за этими общими запретами, а затем разумно использовать функции, понимая, что во многих ситуациях они являются лучшим инструментом для работы.
«Я обычно стараюсь избегать типов приведения в максимально возможной степени» - хороший пример такого чрезмерно обобщенного правила. Приводы необходимы во многих распространенных ситуациях. Некоторые примеры:
typedef
s). (Пример:GLfloat
<-->double
<-->Real
.)std::size_type
->int
.)Есть , конечно , много ситуаций , когда это не целесообразно использовать бросок, и это важно , чтобы узнать их , а также; Я не буду вдаваться в подробности, поскольку приведенные выше ответы хорошо помогли выявить некоторые из них.
источник
Чтобы уточнить ответ KDeveloper , он не является безопасным по типу. При приведении нет гарантии, что то, что вы выполняете и на что выполняете, будет соответствовать, и если это произойдет, вы получите исключение времени выполнения, что всегда плохо.
С конкретными относительно C #, потому что она включает в себя
is
иas
операторы, у Вас есть возможность (по большей части) сделать определение относительно того , будет ли или не литая успеха. Из-за этого вы должны предпринять соответствующие шаги, чтобы определить, будет ли операция успешной, и продолжить надлежащим образом.источник
В случае C # нужно быть более осторожным при приведении из-за накладных расходов на упаковку / распаковку, возникающих при работе с типами значений.
источник
Не уверен, что кто-то уже упоминал об этом, но в C # приведение типов можно использовать довольно безопасно и часто необходимо. Предположим, вы получили объект, который может быть нескольких типов. Используя
is
ключевое слово, вы можете сначала подтвердить, что объект действительно относится к тому типу, к которому вы собираетесь его привести, а затем напрямую привести объект к этому типу. (Я мало работал с Java, но уверен, что там тоже есть очень простой способ сделать это).источник
Вы только приводите объект к определенному типу, если выполняются 2 условия:
Это означает, что не вся имеющаяся у вас информация хорошо представлена в используемой вами структуре типов. Это плохо, потому что ваша реализация должна семантически включать вашу модель, чего в данном случае явно нет.
Теперь, когда вы делаете гипс, это может иметь 2 разные причины:
На большинстве языков вы часто сталкиваетесь со второй ситуацией. Дженерики, как в Java, немного помогают, система шаблонов C ++ - даже больше, но ее сложно освоить, и даже тогда некоторые вещи могут оказаться невозможными или просто не стоящими усилий.
Таким образом, можно сказать, что приведение типов - это грязный прием, позволяющий обойти ваши проблемы, чтобы выразить определенные отношения типов на каком-то конкретном языке. Следует избегать грязных взломов. Но без них жить невозможно.
источник
Обычно шаблоны (или обобщения) более безопасны по типу, чем приведение типов. В этом отношении я бы сказал, что проблема с приведением типов - это безопасность типов. Однако есть еще одна более тонкая проблема, особенно связанная с понижением качества: дизайн. По крайней мере, с моей точки зрения, отрицательная оценка - это запах кода, указание на то, что с моим дизайном что-то не так, и я должен исследовать дальше. Почему просто: если вы «понимаете» абстракции правильно, они вам просто не нужны! Кстати, хороший вопрос ...
Ура!
источник
Чтобы быть действительно кратким, хорошая причина - портативность. Разная архитектура, которая поддерживает один и тот же язык, может иметь, скажем, целые числа разного размера. Поэтому, если я перейду с ArchA на ArchB, который имеет более узкое int, я могу увидеть в лучшем случае странное поведение, а в худшем - сбои в сегменте.
(Я явно игнорирую независимый от архитектуры байт-код и IL.)
источник