Чому слід уникати кастингу? [зачинено]


97

Я, як правило, уникаю типів кастингу якомога більше, оскільки мені здається, що це погана практика кодування та може спричинити штраф за ефективність.

Але якби хтось попросив мене пояснити, чому це саме так, я, мабуть, дивився б на них, як на оленя у фарах.

То чому / коли кастинг поганий?

Це взагалі для java, c #, c ++ чи всі різні середовища виконання вирішують це на власних умовах?

Особливості для будь-якої мови вітаються, наприклад, чому це погано в c ++?


3
Звідки у вас склалося таке враження?
Одід

8
У мене склалося таке враження, оскільки я ніколи не читав книгу і не зустрічався з програмістом, який сказав: "КАСТУЙТЕ СУОГО ГУДОВОГО !!!"
LoudNPossblyWrong

15
Відповідь на C ++ неминуче відрізняється від відповіді на C #. Це дуже мовно. На сьогодні відповіді дають відповідь на це питання для конкретних мов, а в деяких випадках не зазначено, якою мовою вони говорять.
Джеймс Мак-Нілліс

2
Поганий - відносний термін. Уникати кастингу - найкраща практика, але іноді програміст повинен робити те, що повинен робити програміст. (особливо, якщо ви пишете програму java 1.5+, яка використовує бібліотеку, написану для 1.4) Можливо, перейменуйте питання "Чому слід уникати кастингу?"
Майк Міллер

2
Це чудове запитання .... Його закрили 5 користувачів, кожен з яких має репутацію менше 10 к !! Перебільшене використання нових повноважень, однозначно. Проголосували за повторне відкриття.
досягати 4thelasers

Відповіді:


141

Ви позначили це трьома мовами, і відповіді дійсно сильно відрізняються між трьома. Обговорення С ++ більш-менш передбачає обговорення C-ролей, що дає (більш-менш) четверту відповідь.

Оскільки це той, про який ви прямо не згадали, я розпочну з C. C Casts має ряд проблем. Одне полягає в тому, що вони можуть робити будь-яку з кількох різних речей. У деяких випадках акторський склад не робить нічого, крім того, щоб сказати компілятору (по суті): "заткнись, я знаю, що я роблю" - тобто гарантує, що навіть коли ти перетвориш, що може спричинити проблеми, компілятор не попередить вас про ці потенційні проблеми. Так, наприклад, char a=(char)123456;. Точний результат цієї реалізації визначений (залежить від розміру та підписаностіchar), і, за винятком досить дивних ситуацій, мабуть, це не корисно. C-касти також відрізняються тим, чи є вони чимось, що відбувається лише під час компіляції (тобто ви просто розповідаєте компілятору, як інтерпретувати / обробляти деякі дані), або щось, що відбувається під час виконання (наприклад, фактичне перетворення з подвійного в довго).

C ++ намагається впоратися з цим хоча б певною мірою шляхом додавання декількох "нових" операторів ролях, кожен з яких обмежений лише підмножиною можливостей C-акторів. Це ускладнює (наприклад) випадкове здійснення конверсії, якої ви насправді не мали наміру - якщо ви лише маєте намір відмовитись від жорсткості на об'єкті, ви можете використовувати const_castі бути впевненим, що єдине, на що це може вплинути, - чи об'єкт знаходиться const, volatileчи ні. І навпаки, a static_castне дозволяється впливати на те, чи є об'єкт constчиvolatile. Коротше кажучи, у вас є більшість однакових типів можливостей, але вони класифіковані, так що один склад може, як правило, робити лише один вид перетворення, де один склад в стилі С може зробити дві або три перетворення за одну операцію. Основним винятком є ​​те, що ви можете використовувати dynamic_castзамість а static_castпринаймні в деяких випадках, і, незважаючи на те, що вони написані як " dynamic_cast, це дійсно закінчиться як" static_cast. Наприклад, ви можете використовувати dynamic_castдля переходу вгору або вниз по ієрархії класів - але ієрархія класів "вгору" завжди безпечна, тому це може бути зроблено статично, тоді як ієрархія "вниз" не обов'язково безпечна, тому це робиться динамічно.

