Полезность указателя void в C и C++

Я знаю, что void type вообще не имеет значения https://www.gnu.org/software/c-intro-and-ref/manual/html_node/The-Void-Type.html в C. Но у меня есть видел некоторые коды на C, в которых некоторые переменные параметров передавались как указатель void:

return_type my_function_signature(void* param1, int num_count, double real_value){
      type_to_cast_to var_param1 = (type_to_cast_to *)param1;
      // write something here
    enter code here
    return return_value;

}

Интересно, какой смысл иметь void* param1 в списке параметров, если в теле функции точно известен тип для приведения?

Это не функция шаблона в C++ https://en.cppreference.com/w/cpp/language/variable_template.

Любая помощь от знатоков C и C++?

Действительно ограничьте свой вопрос либо «C», либо «C++». В C++ используйте void* только тогда, когда имеете дело с устаревшим API C или когда вы знаете, что означают шаблоны и стирание типов.

Pepijn Kramer 05.09.2024 21:00

Его полезность состоит в том, чтобы скрыть детали реализации. «Внешний» пользователь указателя просто знает, что это непрозрачный указатель, но внутри кода он приводит его к правильному типу.

Eljay 05.09.2024 21:00

@Eljay, риск сделать это в C++ заключается в том, что можно попытаться передать необработанные данные в экземпляр класса, который не начинает свое существование и, следовательно, является неверным.

Pepijn Kramer 05.09.2024 21:02

Подумайте, например, о malloc. Он возвращает указатель на что-то, что не имеет определенного типа, пока не будет использовано для хранения чего-либо.

Eugene Sh. 05.09.2024 21:04

Также взгляните, например, на pthreads и на то, как в него передается функция потока. Если вам нужен несколько «обобщенный» API, вам нужно иметь возможность иметь некий «универсальный» тип, который можно будет законно преобразовать в другие типы.

Eugene Sh. 05.09.2024 21:08

@Eljay Этого можно добиться с помощью предварительного объявления какого-либо типа. Это имеет то преимущество, что является указателем на правильный тип и не раскрывает реализацию «внешнему» пользователю. void указатели в C используются для передачи указателей на «любые» данные. Как в примере с malloc или передачей непрозрачных пользовательских данных обратно пользователю.

sklott 05.09.2024 21:10

@Pepijn Kramer вопрос касается исключительно буквы «C». Но их нередко можно увидеть и в «C++». Как вы упомянули, устаревшие функции.

José Mabuxa 05.09.2024 21:11

Обязательно спросите в контексте C. Если вы спросите в контексте C++, вы просто получите поток вопросов: «Зачем стрелять себе в лицо?» комментарии. Если вам повезет, кто-нибудь напишет ответ, показывающий, как можно избежать указателей void в каждом случае, не связанном с устаревшим кодом. Что отвечает на вопрос: «Обычно бесполезно в C++».

user4581301 05.09.2024 21:12

Некоторая путаница может возникнуть из-за того, что тип void обозначает «ничего», а void* — это указатель на «что угодно».

Eugene Sh. 05.09.2024 21:15

Используется в MPI для параллельной обработки для распространения общего типа данных: например. mpich.org/static/docs/v3.0.x/www3/MPI_Sendrecv.html

lastchance 05.09.2024 21:17

@Eljay, для простоты использования я просто смотрю на определение функции и передаю ей количество переменных с соответствующими типами, не заглядывая в исходный файл, в котором она была определена. Так что я не вижу в этом риска. memcpy, memmove и т. д.

José Mabuxa 05.09.2024 21:20

Опасность заключается в том, что функция ожидает заданный тип, и компилятор не может проверить, действительно ли тип аргумента соответствует ожидаемому, что позволяет легко случайно передать foo вместо bar и не узнать, пока программа не выйдет из строя во время выполнения. memcpy и memmove просто оперируют сгустками памяти, не применяя никакой интерпретации к тому, что они копируют. Они не могут потерпеть неудачу, но программист все равно может использовать их неправильно и увидеть результаты ошибок позже.

user4581301 05.09.2024 21:37

@ЕвгенийШ. pthreads — это устаревший API.

Pepijn Kramer 05.09.2024 21:43

memset, memcpy и memmove также являются API-интерфейсами в стиле «C». И вообще, в современном C++ им уже нет места. Вы можете очистить структуры при инициализации. struct S{ int x; }; S s{}. Вы можете использовать typesafe std::copy вместо memcpy. И т. д... вот что имеет в виду @user4581301, когда говорит, что в C++ есть способы избежать void* в каждом случае.

Pepijn Kramer 05.09.2024 21:48

В ответе ниже в качестве примера используется qsort. Хороший пример, но qsort — это еще одна функция C. Функции сортировки стандартной библиотеки C++ являются шаблонными, что позволяет компилятору определить типы и убедиться, что они имеют смысл при построении программы. Обычно это лучшее время для решения проблемы, чем когда вы находитесь в поле с разгневанным клиентом.

user4581301 05.09.2024 21:58

Также обратите внимание, что приведенный выше пример, MPI, представляет собой библиотеку, написанную в основном на C и предоставляющую интерфейс C. Интерфейсы C++, как правило, непереносимы (по нескольким причинам), поэтому вы либо выпускаете библиотеку как код для сборки пользователями (но используете менеджер пакетов для выполнения черновой работы, где это возможно), либо распространяется в виде двоичной оболочки. в интерфейсе C, и здесь вы можете использовать void * в C++.

user4581301 05.09.2024 22:15
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
16
73
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

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

Хорошо известным примером того и другого является функция qsort из стандартной библиотеки, которая имеет следующее объявление:

   void qsort(void *base, size_t nmemb, size_t size,
              int (*compar)(const void *, const void *));

Здесь параметр base указывает на начало массива, а указатель функции compar принимает указатели на два объекта для сравнения. В обоих случаях функции qsort неизвестно, какой тип массива/элемента.

Предположим, вы хотите отсортировать массив int и массив float. Вы должны создать следующие функции обратного вызова:

int comp_int(const void *p1, const void *p2)
{
    const int *i1 = p1;
    const int *i2 = p2;

    if (*i1 < *i2) {
        return -1;
    } else if (*i1 > *i2) {
        return 1;
    } else {
        return 0;
    }
}

int comp_float(const void *p1, const void *p2)
{
    const float *f1 = p1;
    const float *f2 = p2;

    if (*f1 < *f2) {
        return -1;
    } else if (*f1 > *f2) {
        return 1;
    } else {
        return 0;
    }
}

А функция qsort будет вызываться так:

int a1[5] = { ... };
float a2[10] = { ... };
qsort(a1, 5, sizeof *a1, comp_int);
qsort(a2, 10, sizeof *a2, comp_float);

Другие вопросы по теме