Есть ли законное использование void *?

87

Есть ли законное использование void*в C ++? Или это было введено, потому что это было в C?

Просто резюмирую свои мысли:

Ввод : если мы хотим разрешить несколько типов ввода, мы можем перегрузить функции и методы, в качестве альтернативы мы можем определить общий базовый класс или шаблон (спасибо, что упомянули это в ответах). В обоих случаях код становится более наглядным и менее подверженным ошибкам (при условии, что базовый класс реализован разумным образом).

Вывод : я не могу представить себе ситуацию, когда я предпочел бы получать, void*а не что-то производное от известного базового класса.

Просто чтобы прояснить, что я имею в виду: я специально не спрашиваю, есть ли вариант использования void*, но есть ли случай, когда void*это лучший или единственный доступный выбор. На что несколько человек ниже прекрасно ответили.

magu_
источник
3
как насчет того времени, когда вы хотите иметь несколько типов, например int & std :: string?
Амир
12
@Amir, variant, any, помеченный союз. Все, что может сказать вам реальный тип контента и что безопаснее в использовании.
Revolver_Ocelot
15
«В C есть это» - достаточно веское оправдание, не нужно искать большего. Как можно чаще избегать этого - это хорошо для любого языка.
п. 'местоимения' м.
1
Замечу одно: взаимодействие с API в стиле C без него неудобно.
Мартин Джеймс,
1
Интересное использование - стирание типов векторов указателей
Паоло М.

Ответы:

81

void*по крайней мере необходимо как результат ::operator new(также каждый operator new...) and of mallocи как аргумент newоператора размещения .

void*можно рассматривать как общий супертип для каждого типа указателя. Так что это не совсем указатель void, а указатель на что угодно.

Кстати, если вы хотите сохранить некоторые данные для нескольких несвязанных глобальных переменных, вы можете использовать их std::map<void*,int> score; тогда, после того, как объявили global int x;and double y;and std::string s;do score[&x]=1;and score[&y]=2;andscore[&z]=3;

memset хочет void*адрес (самые общие)

Кроме того, в системах POSIX есть dlsym, и его тип возврата, очевидно, должен бытьvoid*

Василий Старынкевич
источник
1
Это очень хороший момент. Я забыл упомянуть, что больше думал о языке пользователя, чем о реализации. Еще одна вещь, которую я не понимаю: это новая функция? Я думал об этом больше как о ключевом слове
magu_
9
new - это оператор, например + и sizeof.
Джошуа
7
Конечно, память char *тоже можно вернуть . Они очень близки в этом аспекте, хотя смысл немного другой.
edmz
@Джошуа. Не знал этого. Спасибо, что научили меня этому. Поскольку C++в C++этом написано, это достаточная причина для того, чтобы иметь void*. Должен любить этот сайт. Задайте один вопрос, узнайте много нового.
magu_
3
@black Назад в старые времена, C даже не имеют в void*тип. Использованы все стандартные библиотечные функцииchar*
Коул Джонсон
28

Есть несколько причин для использования void*, из которых 3 наиболее распространенные:

  1. взаимодействие с библиотекой C, используя void*в ее интерфейсе
  2. стирание шрифта
  3. обозначает нетипизированную память

В обратном порядке обозначение нетипизированной памяти с помощью void*(3) вместо char*(или вариантов) помогает предотвратить случайную арифметику указателя; доступно очень мало операций, void*поэтому обычно требуется преобразование, прежде чем оно станет полезным. И, конечно же, как и в случае char*с псевдонимом, нет проблем.

Стирание типа (2) все еще используется в C ++ в сочетании с шаблонами или нет:

  • неуниверсальный код помогает уменьшить двоичное раздувание, он полезен в холодных путях даже в общем коде
  • неуниверсальный код иногда необходим для хранения, даже в универсальном контейнере, таком как std::function

И, очевидно, когда интерфейс, с которым вы работаете, использует void*(1), у вас мало выбора.

Матье М.
источник
15

О да. Даже в C ++ иногда мы используем, void *а не template<class T*>потому, что иногда дополнительный код из расширения шаблона слишком много весит.

Обычно я использовал бы его как фактическую реализацию типа, а тип шаблона унаследовал бы от него и обернул бы приведение типов.

Кроме того, должны использоваться специальные распределители slab (новые реализации операторов) void *. Это одна из причин, по которой g ++ добавил расширение, разрешающее арифметику указателя, void *как если бы он был размером 1.

Джошуа
источник
Спасибо за ваш ответ. Вы правы, я забыл упомянуть шаблоны. Но было бы не шаблоны быть даже лучше подходит для выполнения этой задачи , так как редко ввести штраф производительности (? Stackoverflow.com/questions/2442358/... )
magu_
1
Шаблоны приводят к снижению размера кода, что в большинстве случаев также снижает производительность.
Джошуа,
struct wrapper_base {}; template<class T> struct wrapper : public wrapper_base {T val;} typedef wrapper* like_void_ptr;это минимальный симулятор void - * - с использованием шаблонов.
user253751
3
@Joshua: Вам понадобится ссылка на «большинство».
user541686
@Mehrdad: См. Кеш L1.
Джошуа
10

