Практически всегда необходимость явного приведения типов в 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 может появиться лишь в случае использования значительного объема стороннего кода, который нельзя изменить. И даже в этом случае это можно считать серьезной ошибкой проектирования.