Чи правомірно використовувати void *?


87

Чи є законним використання void*C ++? Або це було запроваджено тому, що це було у С?

Просто, щоб підбити підсумки своїх думок:

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

Результат : Я не можу придумати жодної ситуації, коли б я віддав перевагу отримувати void*на відміну від чогось, що походить від відомого базового класу.

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


3
як щодо часу, коли ви хочете мати кілька типів, таких як int & std :: string?
Амір

12
@Amir, variant, any, позначений союз. Все, що може сказати вам фактичний тип вмісту та безпечніше у використанні.
Revolver_Ocelot

15
"C has it" - досить вагоме виправдання, не потрібно шукати більше. Якнайменше цього уникати - це добре в обох мовах.
п. 'займенники' m.

1
Але одне: взаємодія з API в стилі C без неї незручна.
Мартін Джеймс

1
Цікавим є використання стирання типу для векторів покажчиків
Paolo M

Відповіді:


81

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

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

До речі, якщо ви хочете зберегти деякі дані для декількох не пов'язаних глобальних змінних, ви можете використати деякі std::map<void*,int> score; тоді, після оголошення глобальних int x;і double y;і std::string s;, score[&x]=1;і score[&y]=2;і іscore[&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*>тому, що іноді додатковий код розширення шаблону важить занадто багато.

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

Також слід використовувати спеціальні розподільники слябів (нові реалізації оператора) 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;є мінімальним порожнечею - * - симулятором з використанням шаблонів.
user253751

3
@ Джошуа: Вам знадобиться посилання на "більшість".
user541686

@Mehrdad: Див. Кеш L1.
Джошуа

10

Введення: Якщо ми хочемо дозволити декілька типів введення, ми можемо перевантажити функції та методи

Правда.

в якості альтернативи ми можемо визначити загальний базовий клас.

Це частково вірно: що робити, якщо ви не можете визначити загальний базовий клас, інтерфейс або подібний? Для визначення тих, кому потрібно мати доступ до вихідного коду, що часто неможливо.

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

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

Навіть далі, як правило, вам доведеться перетворити його на інші конкретні типи. Ось чому char *зазвичай краще, хоча це може вказувати на те, що ви очікуєте рядок у стилі С, а не чистий блок даних. Ось чому void*краще, ніж char*для цього, оскільки він дозволяє неявний привід з інших типів покажчиків.

Ви повинні отримувати деякі дані, працювати з ними та видавати результати; щоб досягти цього, вам потрібно знати дані, з якими ви працюєте, інакше у вас інша проблема, не та, яку ви вирішували спочатку. void*Наприклад, багато мов не мають і не мають з цим проблем.

Ще одне законне використання

При друку адрес вказівника з такими функціями, як printfвказівник, має бути void*тип, і, отже, вам може знадобитися приведення до void*


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

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

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

void* m_status_ptr = ada_function();

Це повертає вказівник на те, про що Ада хотіла вам розповісти. Вам не потрібно робити з цим нічого вигадливого, ви можете повернути його Аді, щоб зробити наступне. Насправді розплутування вказівника Ада в C ++ є нетривіальним.


Чи не могли б ми використати autoзамість цього в цьому випадку? Припускаючи, що тип відомий під час компіляції.
magu_

Ах, сьогодні ми, мабуть, могли б. Цей код походить від кількох років тому, коли я зробив кілька обгортків від Ади.
RedSonja

Типи Ада можуть бути диявольськими, самі розумієте, і отримують збочене задоволення, роблячи це хитро. Мені не дозволили змінити інтерфейс, це було б занадто просто, і це повернуло щось неприємне, заховане в тих порожніх вказівниках. Тьфу.
RedSonja

2

Коротше кажучи, С ++ як сувора мова (не беручи до уваги реліквії С, як malloc () ) вимагає void *, оскільки він не має загального батьківського з усіх можливих типів. На відміну від ObjC, наприклад, який має об'єкт .


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 дає екземпляр для роботи, дозволяючи кожному екземплярові об’єкту мати робочий потік з одного реалізації статичного методу.


0

У разі множинного спадкоємства, якщо вам потрібно отримати покажчик на перші байти шматку пам'яті , займаного об'єкт, ви можете dynamic_castв void*.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.