Static cast int c что это
Перейти к содержимому

Static cast int c что это

  • автор:

sunmeat / Main.cpp

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

# include
# include
using namespace std ;
class Person
public:
virtual void DoWork ()
cout
>
>;
class Employee : public Person
string profession;
public:
virtual void DoWork () override
cout
>
void SetProfession (string p)
profession = p;
>
string GetProfession () const
return profession;
>
>;
int main ()
int x = 10 ;
cout
/*
ВНЕЗАПНО, приведение в стиле языка C — самый нежелательный способ приведения типов. вот что Страуструп пишет:
«. что значит это выражение: x = (T)y; ? Мы не знаем. Это зависит от типа T, типов x и y.
T может быть названием типа, typedef, или может быть параметр template-а. Может быть, х и у являются
скалярными переменными и Т представляет собой значение преобразования. А может быть, х — объект класса,
производного от класса Y, и Т — нисходящее преобразование. По этой причине программист может не знать,
что он делает на самом деле.»
Вторая причина нежелательного использования приведения типов в C-style — трудоёмкость процесса
поиска мест приведения типов.
*/
cout (x) / 3
// static_cast преобразует выражения одного статического типа в объекты и значения другого
// статического типа. Поддерживается преобразование численных типов, указателей и ссылок
// по иерархии наследования как вверх, так и вниз. Проверка производится на уровне компиляции,
// так что в случае ошибки сообщение будет получено в момент сборки приложения или библиотеки.
// /////////////////////////////////////////////////////////////////////////////////////
double d = 1.58947 ;
int i = d; // warning C4244 : possible loss of data
int j = static_cast < int >(d); // no warning
// string s1 = static_cast(d); // error C2440 : cannot convert from double to std:string
// string s2 = (string)d; // same error
Person* p = new Person; // поменять потом на «new Employee;»
// Employee *e = p; // невозможно преобразовать «Person *» в «Employee *»!
Employee *e = static_cast (p); // no error, but may be unsafe!
e-> DoWork ();
e-> SetProfession ( » blacksmith » );
cout GetProfession ()
>

Для чего нужен static_cast, как он работает и где его применяют?

У static_cast очень много различных применений. Его идея состоит в следующем: это ограниченный по мощи C-style cast. Ограничение нужно потому, что C-style cast может привести что угодно к чему угодно (ну, почти), и тем самым может скрыть ошибку. Например, вы можете случайно закастить const char* в char* , получив крэш в некоторых системах с аппаратной поддержкой const-памяти. static_cast вам такого не позволит.

Большую часть времени, когда вы хотите сделать явное преобразование типов (а я надеюсь, это бывает достаточно редко), вы хотите именно static_cast .

Формальный список всего, что умеет static_cast , очень большой, я приведу лишь наиболее важные вещи, которые он умеет (а также которые он не умеет):

  1. Преобразование указателя на родительский класс к указателю на дочерний класс. Объект по указателю обязан быть правильного дочернего класса, иначе undefined behaviour. Если вы не уверены и хотите проверить, тот ли подкласс у объекта, пользуйтесь dynamic_cast (он специально для этого предназначен).
  2. Преобразования между числовыми типами. int , long , char , unsigned int — все их можно кастить друг в друга при помощи static_cast .
  3. Можно закастить любое выражение в void . Результат будет вычислен и отброшен (но побочные эффекты, разумеется, выполнятся).
  4. static_cast может привести константу nullptr к любому типу-указателю. Обычно это не нужно и можно полагаться на неявное преобразование типов, но иногда (например, для выбора нужной перегрузки функции) такое может пригодится.
  1. Преобразование между указателями на в принципе несовместимые типы. Например, указатель на double нельзя привести к указателю на int . Для трюков с нарушением type safety пользуйтесь reinterpret_cast .
  2. Указатели на типы, а также сами типы с несовместимыми атрибутами const и/или volatile . Если вам необходимо нарушить const-корректность, пользуйтесь const_cast .
  3. Разумеется, вы не сможете привести указатель на функцию-член к указателю на обычную функцию, или указатель на код к указателю на данные. Для подобных грязных хаков пользуйтесь reinterpret_cast .

Ещё одной причиной использования static_cast (как и других C++-специфических преобразований типов) является лёгкость его поиска в исходниках, как глазами, так и поисковыми утилитами. Сишный каст (особенно его функциональную разновидность) очень легко пропустить в коде.

