Когда использовать C-style касты?
В C++ есть куча своих кастов (static_cast
, dynamic_cast
, ...). Есть ли случаи, когда стоит использовать C-style каст (type)obj
?
В C++ есть куча своих кастов (static_cast
, dynamic_cast
, ...). Есть ли случаи, когда стоит использовать C-style каст (type)obj
?
Сначала терминология:
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
- вот это что? Варианты:
Каст к родителю (который может сдвигать указатель при множественном наследовании).
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 касты в следующих случаях:
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_cast
followed by aconst_cast
,— a
reinterpret_cast
, or— a
reinterpret_cast
followed 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();