10.20 – Указатели void

Добавлено 9 июня 2021 в 18:31
Глава 10 – Массивы, строки, указатели и ссылки  (содержание)

Указатель 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 может быть нулевым указателем.

Теги

C++ / CppLearnCppvoidДля начинающихОбучениеПрограммированиеУказатель / Pointer (программирование)Указатель void / Обобщенный указатель

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.