Для сравнения, «привычное» преобразование типов (C-style cast) равносильно следующей последовательности:

  1. const_cast .
  2. Если const_cast не может дать нужный результат, то static_cast (но с разрешённым преобразованием к недообъявленному типу)
  3. Если и так не выходит, то компилятор пробует в хвост к static_cast добавить const_cast .
  4. Если и это не получается, то reinterpret_cast .
  5. . а если не выйдет, то к нему дописывается const_cast .

static_cast conversion

Converts between types using a combination of implicit and user-defined conversions.

Contents

[edit] Syntax

static_cast target-type  >( expression  )

Returns a value of type target-type .

[edit] Explanation

Only the following conversions can be done with static_cast , except when such conversions would cast away constness or volatility.

1) If target-type is a reference to some class D and expression is an lvalue of its non-virtual base B , or target-type is a pointer to some complete class D and expression is a prvalue pointer to its non-virtual base B , static_cast performs a downcast. (This downcast is ill-formed if B is ambiguous, inaccessible, or virtual base (or a base of a virtual base) of D .)

Such a downcast makes no runtime checks to ensure that the object’s runtime type is actually D , and may only be used safely if this precondition is guaranteed by other means, such as when implementing static polymorphism. Safe downcast may be done with dynamic_cast . If the object expression refers or points to is actually a base class subobject of an object of type D , the result refers to the enclosing object of type D . Otherwise, the behavior is undefined:

struct B {}; struct D : B { B b; }; D d; B& br1 = d; B& br2 = d.b; static_castD&>(br1); // OK: lvalue denoting the original d object static_castD&>(br2); // UB: the b subobject is not a base class subobject

2) If target-type is an rvalue reference type, static_cast converts the value of glvalue, class prvalue, or array prvalue (until C++17) any lvalue (since C++17) expression to xvalue referring to the same object as the expression, or to its base sub-object (depending on target-type ). If the target type is an inaccessible or ambiguous base of the type of the expression, the program is ill-formed. If the expression is a bit-field lvalue, it is first converted to prvalue of the underlying type. This type of static_cast is used to implement move semantics in std::move .

3) If there is an implicit conversion sequence from expression to target-type , or if overload resolution for a direct initialization of an object or reference of type target-type from expression would find at least one viable function, then static_cast < target-type  >( expression  ) returns the imaginary variable Temp initialized as if by target-type Temp ( expression  ); , which may involve implicit conversions, a call to the constructor of target-type or a call to a user-defined conversion operator. For non-reference target-type , the result object of the static_cast prvalue expression is what’s direct-initialized. (since C++17)

4) If target-type is the type void (possibly cv-qualified), static_cast discards the value of expression after evaluating it.

5) If a standard conversion sequence from target-type to the type of expression exists, that does not include lvalue-to-rvalue, array-to-pointer, function-to-pointer, null pointer, null member pointer, function pointer, (since C++17) or boolean conversion, then static_cast can perform the inverse of that implicit conversion.

6) If conversion of expression to target-type involves lvalue-to-rvalue, array-to-pointer, or function-to-pointer conversion, it can be performed explicitly by static_cast .

When the target type is bool (possibly cv-qualified), the result is false if the original value is zero and true for all other values. For the remaining integral types, the result is the value of the enum if it can be represented by the target type and unspecified otherwise.

The result is the same as implicit conversion from the enum’s underlying type to the destination type.

  • If the underlying type is not fixed, the behavior is undefined if the value of expression is out of range (the range is all values possible for the smallest bit-field large enough to hold all enumerators of the target enumeration).
  • If the underlying type is fixed, the result is the same as converting the original value first to the underlying type of the enumeration and then to the enumeration type.
  • The result is the same as converting the original value first to the underlying type of the enumeration, and then to the enumeration type.
  • If the source value can be represented exactly in the destination type, it does not change.
  • If the source value is between two representable values of the destination type, the result is one of those two values (it is implementation-defined which one, although if IEEE arithmetic is supported, rounding defaults to nearest).
  • Otherwise, the behavior is undefined.

10) A pointer to member of some complete class D can be upcast to a pointer to member of its unambiguous, accessible base class B . This static_cast makes no checks to ensure the member actually exists in the runtime type of the pointed-to object: if B does not contain the original member and is not a base class of the class containing the original member, the behavior is undefined.

11) A prvalue of type pointer to void (possibly cv-qualified) can be converted to pointer to any object type. If the original pointer value represents an address of a byte in memory that does not satisfy the alignment requirement of the target type, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a , and there is an object b of the target type (ignoring cv-qualification) that is pointer-interconvertible (as defined below) with a , the result is a pointer to b . Otherwise the pointer value is unchanged. Conversion of any pointer to pointer to void and back to pointer to the original (or more cv-qualified) type preserves its original value.