Ввод: если мы хотим разрешить несколько типов ввода, мы можем перегрузить функции и методы.

Правда.

в качестве альтернативы мы можем определить общий базовый класс.

Отчасти это правда: что, если вы не можете определить общий базовый класс, интерфейс или что-то подобное? Чтобы определить их, вам нужен доступ к исходному коду, что часто невозможно.

Вы не упомянули шаблоны. Однако шаблоны не могут помочь вам с полиморфизмом: они работают со статическими типами, то есть известными во время компиляции.

void*можно рассматривать как наименьший общий знаменатель. В C ++ он обычно не нужен, потому что (i) вы сами по себе мало что можете с ним сделать и (ii) почти всегда есть лучшие решения.

Более того, вы, как правило, преобразуете его в другие конкретные типы. Вот почему char *обычно лучше, хотя это может указывать на то, что вы ожидаете строку в стиле C, а не чистый блок данных. Вот почему void*это лучше, чем char*для этого, потому что он позволяет неявное приведение из других типов указателей.

Вы должны получать данные, работать с ними и производить результат; для этого вам необходимо знать данные, с которыми вы работаете, иначе у вас возникнет другая проблема, отличная от той, которую вы решали изначально. void*Например, у многих языков нет и нет проблем с этим.

Другое законное использование

При печати адресов указателя с помощью таких функций, как printfуказатель, должен быть void*тип, и поэтому вам может потребоваться приведение к void*

EDMZ
источник
7

Да, это так же полезно, как и все остальное на этом языке.
Например, вы можете использовать его, чтобы стереть тип класса, который вы можете статически преобразовать к нужному типу при необходимости, чтобы иметь минимальный и гибкий интерфейс.

В этом ответе есть пример использования, который должен дать вам представление.
Я скопировал и вставил его ниже для ясности:

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*.

скайпджек
источник
4

Взаимодействие с функцией внешней библиотеки, которая возвращает указатель. Вот один для приложения на Аде.

extern "C" { void* ada_function();}

void* m_status_ptr = ada_function();

Это возвращает указатель на то, о чем Ада хотела вам рассказать. Вам не нужно делать с ним ничего особенного, вы можете вернуть его Аде, чтобы он сделал следующее. На самом деле распутывание указателя Ada в C ++ нетривиально.

RedSonja
источник
Не могли бы мы использовать autoвместо этого в этом случае? Предполагая, что тип известен во время компиляции.
magu_
Ах, в настоящее время мы, наверное, могли бы. Этот код взят несколько лет назад, когда я делал несколько оберток для Ada.
RedSonja
Типы Ады могут быть дьявольскими - они создают свои собственные, знаете ли, и получают извращенное удовольствие, делая их хитрыми. Мне не разрешили изменить интерфейс, это было бы слишком просто, и он вернул некоторые неприятные вещи, скрытые в этих пустых указателях. Тьфу.
RedSonja
2

Короче говоря, C ++ как строгий язык (без учета таких реликвий C, как malloc () ) требует void *, поскольку у него нет общего родителя для всех возможных типов. В отличие от ObjC, например, у которого есть object .

нредко
источник
mallocи newоба возвращаются void *, так что вам нужно было бы, если бы даже был класс объекта в C ++
Дмитрий Григорьев
malloc является реликтом, но в строгих языках новый должен возвращать объект *
nredko
как тогда выделить массив целых чисел?
Дмитрий Григорьев
@DmitryGrigoryev operator new()возвращается void *, а newвыражение - нет
MM
1. Я не уверен, что хотел бы видеть объект виртуального базового класса над каждым классом и типом , включая int2. если это не виртуальный EBC, то чем он отличается от void*?
lorro
1

Первое, что приходит мне в голову (что, как я подозреваю, является конкретным случаем пары ответов, приведенных выше), - это возможность передать экземпляр объекта в threadproc в Windows.

У меня есть пара классов C ++, которым это необходимо, у них есть реализации рабочего потока, а параметр LPVOID в API CreateThread () получает адрес реализации статического метода в классе, чтобы рабочий поток мог выполнять работу с конкретный экземпляр класса. Простое статическое приведение обратно в threadproc дает экземпляр для работы, позволяя каждому созданному объекту иметь рабочий поток из единственной реализации статического метода.

AndyW
источник
0

В случае множественного наследования, если вам нужно получить указатель на первый байт блока памяти, занятого объектом, вы можете это dynamic_castсделать void*.

Незначительная угроза
источник