Существует ли законное использование для void *?
Существует ли законное использование void*
в С++? Или это было введено, потому что C имел это?
Просто чтобы вспомнить мои мысли:
Ввод. Если мы хотим разрешить несколько типов ввода, мы можем перегружать функции и методы, в качестве альтернативы мы можем определить общий базовый класс или шаблон (спасибо за упоминание об этом в ответах). В обоих случаях код становится более описательным и менее подверженным ошибкам (при условии, что базовый класс реализован разумным образом).
Вывод. Я не могу представить себе ситуации, в которой я бы предпочел получить void*
, а не что-то, полученное из известного базового класса.
Просто, чтобы понять, что я имею в виду: я не спрашиваю, есть ли прецедент для void*
, но если есть случай, когда void*
является лучшим или только доступным выбором. На что отлично ответили несколько человек ниже.
Ответы
Ответ 1
void*
по крайней мере необходимо в результате ::operator new
(также каждый operator new
...) и malloc
и как аргумент оператора размещения new
.
void*
можно рассматривать как общий супертип каждого типа указателя. Таким образом, это не совсем означает указатель на void
, но указатель на что-либо.
Кстати, если вы хотите сохранить некоторые данные для нескольких несвязанных глобальных переменных, вы можете использовать некоторые std::map<void*,int> score;
, а затем объявив глобальные int x;
и double y;
и std::string s;
do score[&x]=1;
и score[&y]=2;
и score[&z]=3;
memset
требует адрес void*
(самые общие)
Кроме того, системы POSIX имеют dlsym, и его тип возврата, очевидно, должен быть void*
Ответ 2
Существует несколько причин использовать void*
, 3 наиболее распространенных типа:
- взаимодействует с библиотекой C, используя
void*
в своем интерфейсе
- типа стирания
- для обозначения непечатаемой памяти
В обратном порядке обозначение непечатаемой памяти с void*
(3) вместо char*
(или вариантов) помогает предотвратить случайную арифметику указателя; на void*
доступно очень мало операций, поэтому для их использования обычно требуется отливка. И, конечно, очень похоже на char*
нет проблемы с aliasing.
Тип-стирание (2) все еще используется в С++ в сочетании с шаблонами или нет:
- не общий код помогает уменьшить бинарное раздувание, он полезен при холодных путях даже в общем коде
- не общий код необходим для хранения иногда даже в универсальном контейнере, таком как
std::function
И, очевидно, когда интерфейс, с которым вы работаете, использует void*
(1), у вас мало выбора.
Ответ 3
О да. Даже в С++ иногда мы встречаемся с void *
, а не с template<class T*>
, потому что иногда лишний код из расширения шаблона слишком велик.
Обычно я использовал бы его как фактическую реализацию типа, и тип шаблона наследовал бы от него и завершал бы приведения.
Кроме того, пользовательские распределители slab (новые реализации оператора) должны использовать void *
. Это одна из причин, по которой g++ добавила расширение разрешающего арифметического указателя на void *
, как если бы оно имело размер 1.
Ответ 4
Ввод: если мы хотим разрешить несколько типов ввода, мы можем перегрузить функции и методы
True.
альтернативно мы можем определить общую базу класс.
Это отчасти верно: что, если вы не можете определить общий базовый класс, интерфейс или подобное? Чтобы определить те, которые вам нужны для доступа к исходному коду, что часто невозможно.
Вы не указали шаблоны. Тем не менее шаблоны не могут помочь вам в полиморфизме: они работают со статическими типами, то есть известны во время компиляции.
void*
можно рассматривать как самый низкий общий знаменатель. В С++ вы, как правило, не нуждаетесь в нем, потому что (i) вы не можете по своей сути много сделать с ним, и (ii) почти всегда есть лучшие решения.
Кроме того, вы, как правило, должны конвертировать его в другие конкретные типы. Вот почему char *
обычно лучше, хотя это может указывать на то, что вы ожидаете строку C-стиля, а не чистый блок данных. Для этого void*
лучше, чем char*
для этого, потому что он допускает неявное отбрасывание из других типов указателей.
Вы должны получать некоторые данные, работать с ним и производить вывод; Чтобы достичь этого, вам нужно знать данные, с которыми работаете, иначе у вас есть другая проблема, которая не та, которую вы изначально решали. На многих языках нет void*
и, например, нет проблем с этим.
Другое законное использование
При печати адресов указателей с такими функциями, как printf
, указатель должен иметь тип void*
и, следовательно, вам может понадобиться приведение к void
*
Ответ 5
Да, это так же полезно, как и любая другая вещь на этом языке.
В качестве примера вы можете использовать его для стирания типа класса, который при необходимости можно статически применять в нужном типе, чтобы иметь минимальный и гибкий интерфейс.
В который отвечает, есть пример использования, который должен дать вам идею.
Я скопирую и вставляю его ниже для ясности:
class Dispatcher {
Dispatcher() { }
template<class C, void(C::*M)() = C::receive>
static void invoke(void *instance) {
(static_cast<C*>(instance)->*M)();
}
public:
template<class C, void(C::*M)() = &C::receive>
static Dispatcher create(C *instance) {
Dispatcher d;
d.fn = &invoke<C, M>;
d.instance = instance;
return d;
}
void operator()() {
(fn)(instance);
}
private:
using Fn = void(*)(void *);
Fn fn;
void *instance;
};
Очевидно, что это только одна из задач использования void*
.
Ответ 6
Взаимодействие с внешней библиотечной функцией, которая возвращает указатель. Вот один из приложений Ada.
extern "C" { void* ada_function();}
void* m_status_ptr = ada_function();
Это возвращает указатель на то, о чем хотела рассказать Ада. Вам не нужно ничего делать с этим, вы можете вернуть его Аде, чтобы сделать следующее.
Фактически распутывание указателя Ada в С++ является нетривиальным.
Ответ 7
Короче говоря, С++ как строгий язык (не принимая во внимание реликвии C, такие как malloc()), требует void *, поскольку у него нет общего родителя всех возможных типов. В отличие от ObjC, например, у которого есть объект.
Ответ 8
Первое, что приходит мне на ум (я подозреваю, что это конкретный случай из пары ответов выше) - это возможность передать экземпляр объекта потоку в Windows.
У меня есть несколько классов С++, которые должны это сделать, у них есть реализации рабочего потока, а параметр LPVOID в API CreateThread() получает адрес реализации статического метода в классе, чтобы рабочий поток мог выполнять работа с конкретным экземпляром класса. Простой статический возврат в threadproc дает экземпляр для работы, позволяя каждому экземпляру-экземпляру иметь рабочий поток из одной статической реализации метода.
Ответ 9
В случае множественного наследования, если вам нужно получить указатель на первый байт блока памяти, занимаемого объектом, вы можете dynamic_cast
до void*
.