Java та C # набагато більше схожі між собою. Зокрема, для обох кастинг - це (практично?) Завжди операція, що виконується. Що стосується операторів лиття C ++, це, як правило, найближче до a dynamic_castз точки зору того, що реально робиться - тобто, коли ви намагаєтеся передати об'єкт до якогось цільового типу, компілятор вставляє перевірку часу, щоб побачити, чи дозволена ця конверсія , і киньте виняток, якщо це не так. Точні деталі (наприклад, ім'я, яке використовується для винятку "bad cast") відрізняються, але основний принцип залишається здебільшого схожим (хоча, якщо пам'ять слугує, Java робить касти, застосовані до кількох необ'єктних типів, таких як intнабагато ближче до C касти - але ці типи використовуються досить рідко, щоб 1) я цього точно не пам’ятаю, і 2) навіть якщо це правда, все одно це не має великого значення).

Якщо дивитися на речі загалом, ситуація досить проста (принаймні IMO): акторський склад (очевидно, достатньо) означає, що ви перетворюєте щось з одного типу в інший. Коли / якщо ви це зробите, то виникає питання "Чому?" Якщо ви дійсно хочете, щоб щось було певного типу, чому ви не визначили це саме таким типом? Це не означає, що ніколи не виникає причин проводити таке перетворення, але коли б це трапилося, слід підказати питання про те, чи можна було переконструювати код, щоб правильний тип використовувався протягом усього часу. Навіть, здавалося б, нешкідливі перетворення (наприклад, між цілим числом і плаваючою точкою) слід розглядати набагато ретельніше, ніж зазвичай. Незважаючи на їх уявленняподібність, цілі числа дійсно повинні використовуватися для "підрахованих" типів речей і плаваючої точки для "вимірюваних" видів речей. Ігнорування розрізнення - це те, що призводить до деяких шалених тверджень на кшталт "середня американська родина має 1,8 дитини". Хоча ми всі бачимо, як це відбувається, факт полягає в тому, що жодна сім'я не має 1,8 дитини. Вони можуть мати 1 або вони можуть 2 або вони можуть мати більше, але ніколи 1,8.


10
Схоже, ви страждаєте "смерть через тривалість уваги" тут, це хороша відповідь imo.
Стів Таунсенд

2
Важлива відповідь, але деякі частини "я не знаю" можуть бути посилені, щоб зробити його всебічним. @ Dragontamer5788 - це гарна відповідь, але не вичерпна.
M2tM

5
Одна ціла дитина та одна дитина з відсутньою ніжкою були б 1,8 дитини. Але дуже приємна відповідь. :)
Оз.

1
Дуже приємна відповідь, що не перешкоджає, принаймні, виключаючи пари без дітей.

@Roger: не виключаючи, просто ігноруючи.
Джеррі Труну

48

