Практически всегда необходимость явного приведения типов в C++ указывает на ошибки проектирования. Но из всех правил есть исключения.
static_cast
Многие библиотеки, поддерживающие функции обратного вызова, позволяют указать сырой указатель void*
, который передается при каждом обратном вызове. Такое решение обеспечивает доступ к произвольному классу из функции обратного вызова. Например:
void onCallback( void* userData );
Предположим, что в качестве пользовательских данных мы передали указатель на некоторый класс:
class UserClass {
// …
};
void testCallback( void ( *callback )( void* ), void* userData ) {
callback( userData );
}
UserClass c;
testCallback( onCallback, &c );
Теперь восстановим указатель в функции onCallback()
:
void onCallback( void* userData ) {
// UserClass* c = userData; Не сработает
UserClass* c = static_cast< UserClass* >( userData ); // А вот это нормально
}
Других обоснованных случаев использования static_cast
в современном C++ практически не существует.
reinterpret_cast
Для приведения несовместимых типов данных используйте reinterpret_cast
, но учитывайте, что всегда есть вероятность, что такой код перестанет работать на другой платформе. Рассмотрим несколько примеров.
Различные силовые тренажеры в интернет магазине большой выбор.
Предположим, что имеется две сторонних функции. Одна обеспечивает формирование исходного массива данных, а вторая используется для его пост-обработки. Однако первая работает с char*
, а вторая с unsigned char*
:
// Функция, формирующая исходный массив данных
void generatorFunction( char**, size_t* );
// Функция для обработки данных
void processorFunction( const unsigned char*, size_t );
char* data = NULL;
size_t size = 0;
someLibFunction( &data, &size );
// someFunction( data, size ); char* в unsigned char* не преобразовывается
// someFunction( static_cast< const unsigned char* >( data ), size ); Слишком разные типы данных
someFunction( reinterpret_cast< unsigned char* >( data ), size ); // ОК
Другой пример из области системного программирования. Если аппаратная среда выполнения однозначно определена, то могут быть сделаны допущения относительно распределения памяти:
class SystemDevice;
// 0xaabbcc - некий адрес
// SystemDevice* device = 0xaabbcc; Недопустимо
SystemDevice* device = reinterpret_cast< SystemDevice* >( 0xaabbcc ); // ОК, но может привести к ошибкам
dynamic_cast
Полиморфизм в C++ и других языках программирования обеспечивает свободную навигацию по иерархии классов сверху-вниз от наследников к предкам. Но в редких случаях может потребоваться выполнить обратную процедуру, то есть перейти по известному предку к наследнику:
class A {
public:
virtual ~A() { }
};
class B : public A {
};
B* b = new B; // Создаем экземпляр наследника
A* a = b; // От наследника к предку: все хорошо
// B* anotherB = a; Но от предка к наследнику уже не работает
if( B* anotherB = dynamic_cast< B* >( a ) ) { // Допустимо, но не лучший вариант
// …
}
delete b;
Несмотря на то, что в C++ есть dynamic_cast
, его использование с большой вероятностью указывает на ошибку в архитектуре приложения, если это не является осознанным проектным решением.
const_cast
Самый спорный из рассмотренных здесь операторов приведения. Он позволяет снять модификаторы const
и volatile
с указателей и ссылок объектов. Рассмотрим пример:
void someFunction( const int& x ) {
// x = 3; Нельзя: x - константная ссылка
const_cast< int& >( x ) = 3;
}
int x = 5;
someFunction2( x );
cout << x << endl; // x = 3
Потребность в использовании const_cast
может появиться лишь в случае использования значительного объема стороннего кода, который нельзя изменить. И даже в этом случае это можно считать серьезной ошибкой проектирования.