Приведение типов. RTTI — различия между версиями

Материал из SEWiki
Перейти к: навигация, поиск
(Приведение в стиле С++)
(=Общее замечание по разделу Приведение типов)
Строка 76: Строка 76:
 
</source>
 
</source>
  
===Общее замечание по разделу Приведение типов==
+
===Общее замечание по разделу Приведение типов===
 
При программировании на С++ рекомендуется использовать C-style cast только в случаях приведения элементарных типов друг к другу.
 
При программировании на С++ рекомендуется использовать C-style cast только в случаях приведения элементарных типов друг к другу.
  

Версия 03:34, 11 июня 2012

Приведение типов

Приведение типов делится на 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 бывает полезно.