Еще раз про приведение типов в языке С++ или расстановка всех точек над cast

от автора

Этот пост попытка кратко оформить все, что я читал или слышал из разных источников про операторы приведения типов в языке C++. Информация ориентирована в основном на тех, кто изучает C++ относительно недолго и, как мне кажется, должна помочь понять Специфику применения данных операторов. Старожилы и гуру С++ возможно помогут дополнить или скорректировать описанную мной картину. Всех интересующихся приглашаю под кат.

Приведение типов в стиле языка C

Приведение типов в стиле языка C может привести выражение любого типа к любому другому типу данных (исключение это приведение пользовательских типов по значению, если не определены правила их приведения, а также приведение вещественного типа к указателю или наоборот). К примеру, unsigned int может быть преобразован к указателю на double. Данный метод приведения типов может быть использован в языке C++. Однако, метод приведения типов в стиле языка C не делает проверки типов на совместимость, как это могут сделать static_cast и dynamic_cast на этапе компиляции и на этапе выполнения соответственно. При этом все, что умеют const_cast и reinterpret_cast данный метод приведения типов делать может.

Общий вид приведения:

(new_type)exp

, где new_type – новый тип, к которому приводим, а exp – выражение, которое приводится к новому типу.

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

Показать пример

#include <iostream> //Пустые классы только //для теста приведения struct AAA{ }; struct BBB{ }; //Наследники BBB struct BBB_X:BBB{ }; struct BBB_Y:BBB{ };  int main() { 	//Переменные простых типовы и указатели на переменные простых типов 	int    i = 5; 	double d = 111.222; 	char   c = 'a'; 	int*   pi = &i; 	double * pd = &d; 	const int* cpi = &i; 	void*  v = NULL; 	//Объекты классов 	AAA A; 	BBB B; 	BBB_X BX; 	BBB_Y BY; 	//Указатели на объекты классов 	AAA* pA = &A; 	BBB* pB = &B; 	BBB_X* pBX = &BX; 	BBB_Y* pBY = &BY; 	//Приводим явно double к int 	i = (int)d;     //и наоборот 	d = (double)i; 	//указатель на int к char 	c = (char)pi; 	//char к указателю на void 	v = (void*)c; 	//указатель на void к указателю на int 	pi = (int*)v; 	//Снимаем константность const int* 	pi = (int *) cpi; 	//Приводим указатель на объект AAA к указателю на объект BBB     //из разных иерархий 	pA = (AAA*) pB; 	//Приводим указатель на double к double 	d = (double)pd;//Ошибка!!! 	//А если наоборот? 	pd = (double*)d;//Ошибка!!! 	//Перемещение из одной иерархии наследования в другую 	pB = (BBB*)pBX; 	pBY = (BBB_Y*) pB; 	return 0; } 

const_cast

Оператор приведения const_cast удаляет квалификаторы const и volatile с исходного типа данных (простые типы, пользовательские типы, указатели, ссылки). Например, был const int, а после преобразования стал int. Квалификаторы const и volatile называют cv-квалификаторы (cv-qualifiers). Данные квалификаторы указываются перед именами типов. Как ни трудно догадаться квалификатор const задает константность, т.е. защищает переменную от изменения. Квалификатор volatile говорит о том, что значение переменной может меняться без явного выполнения присваивания. Это обеспечивает защиту от оптимизации компилятором операций с данной переменной.

Общий вид приведения:

const_cast<new_type>(exp)

Показать пример

#include <iostream>  void test_func(const int* in1, const int& in2) { 	int *p; 	//Сняли константность и записали 33 	p = const_cast<int*>(in1); 	*p = 33; 	//Сняли константность и записали 55 	const_cast<int&>(in2) = 55; }  int main() { 	int x=3,y=5; 	std::cout<<x<<" "<<y<<std::endl; 	test_func(&x,y); 	std::cout<<x<<" "<<y<<std::endl; 	system("pause"); 	return 0; }  

