10.20 – Указатели void
Указатель void
, также известный как обобщенный указатель, представляет собой специальный тип указателя, который может указывать на объекты любого типа данных! Указатель void
объявляется как обычный указатель с использованием ключевого слова void
в качестве типа указателя:
void *ptr; // ptr - это обобщенный указатель
Указатель void
может указывать на объекты любого типа данных:
int nValue;
float fValue;
struct Something
{
int n;
float f;
};
Something sValue;
void *ptr;
ptr = &nValue; // допустимо
ptr = &fValue; // допустимо
ptr = &sValue; // допустимо
Однако, поскольку указатель void
не знает, на какой тип объекта он указывает, непосредственное косвенное обращение через него невозможно! Указатель void
должен быть сначала явно приведен к другому типу указателя, после чего может быть выполнено косвенное обращение через этот новый указатель.
int value{ 5 };
void *voidPtr{ &value };
// недопустимо: косвенное обращение через указатель void
// std::cout << *voidPtr << '\n';
// однако если мы приведем наш указатель void к указателю int ...
int *intPtr{ static_cast<int*>(voidPtr) };
// тогда мы можем использовать косвенное обращение через него, как обычно
std::cout << *intPtr << '\n';
Этот код напечатает:
5
Следующий очевидный вопрос: если указатель void
не знает, на что он указывает, как мы узнаем, к чему его привести? В конечном итоге это зависит от вас.
Вот пример использования указателя void
:
#include <iostream>
enum class Type
{
INT,
FLOAT,
CSTRING
};
void printValue(void *ptr, Type type)
{
switch (type)
{
case Type::INT:
// приведение к указателю int и выполнение косвенного обращения
std::cout << *static_cast<int*>(ptr) << '\n';
break;
case Type::FLOAT:
// приведение к указателю float и выполнение косвенного обращения
std::cout << *static_cast<float*>(ptr) << '\n';
break;
case Type::CSTRING:
// приведение к указателю char (без косвенного обращения)
std::cout << static_cast<char*>(ptr) << '\n';
// std::cout умеет обрабатывать char* как строку в стиле C
// если бы мы выполняли косвенное обращение к результату,
// мы бы просто напечатали единственный символ, на который указывает ptr
break;
}
}
int main()
{
int nValue{ 5 };
float fValue{ 7.5f };
char szValue[]{ "Mollie" };
printValue(&nValue, Type::INT);
printValue(&fValue, Type::FLOAT);
printValue(szValue, Type::CSTRING);
return 0;
}
Эта программа печатает:
5
7.5
Mollie
Другие сведения об указателях void
Указатели void
могут иметь нулевое значение:
// ptr - это обобщенный указатель, который
// сейчас является нулевым указателем
void *ptr{ nullptr };
Хотя некоторые компиляторы позволяют удалять указатель void
, указывающий на динамически выделенную память, этого следует избегать, поскольку это может привести к неопределенному поведению.
Над указателями void
невозможно выполнять арифметику указателей. Это связано с тем, что арифметика указателей требует, чтобы указатель знал размер объекта, на который он указывает, чтобы можно было правильно инкрементировать или декрементировать указатель.
Обратите внимание, что не существует такой вещи, как ссылка void
. Это связано с тем, что обобщенная ссылка будет иметь тип void&
и не будет знать, на какой тип значения она ссылается.
Заключение
В общем, рекомендуется избегать использования указателей void
без крайней необходимости, поскольку они позволяют избежать проверки типов. Это позволяет вам непреднамеренно делать вещи, которые бессмысленны, и компилятор не будет жаловаться на это. Например, допустимо следующее:
int nValue{ 5 };
printValue(&nValue, Type::CSTRING);
Но кто знает, каков будет результат!
Хотя приведенная выше функция кажется изящным способом заставить одну функцию обрабатывать несколько типов данных, C++ на самом деле предлагает гораздо лучший способ сделать то же самое (с помощью перегрузки функций), который сохраняет проверку типов, чтобы предотвратить неправильное использование. Многие другие места, где когда-то использовались указатели void
для обработки нескольких типов данных, теперь лучше делать с помощью шаблонов, которые также предлагают строгую проверку типов.
Однако в очень редких случаях вы можете найти разумное применение для указателя void
. Просто убедитесь, что нет лучшего (более безопасного) способа сделать то же самое с использованием других языковых механизмов!
Небольшой тест
Вопрос 1
В чем разница между указателем void
и указателем null
?
Ответ
Указатель
void
(обобщенный указатель) – это указатель, который может указывать на объект любого типа, но не знает, на какой тип объекта он указывает. Для выполнения косвенного обращения указательvoid
должен быть явно приведен к другому типу указателя.Указатель
null
(нулевой указатель) – это указатель, который не указывает на адрес.Указатель
void
может быть нулевым указателем.