As with all cast expressions, the result is:

  • an lvalue if target-type is an lvalue reference type or an rvalue reference to function type (since C++11) ;
  • an xvalue if target-type is an rvalue reference to object type;
  • a prvalue otherwise.

Two objects a and b are pointer-interconvertible if:

  • they are the same object, or
  • one is a union object and the other is a non-static data member of that object, or
  • one is a standard-layout class object and the other is the first non-static data member of that object or any base class subobject of that object, or
  • there exists an object c such that a and c are pointer-interconvertible, and c and b are pointer-interconvertible.
union U { int a; double b; } u; void* x = &u; // x's value is "pointer to u" double* y = static_castdouble*>(x); // y's value is "pointer to u.b" char* z = static_castchar*>(x); // z's value is "pointer to u"

[edit] Notes

static_cast may also be used to disambiguate function overloads by performing a function-to-pointer conversion to specific type, as in

std::for_each(files.begin(), files.end(), static_caststd::ostream&(*)(std::ostream&)>(std::flush));

[edit] Keywords

[edit] Example

Run this code

#include #include struct B { int m = 42; const char* hello() const { return "Hello world, this is B!\n"; } }; struct D : B { const char* hello() const { return "Hello world, this is D!\n"; } }; enum class E { ONE = 1, TWO, THREE }; enum EU { ONE = 1, TWO, THREE }; int main() { // 1. static downcast D d; B& br = d; // upcast via implicit conversion std::cout  "1) "  br.hello(); D& another_d = static_castD&>(br); // downcast std::cout  "1) "  another_d.hello(); // 2. lvalue to xvalue std::vectorint> v0{1,2,3}; std::vectorint> v2 = static_caststd::vectorint>&&>(v0); std::cout  "2) after move, v0.size() = "  v0.size()  '\n'; // 3. initializing conversion int n = static_castint>(3.14); std::cout  "3) n = "  n  '\n'; std::vectorint> v = static_caststd::vectorint>>(10); std::cout  "3) v.size() = "  v.size()  '\n'; // 4. discarded-value expression static_castvoid>(v2.size()); // 5. inverse of implicit conversion void* nv = &n; int* ni = static_castint*>(nv); std::cout  "5) *ni = "  *ni  '\n'; // 6. array-to-pointer followed by upcast D a[10]; [[maybe_unused]] B* dp = static_castB*>(a); // 7. scoped enum to int E e = E::TWO; int two = static_castint>(e); std::cout  "7) "  two  '\n'; // 8. int to enum, enum to another enum E e2 = static_castE>(two); [[maybe_unused]] EU eu = static_castEU>(e2); // 9. pointer to member upcast int D::*pm = &D::m; std::cout  "9) "  br.*static_castint B::*>(pm)  '\n'; // 10. void* to any type void* voidp = &e; [[maybe_unused]] std::vectorint>* p = static_caststd::vectorint>*>(voidp); }
1) Hello world, this is B! 1) Hello world, this is D! 2) after move, v0.size() = 0 3) n = 3 3) v.size() = 10 5) *ni = 3 7) 2 9) 42

[edit] Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
CWG 137 C++98 the constness and volatility of void
pointers could be casted away
cv-qualifications cannot be
casted away in such cases
CWG 439 C++98 when converting a ‘pointer to object’ to ‘pointer to
void’ then back to itself, it could only preserve its
value if the result type has the same cv-qualification
cv-qualification
may be different
CWG 1094 C++98 the conversion from floating-point values
to enumeration values was unspecified
specified
CWG 1320 C++11 the conversion from scoped enumeration
values to bool was unspecified
specified
CWG 1447 C++11 the conversion from bit-fields to rvalue references
was unspecified (cannot bind references to bit-fields)
specified
CWG 1766 C++98 the conversion from integral or enumeration values to enumeration
values yielded unspecified result if expression is out of range
the behavior is
undefined in this case
CWG 1832 C++98 the conversion from integral or enumeration values to
enumeration values allowed target-type to be incomplete
not allowed
CWG 2224 C++98 the conversion from a member of base class type to
its complete object of derived class type was valid
the behavior is
undefined in this case
CWG 2254 C++11 a standard-layout class object with no data members
was pointer-interconvertible to its first base class
it is pointer-interconvertible
to any of its base classes
CWG 2284 C++11 a non-standard-layout union object and a non-static data
member of that object were not pointer-interconvertible
they are
CWG 2310 C++98 for base-to-derived pointer conversions and
derived-to-base pointer-to-member conversions,
the derived class type could be incomplete
must be complete
CWG 2338 C++11 the conversion to enumeration types with fixed underlying type
resulted in undefined behavior if expression is out of range
convert to the underlying type
first (no undefined behavior)
CWG 2499 C++11 a standard-layout class might have a non-pointer-interconvertible
base class, even though all base subobjects have the same address
it does not have