Квалификаторы const и volatile можно удалить только с помощью оператора приведения const_cast и привидения типов в стиле языка C. Другие операторы приведения типов не влияют на квалификаторы const и volatile (reinterpret_cast, static_cast, dynamic_cast).

reinterpret_cast

Оператор приведения reinterpret_cast используется для приведения несовместимых типов. Может приводить целое число к указателю, указатель к целому числу, указатель к указателю (это же касается и ссылок). Является функционально усеченным аналогом привидения типов в стиле языка С. Отличие состоит в том, что reinterpret_cast не может снимать квалификаторы const и volatile, а также не может делать небезопасное приведение типов не через указатели, а напрямую по значению. Например, переменную типа int к переменной типа double привести при помощи reinterpret_cast нельзя.

Общий вид приведения:

reinterpret_cast<new_type>(exp)

Показать пример

 #include <iostream> //Пустые классы только //для теста приведения struct AAA{ }; struct BBB{ }; //Наследники BBB struct BBB_X:BBB{ }; struct BBB_Y:BBB{ };  int main() { 	//Переменные простых типовы и указатели на переменные простых типов 	int    i = 5; 	double d = 111.222; 	char   c = 'a'; 	int*   pi = &i; 	double * pd = &d; 	const int* cpi = &i; 	void*  v = NULL; 	//Объекты классов 	AAA A; 	BBB B; 	BBB_X BX; 	BBB_Y BY; 	//Указатели на объекты классов 	AAA* pA = &A; 	BBB* pB = &B; 	BBB_X* pBX = &BX; 	BBB_Y* pBY = &BY; 	//Приводим явно double к int 	i = reinterpret_cast<int>(d);//Ошибка!!!     //и наоборот 	/d = reinterpret_cast<int>(i);//Ошибка!!! 	//указатель на int к char 	c = reinterpret_cast<char>(pi); 	//char к указателю на void 	v = reinterpret_cast<void*>(c); 	//указатель на void к указателю на int 	pi = reinterpret_cast<int*>(v); 	//Снимаем константность const int* 	pi = reinterpret_cast<int *>(cpi);//Ошибка!!! 	//Приводим указатель на объект AAA к указателю на объект BBB     //из разных иерархий 	pA = reinterpret_cast<AAA*>(pB); 	//Приводим указатель на double к double 	d = reinterpret_cast<double>(pd);//Ошибка!!! 	//А если наоборот? 	pd = reinterpret_cast<double*>(d0;//Ошибка!!! 	//Перемещение из одной иерархии наследования в другую 	pB = reinterpret_cast<BBB*>(pBX); 	pBY = reinterpret_cast<BBB_Y*>(pB); 	return 0; } 

static_cast

Оператор приведения static_cast применяется для неполиморфного приведения типов на этапе компиляции программы. Отличие static_cast от приведения типов в стиле языка C состоит в том, что данный оператор приведения может отслеживать недопустимые преобразования, такие как приведение указателя к значению или наоборот (unsigned int к указателю на double не приведет), а также приведение указателей и ссылок разных типов считается корректным только, если это приведение вверх или вниз по одной иерархии наследования классов, либо это указатель на void. В случае фиксации отклонения от данных ограничений будет выдана ошибка при компиляции программы.

Общий вид приведения:

static _cast<new_type>(exp)

Показать пример

