Естественно, мы не хотели бы совсем отказываться от защиты, представляемой системой типов, но иногда у нас нет логичной альтернативы (например, когда мы должны обеспечить работу с программой, написанной на другой языке программирования, в котором ничего не известно о системе типов языка С++). Кроме того, существует множество ситуаций, в которых необходимо использовать старые программы, разработанные без учета системы безопасности статических типов.
В таких случаях нам нужны две вещи.
• Тип указателя, ссылающегося на память без учета информации о том, какие объекты в нем размещены.
• Операция, сообщающая компилятору, какой тип данных подразумевается (без проверки) при ссылке на ячейку памяти с помощью такого указателя.
void* означает “указатель на ячейку памяти, тип которой компилятору неизвестен”. Он используется тогда, когда необходимо передать адрес из одной части программы в другую, причем каждая из них ничего не знает о типе объекта, с которым работает другая часть. Примерами являются адреса, служащие аргументами функций обратного вызова (см. раздел 16.3.1), а также распределители памяти самого нижнего уровня (такие как реализация оператора new).
Объектов типа void не существует, но, как мы видели, ключевое слово void означает “функция ничего не возвращает”.
void v; // ошибка: объектов типа void не существует
void f(); // функция f() ничего не возвращает;
// это не значит, что функция f() возвращает объект
// типа void
Указателю типа void* можно присвоить указатель на любой объект. Рассмотрим пример.
void* pv1 = new int; // OK: int* превращается в void*
void* pv2 = new double[10]; // OK: double* превращается в void*
Поскольку компилятор ничего не знает о том, на что ссылается указатель типа void*, мы должны сообщить ему об этом.
void f(void* pv)
{
void* pv2 = pv; // правильно (тип void* для этого
// и предназначен)
double* pd = pv; // ошибка: невозможно привести тип void*
// к double*
*pv = 7; // ошибка: невозможно разыменовать void*
// (тип объекта, на который ссылается указатель,
// неизвестен)
pv[2] = 9; // ошибка: void* нельзя индексировать
int* pi = static_cast
// ...
}
static_cast позволяет явно преобразовать указатели типов в родственный тип, например void* в double* (раздел A.5.7). Имя static_cast — это сознательно выбранное отвратительное имя для отвратительного (и опасного) оператора, который следует использовать только в случае крайней необходимости. Его редко можно встретить в программах (если он вообще где-то используется). Операции, такие как static_cast, называют static_cast.
• Оператор reinterpret_cast может преобразовать тип в совершенно другой, никак не связанный с ним тип, например int в double*.
• Оператор const_cast позволяет отбросить квалификатор const.
Рассмотрим пример.
Register* in = reinterpret_cast
void f(const Buffer* p)
{
Buffer* b = const_cast
// ...
}
Первый пример — классическая ситуация, в которой необходимо применить оператор reinterpret_cast. Мы сообщаем компилятору, что определенная часть памяти (участок, начинающийся с ячейки 0xFF) рассматривается как объект класса Register (возможно, со специальной семантикой). Такой код необходим, например, при разработке драйверов устройств.
Во втором примере оператор const_cast аннулирует квалификатор const в объявлении const Buffer* указателя p. Разумеется, мы понимали, что делали.