Избегайте приведений типов
Причина
Приведения типов — известный источник ошибок и делают некоторые оптимизации ненадёжными.
Пример (плохой)
double d = 2;
auto p = (long*)&d;
auto q = (long long*)&d;
cout << d << ' ' << *p << ' ' << *q << '\n';
Что, по-вашему, напечатает этот фрагмент? Результат в лучшем случае реализационно-зависим. Я получил:
2 0 4611686018427387904
Добавив:
*q = 666;
cout << d << ' ' << *p << ' ' << *q << '\n';
Я получил:
3.29048e-321 666 666
Удивлены? На самом деле это неопределённое поведение, и оно также могло бы завершить программу аварийно.
Примечание
Программисты, пишущие приведения типов, обычно думают, что они знают, что делают, или что написание приведения делает программу «легче читать». На самом деле они часто отключают общие правила использования значений. Разрешение перегрузки и инстанцирование шаблонов обычно выбирают правильную функцию, если таковая имеется. Если её нет, возможно, стоит её создать, а не применять локальное исправление (приведение).
Примечания
Приведения необходимы в языке системного программирования. Например, как иначе получить адрес регистра устройства в указатель? Однако приведения также серьёзно злоупотребляются и являются основным источником ошибок.
Если вы чувствуете необходимость в большом количестве приведений, возможно, существует фундаментальная проблема проектирования.
Профиль типов запрещает reinterpret_cast и приведения в стиле C.
Никогда не приводите к (void) для игнорирования возвращаемого значения [[nodiscard]]. Если вы намеренно хотите отбросить такой результат, сначала хорошо подумайте, действительно ли это хорошая идея (обычно есть веская причина, по которой автор функции или возвращаемого типа использовал [[nodiscard]]). Если вы всё ещё считаете это уместным и ваш рецензент согласен, используйте std::ignore = для отключения предупреждения — это просто, переносимо и легко находится поиском.
Альтернативы
Приведения широко (не)используются. Современный C++ имеет правила и конструкции, которые устраняют необходимость в приведениях во многих контекстах, например:
- Использование шаблонов
- Использование
std::variant - Опора на хорошо определённые, безопасные, неявные преобразования между типами указателей
- Использование
std::ignore =для игнорирования значений[[nodiscard]].
Контроль
- Отмечайте все приведения в стиле C, включая к
void. - Отмечайте функциональные приведения с использованием
Type(value). Вместо этого используйтеType{value}, что не является сужающим. (См. ES.64.) - Отмечайте идентичные приведения между типами указателей, где исходный и целевой типы одинаковы (#pro-type-identitycast).
- Отмечайте явное приведение указателя, которое могло бы быть неявным.