Существует ли законное использование для 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*.