Явное преобразование С++ действительно так плохо?
Мои знания о С++ на данный момент более академичны, чем что-либо еще. Во всем моем чтении до сих пор использование явного преобразования с именованными приведениями (const_cast
, static_cast
, reinterpret_cast
, dynamic_cast
) имело большую предупреждающую метку (и это легко понять, почему), что подразумевает, что явное преобразование является симптомом плохой конструкции и должно использоваться только в качестве крайней меры в отчаянных обстоятельствах. Итак, я должен спросить:
Является ли явное преобразование с именованными листами действительно просто фальсификатором присяжных или есть более грациозное и позитивное приложение к этой функции? Есть ли хороший пример последнего?
Ответы
Ответ 1
Есть случаи, когда вы просто не можете обойтись без него. Как этот. Проблема в том, что у вас есть множественное наследование и нужно преобразовать указатель this
в void*
, гарантируя, что указатель, который входит в void*
, все равно укажет на правый подобъект текущего объекта. Использование явного приведения - единственный способ добиться этого.
Есть мнение, что если вы не можете пойти без броска, у вас плохой дизайн. Я не могу согласиться с этим полностью - возможны разные ситуации, в том числе упомянутые выше, но, возможно, если вам нужно слишком часто использовать явные приведения, у вас действительно плохой дизайн.
Ответ 2
Бывают ситуации, когда вы не можете избежать явных бросков. Особенно при взаимодействии с библиотеками C или плохо спроектированными С++-библиотеками (например, в качестве примеров используется скрипт COM
).
В общем, использование явных отливок - это красная селедка. Это не обязательно означает плохой код, но он привлекает внимание к потенциально опасному использованию.
Однако вы не должны бросать 4 броска в один и тот же пакет: static_cast
и dynamic_cast
часто используются для подбрасывания (от Base to Derived) или для навигации между связанными типами. Их появление в коде довольно нормально (действительно, трудно написать шаблон Visitor
без него).
С другой стороны, использование const_cast
и reinterpret_cast
намного опаснее.
- с помощью
const_cast
, чтобы попытаться изменить объект только для чтения, это поведение undefined (спасибо Джеймсу Макнеллису за исправление)
-
reinterpret_cast
обычно используется только для работы с необработанной памятью (распределителями)
Они, конечно, используют их, но не должны встречаться в нормальном коде. Для работы с внешними или C API они могут быть необходимы, хотя.
По крайней мере, мое мнение.
Ответ 3
Насколько плох литье, как правило, зависит от типа броска. Есть законное использование для всех этих приведений, но некоторый запах хуже других.
const_cast
используется для отбрасывания const
ness (так как добавление его не требует приведения). В идеале это никогда не должно использоваться. Это позволяет легко вызвать поведение undefined (пытается изменить объект, первоначально обозначенный как const
), и в любом случае нарушает const
-корректность программы. Иногда это необходимо при взаимодействии с API, которые сами не являются const
-корректными, что может, например, запрашивать char *
, когда они будут рассматривать его как const char *
, но так как вы не должны писать API-интерфейсы, которые это знак того, что вы используете действительно старый API или кто-то испортил.
reinterpret_cast
всегда будет зависящим от платформы, и поэтому он в лучшем случае сомневается в переносном коде. Более того, если вы не выполняете низкоуровневые операции над физической структурой объектов, это не сохраняет смысла. В C и С++ тип должен быть значимым. int
- это число, которое что-то означает; a int
, который является в основном конкатенацией char
, ничего не значит.
dynamic_cast
обычно используется для downcasting; например от Base *
до Derived *
, при условии, что либо он работает, либо он возвращает 0. Это подчиняет OO почти так же, как и оператор switch
для тега типа: он перемещает код, определяющий класс находится далеко от определения класса. Это связывает определения классов с другим кодом и увеличивает потенциальную нагрузку на обслуживание.
static_cast
используется для преобразований данных, которые, как известно, в целом правильны, такие как преобразования в и из void *
, известные безопасные указатели в иерархии классов. О худшем вы можете сказать, потому что это в какой-то степени подрывает систему типов. Это может понадобиться при взаимодействии с библиотеками C или с частью C стандартной библиотеки, поскольку void *
часто используется в функциях C.
В общем, хорошо продуманный и хорошо написанный код на С++ позволит избежать описанных выше случаев использования, в некоторых случаях, потому что единственное использование броска - это делать потенциально опасные вещи, а в других случаях, поскольку такой код имеет тенденцию избегать необходимость таких преобразований. Система типа С++ обычно считается хорошей вещью для поддержания и отбрасывает ее.
Ответ 4
ИМО, как и большинство вещей, они являются инструментами с надлежащим использованием и ненадлежащими. Кастинг, вероятно, является областью, где инструменты часто используются ненадлежащим образом, например, для перевода между типом int
и указателем с reinterpret_cast
(который может разбиться на платформах, где эти два являются разными) или const_cast
и т.д.
Если вы знаете, для чего они предназначены и предназначены для использования, нет абсолютно ничего плохого в использовании их для того, для чего они предназначены.
Ответ 5
Есть ирония явных бросков. Разработчик, чьи плохие навыки разработки на C++ заставляют его писать код, требующий много кастинга, - это тот же самый разработчик, который не использует явные механизмы литья должным образом или вообще не помещает его код в приведения стиля C.
С другой стороны, разработчик, который понимает их цель, когда использовать их, а когда нет, и какими альтернативами, не пишет код, требующий много кастинга!
Ознакомьтесь с более мелкомасштабными вариациями этих прикладов, например polymorphic_cast
, в библиотеке конверсий boost, чтобы дать вам представление о том, насколько осторожны программисты на С++, когда дело доходит до кастинга.
Ответ 6
Броски являются признаком того, что вы пытаетесь поставить круглую привязку в квадратное отверстие. Иногда эта часть работы. Но если у вас есть некоторый контроль над отверстием и привязкой, было бы лучше не создавать это условие, и написание броска должно заставить вас спросить себя, есть ли что-то, что вы могли бы сделать, это было немного более плавным.