Приведение типов. RTTI
Содержание
Приведение типов
Приведение типов делится на C style cast и C++ style cast
Приведение в стиле C
Синтаксис приведения:
- (тип_к_которому_приводим) переменная
- тип_к_которому_приводим (переменная)
Примеры:
int i = 10;
double d = (double) i;
float f = float(i);
char const* ch = "hello";
string str = (string) ch;
Приведение в стиле С++
В С++ для приведения типов используются следующие функции:
- const_cast
- reinterpret_cast
- static_cast
- dynamic_cast
Во всех случаях синтаксис приведения будет выглядеть следующим образом: ..._cast <тип_к_которому_приводим> (переменная)
Рассмотрим первые три более подробно (dynamic_cast рассматривается в разделе RTTI).
const_cast
Добавляет/убирает у объекта константность. Если убрать константность у объекта, который был объявлен как константный, то при записи может возникнуть undefined behavior (в зависимости от типа объекта).
Рассмотрим ситуацию, когда нам может пригодиться const_cast. Предположим у нас класс A, в котором хранятся элементы типа Т. Пусть требуется реализовать метод get (константный и не константный), который по индексу i будет возвращать i-ый элемент. Можно описать реализацию только одного метода (константного), а второй реализовать через вызов первого при помощи const_cast:
T const& get (int i) const;
T& get (int i) {
return const_cast<T&> (const_cast<A const*> (this).get(i) );
}
reinterpret_cast
Приводит любой указатель к любому указателю.
Пример:
struct Point3 {
double x;
double y;
double z;
};
...
Point3* p = ...;
double* p_x = reinterpret_cast<double*> (p);
Замечание: приведенный пример отработает корректно в случае если структура Point3 плотно упакована в памяти (см. опции компилятора).
static_cast
Используется для приведения
- числовых типов (аналогично C-style cast)
- указателей и ссылок для классов связанных наследованием
- пользовательских преобразований
Преобразование выполняется в момент компиляции и run-time проверки (как это есть в dynamic_cast) приведения типов нет, поэтому сложные приведения типов следует делать очень аккуратно.
Пример:
struct A {};
struct B : A {};
...
B* b;
A* a = static_cast<A*> (b);
b = static_cast<B*> (a);
Замечание: static_cast меняет указатель в зависимости от того какое у классов наследование (в отличии от reinterpret_cast), поэтому если будет forward declaration, то static_cast выдаст ошибку (на этапе компиляции):
struct A;
struct B;
B* b;
A* a = static_cast<A*>(b); // ошибка!
Общее замечание по разделу Приведение типов
При программировании на С++ рекомендуется использовать C-style cast только в случаях приведения элементарных типов друг к другу.
RTTI
RTTI расшифровывается как Run-time type information. Это механизм, позволяющий определять тип объекта в момент выполнения программы. В С++ данный механизм реализуется при помощи следующих элементов:
- оператор dynamic_cast (используется для преобразования полиморфных типов)
- оператор typeid (для точного определения типа объекта)
- класс type_info (для хранения информации, возвращаемой оператором typeid)
Рассмотрим применение и синтаксис typeid и type_info на примере:
#include <typeinfo>
...
A* a;
type_info& ti = typeid(a);
std::cout << ti.name() << std::endl;
Более сложный пример:
#include <typeinfo>
struct A {};
struct B : A {};
...
A* a = new B();
typeid(a); // A*
typeid(*a); // B
У класса type_info помимо метода name (возвращающего строку - char*, характеризующую тип объекта) и операторов равенства/неравенства есть еще метод before, который позволяет упорядочивать экземпляры type_info и хранить их, например, в map.
dynamic_cast
Позволяет приводить указатель или объект одного типа к указателю или ссылке на другой класс (связанный с первым через наследование). В отличии от static_cast выполняется в run-time. При ошибке приведения к указателю на тип dynamic_cast возвращает нулевой указатель, при ошибке приведения к ссылке на тип выбрасывается исключение bad_cast.
Пример использования:
struct A {
virtual void f() {}
};
struct B : A {
virtual void f() {}
};
...
A* pa = new B;
A* pa2 = new A;
B* pb = dynamic_cast<B*>(pa); // pb указывает на B
B* pb2 = dynamic_cast<B*>(pa2); // pb2 указывает на А, а не на В!
pb->f();
pb2->f(); // ошибка на этапе исполнения!
Замечание: При приведении к указателю на void возвращается указатель на начало блока.
Общие замечания по разделу RTTI:
- почти всегда можно обойтись без RTTI и чаще всего использование этого механизма говорит об ошибке проектирования
- на этапе отладки применение RTTI бывает полезно.