IT Notes

Явное приведение типов в C++

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

Похожие публикации