C++
Явные преобразования типов
Поиск…
Вступление
Выражение может быть преобразовано в явном виде или преобразованный к типу T
с помощью dynamic_cast<T>
, static_cast<T>
, reinterpret_cast<T>
или const_cast<T>
, в зависимости от того, какого типа броска предназначен.
C ++ также поддерживает фрейм-нотацию в стиле функции, T(expr)
и буквенную нотацию C-стиля, (T)expr
.
Синтаксис
- простой тип-спецификатор
(
)
- простой тип-спецификатор
(
список выражений)
- simple-type-specifier braced-init-list
- typename-specifier
(
)
- typename-specifier
(
список выражений)
- typename-specifier braced-init-list
-
dynamic_cast
<
type-id>
(
выражение)
-
static_cast
<
type-id>
(
выражение)
-
reinterpret_cast
<
type-id>
(
выражение)
-
const_cast
<
type-id>
(
выражение)
-
(
тип-id)
литье-выражение
замечания
Все шесть нотных обозначений имеют одну общую черту:
- Приведение к ссылочному типу lvalue, как и в
dynamic_cast<Derived&>(base)
, дает значение l. Поэтому, когда вы хотите что-то делать с одним и тем же объектом, но относитесь к нему как к другому типу, вы должны указывать тип ссылки lvalue. - Приведение к ссылочному типу rvalue, как и
static_cast<string&&>(s)
, дает значение r. - Приведение к не ссылочному типу, как в
(int)x
, дает значение prvalue, которое можно рассматривать как копию значения, которое выполняется, но с другим типом от оригинала.
Ключевое слово reinterpret_cast
отвечает за выполнение двух разных «небезопасных» преобразований:
- Преобразования типа «punning» , которые могут использоваться для доступа к памяти одного типа, как если бы он был другого типа.
- Конверсии между целыми типами и типами указателей в любом направлении.
Ключевое слово static_cast
может выполнять множество различных преобразований:
Основание для производных преобразований
Любое преобразование, которое может быть выполнено путем прямой инициализации, включая как неявные преобразования, так и преобразования, которые вызывают явный конструктор или функцию преобразования. См. Здесь и здесь для более подробной информации.
void
, которое отбрасывает значение выражения.// on some compilers, suppresses warning about x being unused static_cast<void>(x);
Между арифметическими и перечисляемыми типами и между различными типами перечислений. См. Переходы перечисления
От указателя к члену производного класса, чтобы указатель на член базового класса. Указанные типы должны совпадать. См. Производное преобразование базы для указателей на участников
- От lvalue до значения x, как в
std::move
. См. Перемещение семантики .
Основание для производной конверсии
Указатель на базовый класс может быть преобразован в указатель на производный класс с использованием static_cast
. static_cast
не выполняет проверку во время выполнения и может привести к неопределенному поведению, когда указатель фактически не указывает на нужный тип.
struct Base {};
struct Derived : Base {};
Derived d;
Base* p1 = &d;
Derived* p2 = p1; // error; cast required
Derived* p3 = static_cast<Derived*>(p1); // OK; p2 now points to Derived object
Base b;
Base* p4 = &b;
Derived* p5 = static_cast<Derived*>(p4); // undefined behaviour since p4 does not
// point to a Derived object
Аналогично, ссылка на базовый класс может быть преобразована в ссылку на производный класс с использованием static_cast
.
struct Base {};
struct Derived : Base {};
Derived d;
Base& r1 = d;
Derived& r2 = r1; // error; cast required
Derived& r3 = static_cast<Derived&>(r1); // OK; r3 now refers to Derived object
Если тип источника является полиморфным, dynamic_cast
может использоваться для преобразования базы в производную. Он выполняет проверку времени выполнения, а отказ восстанавливается, а не создает неопределенное поведение. В случае с указателем при ошибке возвращается нулевой указатель. В ссылочном случае исключение вызывается при ошибке типа std::bad_cast
(или класса, полученного из std::bad_cast
).
struct Base { virtual ~Base(); }; // Base is polymorphic
struct Derived : Base {};
Base* b1 = new Derived;
Derived* d1 = dynamic_cast<Derived*>(b1); // OK; d1 points to Derived object
Base* b2 = new Base;
Derived* d2 = dynamic_cast<Derived*>(b2); // d2 is a null pointer
Изгнание
Указатель на объект const может быть преобразован в указатель на объект, не const_cast
с использованием const_cast
слова const_cast
. Здесь мы используем const_cast
для вызова функции, которая не является const_cast
. Он принимает только аргумент non-const char*
хотя он никогда не пишет через указатель:
void bad_strlen(char*);
const char* s = "hello, world!";
bad_strlen(s); // compile error
bad_strlen(const_cast<char*>(s)); // OK, but it's better to make bad_strlen accept const char*
const_cast
для ссылочного типа может быть использован для преобразования значения const_cast
с const_cast
в значение, не соответствующее const_cast
.
const_cast
опасен, поскольку он не позволяет системе типа C ++ не пытаться изменить объект const. Это приводит к неопределенному поведению.
const int x = 123;
int& mutable_x = const_cast<int&>(x);
mutable_x = 456; // may compile, but produces *undefined behavior*
Преобразование типа punning
Указатель (соответственно ссылка) на тип объекта может быть преобразован в указатель (соотв. Ссылку) на любой другой тип объекта, используя reinterpret_cast
. Это не вызывает никаких конструкторов или функций преобразования.
int x = 42;
char* p = static_cast<char*>(&x); // error: static_cast cannot perform this conversion
char* p = reinterpret_cast<char*>(&x); // OK
*p = 'z'; // maybe this modifies x (see below)
Результат reinterpret_cast
представляет тот же адрес, что и операнд, при условии, что адрес соответствующим образом выровнен для целевого типа. В противном случае результат не указан.
int x = 42;
char& r = reinterpret_cast<char&>(x);
const void* px = &x;
const void* pr = &r;
assert(px == pr); // should never fire
Результат reinterpret_cast
не указан, за исключением того, что указатель (соответствующая ссылка) выдержит круговую поездку от типа источника до типа назначения и обратно, если требование выравнивания типа назначения не является более строгим, чем требование типа источника.
int x = 123;
unsigned int& r1 = reinterpret_cast<unsigned int&>(x);
int& r2 = reinterpret_cast<int&>(r1);
r2 = 456; // sets x to 456
В большинстве реализаций reinterpret_cast
не изменяет адрес, но это требование не было стандартизировано до C ++ 11.
reinterpret_cast
также может использоваться для преобразования из одного типа-указателя-данных в другой или одного типа-указателя-члена в другой.
Использование reinterpret_cast
считается опасным, поскольку чтение или запись через указатель или ссылку, полученные с использованием reinterpret_cast
могут вызывать неопределенное поведение, когда исходный и целевой типы не связаны.
Преобразование между указателем и целым числом
Указатель объекта (включая void*
) или указатель функции может быть преобразован в целочисленный тип с использованием reinterpret_cast
. Это будет скомпилировано только в том случае, если тип назначения достаточно длинный. Результат определяется реализацией и обычно дает числовой адрес байта в памяти, на который указывает указатель.
Как правило, long
или unsigned long
достаточно длинный, чтобы удерживать любое значение указателя, но это не гарантируется стандартом.
Если существуют типы std::intptr_t
и std::uintptr_t
, они гарантированно будут достаточно длинными, чтобы удерживать void*
(и, следовательно, любой указатель на тип объекта). Однако они не гарантируют, что они будут достаточно длинными, чтобы удерживать указатель на функцию.
Аналогично, reinterpret_cast
может использоваться для преобразования целочисленного типа в тип указателя. Опять же результат определяется реализацией, но значение указателя гарантируется неизменным путем кругового перехода через целочисленный тип. Стандарт не гарантирует, что значение нуля преобразуется в нулевой указатель.
void register_callback(void (*fp)(void*), void* arg); // probably a C API
void my_callback(void* x) {
std::cout << "the value is: " << reinterpret_cast<long>(x); // will probably compile
}
long x;
std::cin >> x;
register_callback(my_callback,
reinterpret_cast<void*>(x)); // hopefully this doesn't lose information...
Преобразование с помощью явного конструктора или явной функции преобразования
Преобразование, которое включает вызов явного конструктора или функции преобразования, не может выполняться неявно. Мы можем запросить, чтобы преобразование выполнялось явно с использованием static_cast
. Значение такое же, как и для прямой инициализации, за исключением того, что результат является временным.
class C {
std::unique_ptr<int> p;
public:
explicit C(int* p) : p(p) {}
};
void f(C c);
void g(int* p) {
f(p); // error: C::C(int*) is explicit
f(static_cast<C>(p)); // ok
f(C(p)); // equivalent to previous line
C c(p); f(c); // error: C is not copyable
}
Неявное преобразование
static_cast
может выполнять любое неявное преобразование. Иногда использование static_cast
может быть полезным, например, в следующих примерах:
При передаче аргументов в эллипсис «ожидаемый» тип аргумента не статически известен, поэтому не происходит никакого неявного преобразования.
const double x = 3.14; printf("%d\n", static_cast<int>(x)); // prints 3 // printf("%d\n", x); // undefined behaviour; printf is expecting an int here // alternative: // const int y = x; printf("%d\n", y);
Без явного преобразования типов
double
объект будет передан эллипсису, и произойдет неопределенное поведение.Оператор присваивания производного класса может вызвать оператор присваивания базового класса следующим образом:
struct Base { /* ... */ }; struct Derived : Base { Derived& operator=(const Derived& other) { static_cast<Base&>(*this) = other; // alternative: // Base& this_base_ref = *this; this_base_ref = other; } };
Конверсии Enum
static_cast
может преобразовывать из целого числа или типа с плавающей точкой в тип перечисления (независимо от того, областны ли они или не определены) и наоборот. Он также может преобразовывать типы перечислений.
- Преобразование из неперечисленного типа перечисления в арифметический тип является неявным преобразованием; можно, но не обязательно, использовать
static_cast
.
Когда тип перечислимого типа конвертируется в арифметический тип:
- Если значение перечисления может быть представлено точно в целевом типе, результатом будет это значение.
- В противном случае, если тип назначения является целым типом, результат не указан.
- В противном случае, если тип назначения является типом с плавающей точкой, результат будет таким же, как и для преобразования в базовый тип, а затем в тип с плавающей точкой.
Пример:
enum class Format { TEXT = 0, PDF = 1000, OTHER = 2000, }; Format f = Format::PDF; int a = f; // error int b = static_cast<int>(f); // ok; b is 1000 char c = static_cast<char>(f); // unspecified, if 1000 doesn't fit into char double d = static_cast<double>(f); // d is 1000.0... probably
Когда целочисленный или перечисляемый тип преобразуется в тип перечисления:
- Если исходное значение находится в пределах диапазона адресата, результатом является это значение. Обратите внимание, что это значение может быть неравным для всех счетчиков.
- В противном случае результат не указан (<= C ++ 14) или undefined (> = C ++ 17).
Пример:
enum Scale { SINGLE = 1, DOUBLE = 2, QUAD = 4 }; Scale s1 = 1; // error Scale s2 = static_cast<Scale>(2); // s2 is DOUBLE Scale s3 = static_cast<Scale>(3); // s3 has value 3, and is not equal to any enumerator Scale s9 = static_cast<Scale>(9); // unspecified value in C++14; UB in C++17
Когда тип с плавающей точкой преобразуется в тип перечисления, результат будет таким же, как преобразование в базовый тип перечисления, а затем в тип перечисления.
enum Direction { UP = 0, LEFT = 1, DOWN = 2, RIGHT = 3, }; Direction d = static_cast<Direction>(3.14); // d is RIGHT
Производится преобразование базы для указателей на членов
Указатель на член производного класса может быть преобразован в указатель на член базового класса с использованием static_cast
. Указанные типы должны совпадать.
Если операнд является нулевым указателем на значение члена, результат также является нулевым указателем на значение члена.
В противном случае преобразование будет действительным только в том случае, если элемент, на который указывает операнд, фактически существует в целевом классе, или если целевой класс является базовым или производным классом класса, содержащего член, на который указывает операнд. static_cast
не проверяет достоверность. Если преобразование недействительно, поведение не определено.
struct A {};
struct B { int x; };
struct C : A, B { int y; double z; };
int B::*p1 = &B::x;
int C::*p2 = p1; // ok; implicit conversion
int B::*p3 = p2; // error
int B::*p4 = static_cast<int B::*>(p2); // ok; p4 is equal to p1
int A::*p5 = static_cast<int A::*>(p2); // undefined; p2 points to x, which is a member
// of the unrelated class B
double C::*p6 = &C::z;
double A::*p7 = static_cast<double A::*>(p6); // ok, even though A doesn't contain z
int A::*p8 = static_cast<int A::*>(p6); // error: types don't match
void * to T *
В C ++ void*
не может быть неявно преобразован в T*
где T
- тип объекта. Вместо этого static_cast
следует использовать для явного преобразования. Если операнд фактически указывает на объект T
, результат указывает на этот объект. В противном случае результат не указан.
Даже если операнд не указывает на объект T
, если операнд указывает на байт, адрес которого правильно выровнен для типа T
, результат преобразования указывает на тот же байт.
// allocating an array of 100 ints, the hard way
int* a = malloc(100*sizeof(*a)); // error; malloc returns void*
int* a = static_cast<int*>(malloc(100*sizeof(*a))); // ok
// int* a = new int[100]; // no cast needed
// std::vector<int> a(100); // better
const char c = '!';
const void* p1 = &c;
const char* p2 = p1; // error
const char* p3 = static_cast<const char*>(p1); // ok; p3 points to c
const int* p4 = static_cast<const int*>(p1); // unspecified in C++03;
// possibly unspecified in C++11 if
// alignof(int) > alignof(char)
char* p5 = static_cast<char*>(p1); // error: casting away constness
Литье в стиле C
Кастинг C-Style можно рассматривать как «Лучшее усилие» и назван так, что он является единственным литым, который может быть использован в C. Синтаксис этого (NewType)variable
- (NewType)variable
.
Всякий раз, когда этот приведение используется, он использует одну из следующих c ++-трансляций (по порядку):
-
const_cast<NewType>(variable)
-
static_cast<NewType>(variable)
-
const_cast<NewType>(static_cast<const NewType>(variable))
-
reinterpret_cast<const NewType>(variable)
-
const_cast<NewType>(reinterpret_cast<const NewType>(variable))
Функциональное литье очень похоже, но в качестве нескольких ограничений в результате его синтаксиса: NewType(expression)
. В результате могут быть применены только типы без пробелов.
Лучше использовать новую c ++-трансляцию, потому что она более читаема и может быть легко обнаружена в любом месте исходного кода на C ++, и ошибки будут обнаружены во время компиляции, а не во время выполнения.
Поскольку этот приведение может привести к непреднамеренному reinterpret_cast
, его часто считают опасным.