#include <iostream> //Пустые классы только //для теста приведения struct AAA{ }; struct BBB{ }; //Наследники BBB struct BBB_X:BBB{ }; struct BBB_Y:BBB{ };  int main() { 	//Переменные простых типовы и указатели на переменные простых типов 	int    i = 5; 	double d = 111.222; 	char   c = 'a'; 	int*   pi = &i; 	double * pd = &d; 	const int* cpi = &i; 	void*  v = NULL; 	//Объекты классов 	AAA A; 	BBB B; 	BBB_X BX; 	BBB_Y BY; 	//Указатели на объекты классов 	AAA* pA = &A; 	BBB* pB = &B; 	BBB_X* pBX = &BX; 	BBB_Y* pBY = &BY; 	//Приводим явно double к int 	i = static_cast<int>(d);     //и наоборот 	d = static_cast<int>(i); 	//указатель на int к char 	c = static_cast<char>(pi);//Ошибка!!! 	//char к указателю на void 	v = static_cast<void*>(c);//Ошибка!!! 	//указатель на void к указателю на int 	pi = static_cast<int*>(v); 	//Снимаем константность const int* 	pi = static_cast<int *>(cpi);//Ошибка!!! 	//Приводим указатель на объект AAA к указателю на объект BBB     //из разных иерархий 	pA = static_cast<AAA*>(pB);//Ошибка!!! 	//Приводим указатель на double к double 	d = static_cast<double>(pd);//Ошибка!!! 	//А если наоборот? 	pd = static_cast<double*>(d0);//Ошибка!!! 	//Перемещение из одной иерархии наследования в другую 	pB = static_cast<BBB*>(pBX); 	pBY = static_cast<BBB_Y*>(pB); 	return 0; } 

dynamic_cast

Оператор приведения dynamic_cast применяется для полиморфного приведения типов на этапе выполнения программы (класс считается полиморфным, если в нем есть хотя бы одна виртуальная функция). Если указатель, подлежащий приведению, ссылается на объект результирующего класса или объект класса производный от результирующего то приведение считается успешным. То же самое для ссылок. Если приведение невозможно, то на этапе выполнения программы будет возвращен NULL, если приводятся указатели. Если привидение производится над ссылками, то будет сгенерировано исключение std::bad_cast. Несмотря на то, что dynamic_cast предназначен для приведения полиморфных типов по иерархии наследования, он может быть использован и для обычных неполиморфных типов вверх по иерахии. В этом случае ошибка будет получена на этапе компиляции. Оператор приведения dynamic_cast приводить к указателю на void, но не может приводить указатель на void к другому типу.

Общий вид приведения:

static _cast<new_type>(exp)

Показать пример

#include <iostream> //Пустые классы только //для теста приведения struct AAA{ 	//Сделали полиморфным 	virtual void do_some(){}; }; struct BBB{ 	//Сделали полиморфным 	virtual void do_some(){}; }; //Наследники BBB struct BBB_X:BBB{ }; struct BBB_Y:BBB{ }; int main() { 	//Переменные простых типовы и указатели на переменные простых типов 	void*  v = NULL; 	//Объекты классов 	AAA A; 	BBB B; 	BBB_X BX; 	BBB_Y BY; 	//Указатели на объекты классов 	AAA* pA = &A; 	BBB* pB = &B; 	BBB_X* pBX = &BX; 	BBB_Y* pBY = &BY; 	//Приводим указатель на объект AAA к указателю на объект BBB     //из разных иерархий 	pA = dynamic_cast<AAA*>(pB); 	if (pA == NULL) 	{	 		std::cout<<"FAIL"<<std::endl;//Ошибка на этапе выполнения!!! 	} 	//Приводим указатель на void к указателю на объект BBB 	/pB = dynamic_cast<AAA*>(v); //Ошибка на этапе компиляции!!! 	//Приводим указатель на BBB к указателю на void 	v = dynamic_cast<void*>(pB); 	//Перемещение из одной иерархии наследования в другую 	pB = dynamic_cast<BBB*>(pBX); 	pBY = dynamic_cast<BBB_Y*>(pB); 	if (pBY == NULL) 	{ 		std::cout<<"FAIL"<<std::endl;//Ошибка на этапе выполнения!!! 	} 	system("pause"); 	return 0; } 

Источники:
Видеолекция Евегния Линского с проекта Лекториум
Блог Алёна С++
Этот пост dreary_eyes
«Полный справочник по C++» Герберт Шилдт

ссылка на оригинал статьи http://habrahabr.ru/post/266747/


Комментарии

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

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