Тут багато хороших відповідей. Ось як я дивлюся на це (з точки зору C #).

Кастинг зазвичай означає одну з двох речей:

  • Я знаю тип виконання цього виразу, але компілятор цього не знає. Скажу вам, компілятор, під час виконання об'єкт, що відповідає цьому вислову, справді буде такого типу. На сьогоднішній день ви знаєте, що цей вираз слід сприймати як такий тип. Створіть код, який передбачає, що об’єкт буде заданого типу, або, киньте виняток, якщо я помиляюся.

  • І компілятор, і розробник знають тип виконання виразу. Існує ще одне значення іншого типу, пов’язане зі значенням, яке буде мати це вираження під час виконання. Створити код, який виробляє значення потрібного типу зі значення заданого типу; якщо ви не можете цього зробити, киньте виняток.

Зауважте, що це протилежності . Є два види кастингу! Існують касти, де ви даєте підказку компілятору про реальність - ей, цей об’єкт типу насправді є типом Клієнта - і є касти, де ви говорите компілятору виконувати зіставлення з одного типу на інший - так, Мені потрібен int, який відповідає цьому подвійному.

Обидва типи ролей - червоні прапори. Перший вид акторської кампанії викликає питання "чому саме розробник знає щось, чого не робить компілятор?" Якщо ви знаходитесь в такій ситуації , то краще за все зробити це , як правило , щоб змінити програму таким чином , що компілятор робить обробляють реальності. Тоді вам не потрібен акторський склад; аналіз робиться під час компіляції.

Другий вид амплітуди викликає питання "чому в першу чергу не виконується операція в цільовому типі даних?" Якщо вам потрібен результат в інтах, то чому ви в першу чергу тримаєте дубль? Невже ви не тримаєте int?

Деякі додаткові думки тут:

http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/


2
Я не думаю, що це по суті червоні прапори. Якщо у вас є Fooоб'єкт, який успадковує Bar, і ви зберігаєте його в List<Bar>, тоді вам потрібні касти, якщо ви хочете цього Fooповернути. Можливо, це вказує на проблему на архітектурному рівні (чому ми зберігаємо Bars замість Foos?), Але не обов'язково. І якщо це Fooтакож має правильний склад int, він також має справу з вашим іншим коментарем: Ви зберігаєте Fooне, а не int, тому що intне завжди підходить.
Майк Карон

6
@Mike Caron - Очевидно, я не можу відповісти за Еріка, але для мене червоний прапор означає "це над чим подумати", а не "це щось не так". І немає ніяких проблем із збереженням Fooв a List<Bar>, але в точці акторського складу ви намагаєтесь зробити щось із тим, Fooщо не підходить для Bar. Це означає, що різну поведінку для підтипів здійснюють за допомогою іншого механізму, ніж вбудований поліморфізм, наданий віртуальними методами. Можливо, це правильне рішення, але частіше це червоний прапор.
Джефрі Л Уітлідж

14
Справді. Якщо ви витягуєте речі зі списку тварин, і вам пізніше потрібно сказати компілятору, о, до речі, я випадково знаю, що перший - тигр, другий - лев, а третій є ведмедиком, тоді ви повинні були використовувати кортеж <Лев, тигр, ведмідь>, а не список <Живот>.
Ерік Ліпперт

5
Я згоден з вами, але я думаю, що без кращої синтаксичної підтримки Tuple<X,Y,...>кортежів навряд чи вдасться побачити широке використання в C #. Це одне місце, де мова могла б зробити кращу роботу підштовхувати людей до "ями успіху".
kvb

4
@kvb: Я згоден. Ми розглядали можливість прийняття синтаксису кортежу для C # 4, але він не вкладався в бюджет. Можливо, в С №5; ми ще не розробили повний набір функцій. Занадто зайнятий збиранням CTP разом для асинхронізації. Чи, можливо, у майбутній гіпотетичній версії.
Ерік Ліпперт

36

Помилки кастингу завжди повідомляються як помилки під час виконання Java. Використання дженерики чи шаблонів перетворює ці помилки в помилки під час компіляції, що полегшує виявлення, коли ви помилилися.

Як я вже говорив вище. Це не означає, що всі кастинги погані. Але якщо можна цього уникнути, найкраще це зробити.


4
Це передбачає, що всі кастинги є "поганими"; однак це не зовсім вірно. Візьмемо, наприклад, C #, як з неявною, так і з явною підтримкою передачі. Проблема виникає, коли виконується кастинг, який (випадково) видаляє інформацію або безпеку типу (це залежить від мови).

5
Насправді це неправильно в С ++. Можливо, редагування для включення мов, на яких орієнтована ця інформація, є в порядку.
Стів Таунсенд

@Erick - Я б хотів, але я не знаю першої речі про Java, чи недостатньо деталей C #, щоб переконатися, що інформація правильна.
Стів Таунсенд

Вибачте, я хлопець на Java, тому мої знання зі с ++ досить обмежені. Я міг би відредагувати слово "шаблонування" з нього, оскільки воно мало бути неясним.
Майк Міллер

Не так. Деякі помилки кидка виявляються компілятором Java, а не лише загальними. Розгляньте (Рядок) 0;
Маркіз Лорн

18

Кастинг не є по суті поганим, просто його часто використовують як засіб для досягнення чогось, чого насправді або не слід робити взагалі, або робити більш елегантно.

Якби це було загально погано, мови не підтримували б його. Як і будь-яка інша мовна особливість, вона має своє місце.

Моя порада полягатиме в тому, щоб зосередити увагу на вашій основній мові та зрозуміти всі її викиди та пов'язані з ними кращі практики. Це повинно інформувати про екскурсії іншими мовами.

Відповідні C # документи знаходяться тут .

Тут наводиться чудовий підсумок варіантів C ++ на попередньому запитанні ТА .


9

Я здебільшого виступаю тут за C ++ , але більшість цього, мабуть, стосується також Java та C #:

C ++ - це статично набрана мова . Існує декілька можливостей, якими мова дозволяє це (віртуальні функції, неявна конверсія), але в основному компілятор знає тип кожного об'єкта під час компіляції. Причиною використання такої мови є те, що помилки можуть виявлятись під час компіляції . Якщо компілятор знає типи aі b, то він зловить вас під час компіляції, коли ви робите, a=bде aє складним числом і bє рядком.

Щоразу, коли ви робите явний кастинг, ви кажете компілятору заткнутись, бо ви думаєте, що знаєте краще . Якщо ви помиляєтесь, зазвичай це з’ясовуєте лише під час виконання . І проблема з тим, як знайти це під час виконання, полягає в тому, що це може бути у клієнта.


5

Java, c # і c ++ - це сильно набрані мови, хоча сильно набрані мови можуть розглядатися як негнучкі, але вони мають перевагу робити перевірку типу під час компіляції та захищають вас від помилок виконання, спричинених неправильним типом для певних операцій.

В основному є два типи кастингу: акторський склад для більш загального типу або акторський склад для іншого типу (більш конкретний). Передача на більш загальний тип (переклад на батьківський тип) залишить перевірку часу компіляції недоторканою. Але кастинг для інших типів (більш конкретних типів) відключить перевірку типів часу компіляції і замінить компілятор на перевірку часу виконання. Це означає, що у вас є менша впевненість, що складений код буде працювати правильно. Це також має незначний вплив на продуктивність, завдяки додатковій перевірці типу виконання (Java API повний кастингу!).


5
Не всі ролі безпеки "обходять" у C ++.
Джеймс Мак-Нілліс

4
Не всі ролі безпеки "обходять" у C #.

5
Далеко не всі в Java видають безпеку типу "обхід".
Ерік Робертсон

11
Не всі ролі "обходять" безпеку типу "Кастинг". О, зачекайте, цей тег не стосується мови ...
sbi

2
Майже у всіх випадках у C # та Java кастинг призведе до погіршення продуктивності, оскільки система виконає перевірку типу виконання (яка не є безкоштовною). У C ++, dynamic_castяк правило, повільніше static_cast, оскільки, як правило, доводиться робити перевірку типу виконання (з деякими застереженнями: лиття до базового типу є дешевим тощо).
Travis Gockel

4

Деякі види кастингу настільки безпечні та ефективні, що часто навіть не вважаються кастингом.

Якщо ви переходите від похідного типу до базового типу, це, як правило, досить дешево (часто - залежно від мови, реалізації та інших факторів - це нульова вартість) і є безпечним.

Якщо ви переходите від простого типу, наприклад, int до більш широкого типу, як довгий int, то, знову ж таки, це часто досить дешево (як правило, не набагато дорожче, ніж присвоєння тому ж типу, що і видання) і знову є безпечним.

Інші типи є більш загрозливими та / або дорожчими. У більшості мов лиття з базового типу на похідний тип є дешевим, але має високий ризик серйозної помилки (у C ++, якщо ви static_cast від базової до похідної, це буде дешево, але якщо базове значення не є похідним типом, поведінка не визначена і може бути дуже дивною) або відносно дорогою і з ризиком викликати виняток (динамический_cast в C ++, явний показник від базового до похідного в C # і так далі). Бокс у Java та C # - це ще один приклад цього та ще більших витрат (якщо врахувати, що вони змінюються більше, ніж просто як трактуються основні значення).

Інші типи трансляції можуть втрачати інформацію (довгий цілий тип до короткого цілого типу).

Ці випадки ризику (будь то виняток чи серйозніша помилка) та витрат є всіма причинами уникнути кастингу.

Більш концептуальна, але, мабуть, важливіша причина полягає в тому, що кожен випадок кастингу - це випадок, коли ваша спроможність міркувати про правильність вашого коду стискується: кожен випадок - це ще одне місце, де щось може піти не так, і шляхи, коли це може піти не так, додавши до складності виведення, чи піде невірна система в цілому. Навіть якщо акторський склад є щоправда безпечним, це доводить, що це зайва частина міркувань.

Нарешті, велике використання каст може вказувати на неспроможність добре розглядати об'єктну модель або при її створенні, використанні, або обох: Часто передавання назад і назад між однаковими кількома типами майже завжди є неврахуванням зв’язків між типами б / в. Тут не стільки, що ролі погані, скільки вони є ознакою чогось поганого.


2

Зростає тенденція програмістів чіплятися до догматичних правил щодо використання мовних функцій ("ніколи не використовуйте XXX!", "XXX вважається шкідливим" тощо), де XXX варіюється від gotos до покажчиків, до protectedчленів даних до одиночних передач об'єктів значення.

Дотримуючись таких правил, на мій досвід, гарантується дві речі: ви не будете страшним програмістом, а також не будете чудовим програмістом.

Набагато краще було б докопатися і відкривайте ядро істини позаду цих офсетних заборон, а потім використовувати функції розважливі, з розумінням того, що мають багато ситуацій , в яких вони кращий інструмент для роботи.

"Я, як правило, уникаю типів кастингу якомога більше" - хороший приклад такого перегенералізованого правила. У багатьох звичних ситуаціях касти є важливими. Деякі приклади:

  1. Під час взаємодії з стороннім кодом (особливо, коли цей код є typedefs). (Приклад: GLfloat<--> double<--> Real.)
  2. Кастинг із похідного на базовий клас вказівника / довідки: Це настільки часто і природно, що компілятор буде робити це неявно. Якщо зробити це явним, збільшується читабельність, акторський склад - це крок вперед, а не назад!
  3. Кастинг від базового до похідного вказівника / посилання класу: Також поширений, навіть у добре розробленому коді. (Приклад: неоднорідні контейнери.)
  4. Усередині бінарної серіалізації / десеріалізації чи іншого коду низького рівня, який потребує доступу до необроблених байтів вбудованих типів.
  5. Будь-який час, коли просто зрозуміліше, зручніше і зручніше для читання, використовувати інший тип. (Приклад: std::size_type-> int.)

Безумовно, існує багато ситуацій, коли не доцільно використовувати акторський склад, і це важливо навчитися також; Я не буду надто детально описуватись, оскільки відповіді вище зробили гарну роботу, вказуючи на деякі з них.


1

Для детальної розробки відповіді KDeveloper це не властиво безпеці типу. Якщо ви робите кастинг, немає гарантії того, що те, що ви передаваєте і на яке будете робити трансляцію, збігатиметься, і якщо це станеться, ви отримаєте виняток із виконання часу, що завжди погано.

Що стосується C #, оскільки він включає операторів isта asоператорів, ви маєте можливість (здебільшого) визначитися з тим, чи вдасться виступити. Через це вам слід вжити відповідних заходів, щоб визначити, чи вдалася б операція чи ні, чи належним чином пройти.


1

У випадку з C # потрібно бути обережнішими під час кастингу через накладні витрати на бокс / розблокування під час роботи з типами цінностей.


1

Не впевнений, чи хтось уже згадував про це, але в C # кастинг може використовуватися досить безпечним чином, і це часто необхідно. Припустимо, ви отримуєте об'єкт, який може бути декількох типів. Використовуючи isключове слово, ви можете спершу підтвердити, що об’єкт справді такого типу, до якого ви збираєтеся його передати, а потім передати об'єкт безпосередньо цьому типу. (Я не дуже працював з Java, але впевнений, що існує дуже простий спосіб зробити це і там).


1

Ви кидаєте об'єкт лише певному типу, якщо виконуються 2 умови:

  1. ви знаєте, що це такого типу
  2. компілятор ні

Це означає, що не вся інформація, яку ви маєте, добре представлена ​​в типовій структурі, яку ви використовуєте. Це погано, тому що ваша реалізація повинна семантично містити вашу модель, чого в цьому випадку явно немає.

Тепер, коли ви робите акторський склад, то це може мати дві різні причини:

  1. Ви зробили погану роботу в вираженні відносин типу.
  2. система типів мов просто недостатньо виразна для їх фразування.

У більшості мов ви багато разів стикаєтесь з 2-ю ситуацією. Загальна інформація, як на Java, трохи допомагає, система шаблонів C ++ ще більше, але важко освоїти, і навіть тоді деякі речі можуть бути неможливими або просто не варті зусиль.

Таким чином, ви можете сказати, акторський склад - це брудна хак, щоб обійти ваші проблеми, щоб висловити певні відносини певного типу якоюсь конкретною мовою. Слід уникати брудних злому. Але ніколи не можна жити без них.


0

Зазвичай шаблони (або дженерики) більш безпечні для типів, ніж касти. У цьому відношенні я б сказав, що проблема з кастингом - це безпека типу. Однак є ще одне більш тонке питання, пов’язане, особливо, із занедбаністю: дизайн. Щонайменше, з моєї точки зору, зрив - це кодовий запах, вказівка ​​на те, що з моїм дизайном щось може бути не так, і я повинен досліджувати далі. Чому просто: якщо ви "правильно" отримаєте абстракції, вони вам просто не потрібні! Приємне питання до речі ...

Ура!


0

Якщо бути по-справжньому стислим, вагомою причиною є портативність. Різна архітектура, яка обидва вміщує одну і ту ж мову, може мати, скажімо, різні розміри. Отже, якщо я переходжу з ArchA на ArchB, який має більш вузький внутрішній вигляд, я можу побачити дивне поведінку в кращому випадку, а проникнення в найгіршому випадку.

(Я чітко ігнорую незалежний байт-код та Іл. Архітектури.)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.