Оператор static_cast
Преобразует выражение в тип идентификатора типа, основываясь только на типах, присутствующих в выражении.
Синтаксис
static_cast ( expression )
Замечания
В стандартном языке C++, проверка типа во время выполнения не выполняется, что обеспечивает безопасность преобразования. В C ++/CX выполняются проверки во время компиляции и во время выполнения. Дополнительные сведения см. в разделе Приведение.
Оператор static_cast можно использовать для операций, таких как преобразование указателя в базовый класс в указатель на производный класс. Такие преобразования не всегда являются безопасными.
Как правило, вы static_cast используете, когда требуется преобразовать числовые типы данных, такие как перечисления в инты или инты в с плавающей запятой, и некоторые типы данных, участвующие в преобразовании. static_cast преобразования не так безопасны, как dynamic_cast преобразования, так как static_cast не проверка типа времени выполнения. dynamic_cast Неоднозначный dynamic_cast указатель завершится ошибкой, в то время static_cast как возврат, как будто ничего неправильного; это может быть опасно. Хотя dynamic_cast преобразования являются более безопасными, dynamic_cast работает только на указателях или ссылках, а тип времени выполнения проверка является издержками. Дополнительные сведения см. в разделе dynamic_cast Оператор.
В следующем примере строка D* pd2 = static_cast(pb); небезопасна, поскольку D может иметь поля и методы, не входящие в B . Однако строка B* pb2 = static_cast(pd); является безопасным преобразованием, поскольку D всегда содержит все B .
// static_cast_Operator.cpp // compile with: /LD class B <>; class D : public B <>; void f(B* pb, D* pd) < D* pd2 = static_cast(pb); // Not safe, D can have fields // and methods that are not in B. B* pb2 = static_cast(pd); // Safe conversion, D always // contains all of B. >
В отличие от dynamic_cast, проверка времени выполнения не выполняется при static_cast преобразовании pb . Объект, на который указывает pb , может не быть объектом типа D , и в этом случае использование *pd2 может привести ужасным последствиям. Например, вызов функции, являющейся членом класса D , но не класса B , может привести к нарушению прав доступа.
static_cast Операторы dynamic_cast перемещают указатель на всю иерархию классов. Тем не менее, static_cast использует исключительно информацию, указанную в заявлении приведения, и поэтому может быть небезопасной. Например:
// static_cast_Operator_2.cpp // compile with: /LD /GR class B < public: virtual void Test()<>>; class D : public B <>; void f(B* pb) < D* pd1 = dynamic_cast(pb); D* pd2 = static_cast(pb); >
Если pb действительно указывает на объект типа D , pd1 и pd2 получат одно и то же значение. Также они получат одно и то же значение, если pb == 0 .
Если pb указывает на объект типа B , а не на полный D класс, то dynamic_cast будет известно достаточно, чтобы вернуть ноль. Однако, полагается на утверждение программиста, static_cast указывающее pb на объект типа D и просто возвращает указатель на этот предполагаемый D объект.
Следовательно, static_cast можно сделать обратное неявное преобразование, в этом случае результаты не определены. Программисту осталось убедиться, что результаты static_cast преобразования безопасны.
Это поведение также применяется к типам, отличным от типов класса. Например, static_cast можно использовать для преобразования из int в объект char . Однако результирующий char результат может не иметь достаточно битов для хранения всего int значения. Опять же, программисту осталось убедиться, что результаты static_cast преобразования безопасны.
Оператор static_cast также можно использовать для выполнения любого неявного преобразования, включая стандартные преобразования и определяемые пользователем преобразования. Например:
// static_cast_Operator_3.cpp // compile with: /LD /GR typedef unsigned char BYTE; void f() < char ch; int i = 65; float f = 2.5; double dbl; ch = static_cast(i); // int to char dbl = static_cast(f); // float to double i = static_cast(ch); >
Оператор static_cast может явно преобразовать целочисленное значение в тип перечисления. Если значение типа целого не оказывается в диапазоне значений перечисления, получаемое значение перечисления не определено.
Оператор static_cast преобразует значение указателя NULL в значение null указателя целевого типа.
Любое выражение можно явно преобразовать в тип void оператором static_cast . Тип конечной пустоты может дополнительно включать const volatile атрибут или __unaligned атрибут.
Оператор static_cast не может отбросить const volatile атрибуты или __unaligned атрибуты. Сведения об удалении этих атрибутов см . в const_cast операторе .
C++/CLI: из-за опасности выполнения не проверка приведения на вершине перемещенного сборщика мусора, использование static_cast должно находиться только в критическом коде производительности, если вы уверены, что он будет работать правильно. Если вы должны использовать static_cast в режиме выпуска, замените его safe_cast в сборках отладки, чтобы убедиться в успешном выполнении.
Для чего нужен static_cast, как он работает и где его применяют?
У static_cast очень много различных применений. Его идея состоит в следующем: это ограниченный по мощи C-style cast. Ограничение нужно потому, что C-style cast может привести что угодно к чему угодно (ну, почти), и тем самым может скрыть ошибку. Например, вы можете случайно закастить const char* в char* , получив крэш в некоторых системах с аппаратной поддержкой const-памяти. static_cast вам такого не позволит.
Большую часть времени, когда вы хотите сделать явное преобразование типов (а я надеюсь, это бывает достаточно редко), вы хотите именно static_cast .
Формальный список всего, что умеет static_cast , очень большой, я приведу лишь наиболее важные вещи, которые он умеет (а также которые он не умеет):
- Преобразование указателя на родительский класс к указателю на дочерний класс. Объект по указателю обязан быть правильного дочернего класса, иначе undefined behaviour. Если вы не уверены и хотите проверить, тот ли подкласс у объекта, пользуйтесь dynamic_cast (он специально для этого предназначен).
- Преобразования между числовыми типами. int , long , char , unsigned int — все их можно кастить друг в друга при помощи static_cast .
- Можно закастить любое выражение в void . Результат будет вычислен и отброшен (но побочные эффекты, разумеется, выполнятся).
- static_cast может привести константу nullptr к любому типу-указателю. Обычно это не нужно и можно полагаться на неявное преобразование типов, но иногда (например, для выбора нужной перегрузки функции) такое может пригодится.
- Преобразование между указателями на в принципе несовместимые типы. Например, указатель на double нельзя привести к указателю на int . Для трюков с нарушением type safety пользуйтесь reinterpret_cast .
- Указатели на типы, а также сами типы с несовместимыми атрибутами const и/или volatile . Если вам необходимо нарушить const-корректность, пользуйтесь const_cast .
- Разумеется, вы не сможете привести указатель на функцию-член к указателю на обычную функцию, или указатель на код к указателю на данные. Для подобных грязных хаков пользуйтесь reinterpret_cast .
Ещё одной причиной использования static_cast (как и других C++-специфических преобразований типов) является лёгкость его поиска в исходниках, как глазами, так и поисковыми утилитами. Сишный каст (особенно его функциональную разновидность) очень легко пропустить в коде.
Для сравнения, «привычное» преобразование типов (C-style cast) равносильно следующей последовательности:
- const_cast .
- Если const_cast не может дать нужный результат, то static_cast (но с разрешённым преобразованием к недообъявленному типу)
- Если и так не выходит, то компилятор пробует в хвост к static_cast добавить const_cast .
- Если и это не получается, то reinterpret_cast .
- . а если не выйдет, то к нему дописывается const_cast .
Deep C++. Operation: static_cast
Как подсказывает название, static_cast преобразует выражения одного статического типа в объекты и значения другого статического типа. В соответствии со стандартом C++ (пункт 5.2.9/1-3):
«Результатом выражения static_cast(v) является результат преобразования выражения v к типу T . Если T является ссылкой, то результат lvalue; в противном случае — результат rvalue. В static_cast не должны определяться типы. Оператор static_cast не может снимать константность.
Выражение e может быть явно преобразовано к типу T используя static_cast следующим образом: static_cast(e) , если объявление “ T t(e); ” верно для некоторой воображаемой временной переменной t .
В противном случае static_cast должен выполнить одно из преобразований, указанных ниже. Никаких других преобразований не должно производиться явно при использовании static_cast.»
Это означает, что static_cast допускается, если:
- объект типа, полученного после преобразования, может быть инициализирован значением исходного типа, или
- приведение типа соответствует одному из исключений указанных далее в пункте 5.2.9 (которые я вскоре перечислю).
В этом пункте также упоминается, что static_cast «не снимает константность». Стандарт буквально имеет в виду, что оператор static_cast не может убрать спецификатор const . Например, static_cast не может преобразовать char const * в char * , хотя он может преобразовать char * в char const * .
cv- квалификацией принято называть добавление к типу спецификаторов const и/или volatile .
Если вы хотите убрать cv -квалификацию , то единственным подходящим оператором приведения типа будет const_cast . Я оставлю подробное обсуждение этого оператора до более основательного рассмотрения правил использования спецификатора const .
Примеры
Если даны объявления
struct B < operator int(); >; struct D : B < >; B b; float const f = 0;
то следующие два преобразования
static_castvoid *>(&b); // эквивалентно '(void *) &b;' static_castint>(b); // эквивалентно '(int) b;' and 'int(b);'
являются допустимыми. Первое преобразование основано на стандартном неявном преобразовании типа B* к типу void* , тогда как второе неявно вызывает b.operator int() . Оба преобразования следуют общему правилу пункта 5.2.9/1 стандарта.
Как уже упоминалось выше, этот пункт определяет ряд исключений из общего правила. Эти исключения допускают преобразования
static_castint>(f); // эквивалентно '(int) f;' and 'int(f);' static_cast(b); // эквивалентно '(D &) b;'
(Первый из двух примеров здесь не уместен, т.к., согласно стандарту, объект типа int может быть инициализирован значением типа float — прим. RSDN.)
даже если объекты получаемого типа не могут быть инициализированы значениями исходного типа.
В заключение, два выражения
static_castint const *>(&f); // OK static_castint *>(&f); // ошибка
(С точки зрения стандарта, эти примеры недопустимы: static_cast не может приводить указатели несвязанных типов, каковыми являются int и float — прим. RSDN.)
Первое из них выполнится успешно, тогда как второе – нет. В обоих выражениях осуществляется попытка преобразовать указатель на переменную типа float в указатель на переменную типа int. Однако второе выражение также убирает спецификатор const, что не допускается стандартом. Второе преобразование можно осуществить более обычным способом
Неявное приведение типа
Пункт 5.2.9/1 допускает преобразование
T t = static_cast(e);
T t(e);
также является допустимой – т.е. если прямая инициализация возможна без приведения типа. Следовательно, объявления
long l = static_castlong>('x');
long l('x');
являются эквивалентными. Вам может показаться странным, что язык допускает такое избыточное приведение типа, т.к. оно ухудшает код без добавления какой-либо новой возможности.
Я же считаю полезным указывать все преобразования типов явным образом, даже когда это не необходимо. Преобразования типов опасны, вне зависимости от того, допускаются ли они неявно правилами языка, либо явно — операторами преобразования типа и конструкторами. Если вы будете прилежно выделять таким образом все преобразования типов, то сможете легче выявлять потенциальные источники проблем в дальнейшем.
Кто-то предлагал комитету стандартизации C++ добавить пятый оператор приведения типов ( implicit_cast ) для выделения преобразований, которые язык допускает и без этого. Комитет отклонил это предложение, возможно потому, что шаблон
templatetypename FROM, typename TO> inline TO implicit_cast(FROM const &x) < return x; >
является несложным в написании эквивалентом предложенного оператора . Используя этот шаблон, можно переписать приведенный ранее пример следующим образом:
long l = implicit_castlong>('x');
Если вам не нравится писать static_cast там, где не требуется явного приведения типа, подумайте над добавлением implicit_cast в вашу библиотеку и используйте его там, где возможно.
Явное приведение типа
Если бы оператор static_cast был разрешен только в ситуациях, где достаточно implicit_cast , он потерял бы всякий смысл. Однако, как я показал в предыдущих примерах, static_cast также позволяет производить явные преобразования типа там, где языком не разрешены неявные. Этими допустимыми явными преобразованиями являются:
- Из любого типа к типу void
- Из базового типа к ссылке на производный тип
- Инверсия некоторых стандартных преобразований
К типу void
В соответствии со стандартом, любой тип выражения может быть преобразован к типу void с cv- квалификацией.
Это правило допускает все следующие выражения:
static_castvoid>(5); static_castconst void>("abc"); static_castvolatile const void>('x');
Вы можете удивиться: зачем вам может понадобиться приводить какой-либо тип к void , особенно учитывая, что вы не можете объявлять объекты типа void . На ум приходят две возможные причины.
Для начала рассмотрим шаблон
templatetypename R1, typename R2> R1 adapt(R2 (*f)()) < // . return static_cast(f()); >
adapt принимает в качестве параметра функцию f , которая не имеет параметров и возвращает тип R2 . При возврате управления функция adapt вызывает f() , преобразуя возвращаемый f тип R2 к типу, который возвращает сама функция adapt ( R1 ).
Для любознательных: я называю этот шаблон adapt , потому что его паттерн имеет сходство с адаптерами функций-объектов в STL.
Если вы инстанциируете шаблон функцией
int g(); adaptvoid>(g);
в результате будет создана специализация
void adapt(int (*f)()) < // . return static_castvoid>(f()); >
Поскольку static_cast может преобразовать к void , вы можете использовать один шаблон как для типа void , так и для других, отличных от void, типов.
Другой возможной мотивацией для static_cast служит явное игнорирование побочных эффектов выражения. Если вы вызываете функцию
int f();
без использования возвращаемого значения функции, то программисты, сопровождающие код в дальнейшем, будут гадать: собирались ли вы использовать возвращаемое значение, но случайно этого не сделали, или нет. Если же вы явно отбросите возвращаемое значение таким образом
static_castvoid>(f());
то другие программисты смогут быть более уверенными в ваших намерениях.
Базовый тип к ссылке производного типа
Если даны следующие типы
struct Base < >; struct Derived : Base < >;
Derived derived; Base &base = derived;
то стандарт позволяет явное преобразование
static_cast(base);
Стандарт утверждает, что результат этого приведения — lvalue типа Derived , но я думаю, что результат является – или должен быть – Derived & .
Хотя это приведение является допустимым, оно может привести к неприятностям. Рассмотрим похожий пример
Base base1; static_cast(base1);
Здесь, base1 в действительности является объектом типа Base , а не Derived . Это преобразование «обманывает» компилятор и приводит к неопределенному поведению программы.
Стандарт перечисляет другие ограничения времени компиляции на это преобразование:
- Должно существовать неявное стандартное преобразование из Derived * в Base *
- Base является невиртуальным базовым классом Derived
- Derived имеет по крайней мере такую же cv- квалификацию , как и Base (т.е. вы можете добавить, но не убрать cv- квалификацию ).
Инверсия стандартных преобразований
Статья 4 стандарта перечисляет множество неявных стандартных преобразований:
- Преобразование lvalues к rvalues
- Преобразование массивов и функций к указателям
- Добавление const и/или volatile
- Преобразование и повышение разрядности интегральных типов и типов с плавающей точкой
- Преобразования между указателями и указателями на члены класса
- Преобразование к типу bool
Большинство этих преобразований адаптированы из C,и должны быть хорошо вам знакомы. Поскольку преобразования неявные, то они могут осуществляться без какого-либо явного указания о приведении типа. Например
char a[10]; char *p = a; // преобразование массива в указатель char const *s = p; // добавление спецификатора const void *v = p; // преобразование указателей float f = 123; // преобразование между интегральным числом и числом с плавающей точкой unsigned u = 123; // продвижение интегрального типа
static_cast позволяет вам обратить эти преобразования, заставляя некоторые из них осуществляться противоположным образом. В частности, static_cast может обращать стандартные преобразования интегрального типа и типа с плавающей точкой, указателей и указателей на члены. В примере
char *p; void *v; v = p; // OK, неявное преобразование p = v; // error, нет неявного преобразования p = static_castchar *>(v); // OK, явное преобразование
использование static_cast «подавляет» стандартные правила преобразований, вызывая преобразование, иначе невозможное.
static_cast также позволяет проводить обратные преобразования из:
- Интегральных типов в перечисляемые
- (возможно cv- квалифицированный ) Base * в (с не меньшей cv- квалификацией ) Derived *
- (возможно cv- квалифицированный ) T Base:: * в (с не меньшей cv- квалификацией ) T Derived:: *
- (возможно cv- квалифицированный ) void * в любой T *
Эти преобразования приводят к неопределенному поведению, если:
- Интегральное значение выходит из диапазона перечисления
- Base * не указывает на объект класса Derived
- T Base:: * не указывает на член класса Derived
- void * не указывает на объект класса T
Заметьте, что static_cast не обращает все неявные преобразования. В частности, static_cast не может преобразовать из:
- Rvalues в lvalues
- Указателей в массивы
- Указателей в функции
- Более ограниченного спецификатором const в менее ограниченный
Вы можете принудительно выполнить некоторые из этих преобразований с помощью других способов приведения типа. Остальные обращенные преобразования — такие как преобразование указателя обратно в массив — не могут быть выполнены никаким видом приведения типа.
Альтернатива нисходящему приведению типа
Многие из преобразований, допускаемых оператором static_cast , позволяют преобразовывать базовые классы в производные классы. Подобные преобразования называют нисходящими (downcast), так как они требуют спуска по иерархии наследования. Сравните их с противоположными им восходящими преобразованиями (upcast, от производных к базовым), которые язык позволяет делать неявно.
Нисходящее приведение типа небезопасно, т.к. оно подразумевает, что сущность статического типа, соответствующего базовому классу, в действительности имеет динамический тип производного класса. Если вы осуществляете нисходящее приведение типа с использованием static_cast , компилятор будет считать, что вы правы, даже если это не так. Если же в действительности это не так, результатом будет неопределенное — и подчас весьма неприятное — поведение программы.
Хотя я и не рассматриваю dynamic_cast в этой статье, я хотел бы упомянуть его в качестве альтернативы static_cast для нисходящего приведения типа. Принципиальным преимуществом dynamic_cast является то, что ваша программа проверяет ваше предположение во время исполнения. Если (предположительно) производная сущность в действительности не является объектом производного типа, то dynamic_cast либо вернет NULL (при преобразовании указателей), либо выбросит исключение (при преобразовании ссылок).
Я собираюсь подробно рассмотреть оператор dynamic_cast и ассоциированную с ним идентификацию типов во время выполнения (RTTI — Run-Time Type Identification) в будущих статьях.
Далее
В своей следующей статье я подобным образом рассмотрю reinterpret_cast и дам советы, когда использовать его либо static_cast .
Роберт Шмидт (Robert Schmidt) технический автор MSDN. Его другая основная писательская страсть — C/C++ Users Journal, в котором он является выпускающим редактором и ведет свою колонку. Его предыдущие этапы карьеры – радио ди-джей, дрессировщик диких животных, астроном, pool-hall operator, частный сыщик, разносчик газет и преподаватель колледжа.
Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
Синтаксис: приведение типов данных в C и C++
Cи-стиль приведения типов данных доступен и языке C++, но считается не самодостаточным по сравнению с приведением типов в C++. Так как Си-стиль приведения типов не так точен, как C++-стиль приведения и не так заметен. Cи-стиль приведения типов данных может быть использован для преобразования любого типа в любой другой тип, при этом неважно насколько это небезопасное преобразование, например, преобразование целого числа в указатель типа int ). Казалось бы, такое преобразование невозможно, однако компилятор с нами не согласен, но выполнит это приведение. И каков получится результат. ему совершенно не важно. Смотрим синтаксис приведения типов данных согласно Си-стилю:
(type) val
- type — тип данных к которому преобразуется значение val
причем тип данных обязательно указывается в круглых скобочках.
Рассмотрим следующий пример использования Си-стиля приведения типа данных int к типу double .
double res = (double)13 / 7;
Этот пример приводит int к типу double для того, чтобы при делении 13-ти на 7 избежать усечения результата из-за целочисленного деления. Если вы плохо знакомы с приведением типов данных, прочтите статью: Явное и неявное приведение типов данных.
static_cast — унарная операция приведения типов данных в С++
Операция static_cast доступна только в языке C++. static_cast может быть использована для преобразования одного типа в другой, но она не должна быть использована для выполнения недопустимого преобразования, например, преобразование значения в указатель или наоборот. Рекомендуется пользоваться операцией static_cast , нежели Cи-стилем приведения, потому что static_cast ограничивает недопустимое приведение типов и, следовательно — безопаснее.
Операция static_cast , грубо говоря, — это шаблон функции, в которой необходимо явно указать тип данных для преобразования, то есть задать параметр шаблона.
static_cast(value);
Итак, в треугольных скобочках указывается тип данных, к которому необходимо преобразовать значение value , которое стоит в круглых скобочках. Смотрим все тот же пример, который приводит int к типу double для того, чтобы при делении 13-ти на 7 избежать усечения результата из-за целочисленного деления.
double res = static_cast(13)/7;
dynamic_cast — унарная операция приведения типов данных в С++
Операция dynamic_cast доступна только в C++ и имеет смысл только, применительно к членам класса иерархии «полиморфных типов». Динамическое приведение типов данных может быть использовано для безопасного приведения указателя (или ссылки) на суперкласс, в указатель (или ссылку) на подкласс в иерархии классов. Если динамическое приведение типов — недопустимо, так как реальный тип объекта, указывает не на тот тип подкласса, приведение типов не выполнится.
Динамическое приведение указателя
При приведении указателя, в случае неудачи, dynamic_cast возвращает нулевой указатель NULL . Такое поведение обеспечивает быстрый способ определения, является ли данный объект частностью динамического типа.
Синтаксис указателя динамического приведения:
type *subСlass = dynamic_cast( objPtr );
Динамическое приведение ссылки
При приведении ссылочной переменной, не возможно вернуть указатель, в случае неудачи. Поэтому будет вызвано исключение std::bad_cast (из заголовочного файла ).
type subСlass = dynamic_cast( objReference );
Чтобы безопасно пользоваться динамическим приведением, все вызовы dynamic_cast должны быть обрамлены в блок Try/Catch .
const_cast — константное приведение типов данных
Операция const_cast доступна только в C++. Константное приведение используются, чтобы константную переменную преобразовать в неконстантную. При этом, константным становится возвращаемое значение операции const_cast , а не сама переменная.
const_cast(val);
Следующий пример преобразует константный указатель на символьную строку в неконстантный указатель на эту же строку.
void function(char *); // прототип функции с неконстантным параметром const char *string = "Sevastopol"; // константная строка function(const_cast(string));
reinterpret_cast — операция приведения типов данных
Операция reinterpret_cast доступна только в C++ и является наименее безопасной формой приведения типов данных в С++, она позволяет интерпретировать значение в другой тип данных. reinterpret_cast не должна быть использована для приведения иерархии классов или преобразования константных переменных.
reinterpret_cast( value );
Рассмотрим пример использования этой операции приведения, например, чтобы преобразовать целое значение в указатель, нужно написать следующее:
reinterpret_cast(777);
Более подробно о приведении типов данных читайте в статье: Явное и неявное преобразование типов данных.