Когда использовать C-style касты?
В C++ есть куча своих кастов (static_cast, dynamic_cast, ...). Есть ли случаи, когда стоит использовать C-style каст (type)obj?
Ответы (2 шт):
Сначала терминология:
MyType(x)- это functional cast. От C-style каста он ничем не отличается1, поэтому ниже я и тот и другой буду называть C-style кастом.MyType(x, y, ...)- это вызов конструктора. Это не C-style каст.MyType{x}иMyType{x, y, ...}- это не C-style касты.
Есть два (с половиной) лагеря. (Относительно разумных лагеря. Вообще везде писать C-style касты неразумно, поэтому не рассматривается.)
Лагерь 1: C-style касты пишутся короче, поэтому там, где они безопасны, можно/лучше их и использовать.
Основная претензия к C-style кастам в их работе с указателями и ссылками.
(MyClass &)ptr - вот это что? Варианты:
Каст к родителю (который может сдвигать указатель при множественном наследовании).
- Вообще каст к непубличному (точнее, недоступному) родителю, который C++-style касты не способны выполнить.
reinterpret_cast.Гнусный
const_cast(возможно в комбинации сreinterpret_cast).operator MyClass &у класса.
(аналогично каст в ссылку, не считая последнего пункта)
Во-первых, на глаз сложно определить, какой из вариантов тут сработал. Во-вторых, вариант может неожиданно поменяться при изменении кода (убрали родителя, и вполне законный каст к родителю стал работать как reinterpret_cast).
Еще возможная проблема - хотели сделать reinterpret_cast, но ошиблись типом так, что добавился еще и const_cast. Или наоборот.
А касты в не-указатель-не-ссылку вполне безобидны:
void DrawHealthBar(float percentage); // 0 <= percentage <= 1
int current_health = 80;
int max_health = 100;
DrawHealthBar(current_health / float(max_health));
От того, что вы здесь напишете static_cast<float>(max_health), код безопаснее не станет.
Поэтому лагерь 1 считает, что к не указателям и не ссылкам можно кастовать C-style кастом, а к остальному нужно кастовать C++-кастами.
Тут возможны уточнения, например (T *)nullptr от плюсового каста никак не выигрывает. Можно либо разрешить для краткости, либо запретить для упрощения правил.
Лагерь 2: Везде C++-style касты для однообразия.
Не "для безопасности", как иногда говорят, потому что это ничем не безопаснее варианта 1.
Это именно упрощение правил, чтобы не надо было запоминать, когда С-style касты безопасны, а когда нет. Но это происходит за счет удлиннения записи.
Безотносительно двух лагерей еще есть MyClass{x}, который еще более ограничен, чем C-style cast и static_cast. (Например, int во float не кастует, потому что потеря точности.)
В обоих лагерях некоторые любят по умолчанию использовать его вместо C-style каста и static_castа, если он работает. А можно сразу брать C-style каст или static_cast.
В тему: MyClass x(...); против MyClass x{...};.
1 Одно небольшое отличие - functional cast не компилируется для некоторых типов, в записи которых испольуются пробелы или специальные символы. Это просто ограничение синтаксиса, и если вынести тип в typedef/using, то все начинает работать:
// int *ptr = int*(x); // Ошибка.
// unsigned int y = unsigned int(x); // Ошибка. Хотя MSVC это компилирует, это нарушает стандарт.
using A = int *;
int *ptr = A(x); // ок
using B = unsigned int;
unsigned int y = B(x); // ок
unsigned int z = unsigned(x); // ок
Я бы использовал C-style касты в следующих случаях:
- когда необходим игнорирующий access specifiers каст:
struct A {
};
struct B : private A {
}* b;
// auto c = static_cast<A*>(b); // CE
auto c = (A*)b; // OK
Это возможно только с помощью С-style каста и используется в реальном коде (см. P2637).
The conversions performed by
— a
const_cast,— a
static_cast,— a
static_castfollowed by aconst_cast,— a
reinterpret_cast, or— a
reinterpret_castfollowed by aconst_cast,can be performed using the cast notation of explicit type conversion. The same semantic restrictions and behaviors apply, with the exception that in performing a static_cast in the following situations the conversion is valid even if the base class is inaccessible: ...
- когда хотим проигнорировать возвращаемое значение функции и не можем/хотим использовать другие решения:
// external function without [[maybe_unused]]
bool foo_with_side_effects();
// too verbose
[[maybe_unused]] auto _ = foo_with_side_effects();
static_cast<void>(foo_with_side_effects());
// unspecified and requires a header
#include <tuple>
std::ignore = foo_with_side_effects();
// requires P2169 in C++26
auto _ = foo_with_side_effects();
// good ol' C-style cast
(void)foo_with_side_effects();