Естественно, мы не хотели бы совсем отказываться от защиты, представляемой системой типов, но иногда у нас нет логичной альтернативы (например, когда мы должны обеспечить работу с программой, написанной на другой языке программирования, в котором ничего не известно о системе типов языка С++). Кроме того, существует множество ситуаций, в которых необходимо использовать старые программы, разработанные без учета системы безопасности статических типов.
В таких случаях нам нужны две вещи.
• Тип указателя, ссылающегося на память без учета информации о том, какие объекты в нем размещены.
• Операция, сообщающая компилятору, какой тип данных подразумевается (без проверки) при ссылке на ячейку памяти с помощью такого указателя.
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
. Разумеется, мы понимали, что делали.