Явное преобразование С++ действительно так плохо?

Мои знания о С++ на данный момент более академичны, чем что-либо еще. Во всем моем чтении до сих пор использование явного преобразования с именованными приведениями (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

Броски являются признаком того, что вы пытаетесь поставить круглую привязку в квадратное отверстие. Иногда эта часть работы. Но если у вас есть некоторый контроль над отверстием и привязкой, было бы лучше не создавать это условие, и написание броска должно заставить вас спросить себя, есть ли что-то, что вы могли бы сделать, это было немного более плавным.