[edit] See also

  • const_cast
  • dynamic_cast
  • reinterpret_cast
  • explicit cast
  • implicit conversions

Приведение типов. Наглядное отличие static_cast от dynamic_cast

Доброго времени суток. Очень много статей в интернете о разнице операторов приведения типов, но понимания в данной теме они мне не особо то и не добавили. Пришлось разбираться самому. Хочу поделиться с вами моим опытом на довольно наглядном примере.

Статья рассчитана на тех, кто хочет осознать приведение типов в С++.

Итак, пусть у нас есть такая иерархия наследования:

#include struct A < A():a(0), b(0)<>int a; int b; >; struct B : A < B():g(0)<>int g; >; struct D < D():f(0)<>float f; >; struct C : A, D < C():d(0)<>double d; >; 

На картинке изображена иерархия наследования и расположение членов-данных наследников в памяти

image

Небольшое отступление: почему так важно преобразование типов? Говоря по рабоче-крестьянски, при присваивании объекту типа X объект типа Y, мы должны определить, какое значение будет иметь после присваивания объект типа X.

Начнем с использования static_cast:

int main() < C* pC = new C; A* pA = pC; D* pD = static_cast(pC); std::cout

Почему таков эффект при выводе значений указателей (значение указателя это адрес, по которому лежит переменная)? Дело в том, что static_cast производит сдвиг указателя.
Рассмотрим на примере:

D* pD = static_cast (pC); 

1. Происходит преобразование типа из C* в D*. Результатом этого есть указатель типа D* (назовем его tempD), который указывает (внимание!) на ту часть в объекте класса C, которая унаследована от класса D. Значение самого pC не меняется!

2. Теперь присваиваем указателю pD значение указателя tempD (всё хорошо, типы одинаковы)
Разумный вопрос: а зачем собственно нужно сдвигать указатель? Говоря по простому, указатель класса D* руководствуется определением класса D. Если бы не произошло смещения, то меняя значения переменных через указатель D, мы бы меняли переменные объекта класса С, которые не относятся к переменным, унаследованным от класса D (если бы указатель pD имел то же значение, что и pC, то при обращении pD->f в действительности мы бы работали с переменной
а).

Промежуточный итог: static_cast при работе с иерархией классов определяет значения указателей так, чтобы обращение к переменным класса через указатель было корректным.

Поговорим о недостатках static_cast. Вернемся к той же иерархии наследования.

Рассмотрим такой код:

int main()< C* pC = new C; A* pA = static_cast(pC); D* pD = static_cast (pC); B* pB = static_cast (pA); std::cout g) g = 100; std::cout a << " " b << " " f

Почему pC->f имеет значение отличное от 0? Рассмотрим код по строчкам:

  1. В куче выделяется память под указатель типа С.
  2. Происходит повышающее преобразование. Указатель pA имеет такое же значение как и pC.
  3. Происходит повышающее преобразование. Указатель pD имеет значение, которое есть АДРЕС переменной f, в объекте класса C, на который указывает указатель pC.
  4. Происходит понижающее преобразование. Указатель pB имеет то же значение, что и указатель pA.

image

Теперь если мы хотим сделать запись в переменную g через указатель pB (ведь pB полностью уверен что указывает на объект типа B), мы на самом деле запишем данные в переменную f, унаследованную от класса D. Причем указатель pD будет интерпретировать информацию, записанную в переменную f, как float, что мы и видим при выводе через cout.

Как решить такую проблему?
Для этого следует использовать dynamic_cast, который проверяет не только валидность иерархии классов, но и тот факт, что указатель действительно указывает на объект того типа, к которому мы хотим привести.

Для того, чтобы такая проверка была возможна, следует добавить к классам виртуальность (dynamic_cast использует таблицы виртуальных функций, чтобы делать проверку).

Демонстрация решения проблемы, при той же иерархии классов:

int main() < C* pC = new C; A* pA = pC; if(D* pD = dynamic_cast(pC)) std::cout (pA)) std::cout

Предлагаю запустить код и убедиться, что операция

B* pB = dynamic_cast (pA)

не получится (потому что pA указывает на объект типа С, что и проверил dynamic_cast и вынес свой вердикт).

Ссылок никаких не привожу, источник — личный опыт.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *