Когда использовать пустой указатель? - PullRequest
39 голосов
/ 22 июня 2009

Я понимаю использование указателя void для реализации malloc.

void* malloc  ( size_t size );

Может ли кто-нибудь предложить другие причины или сценарии, если это полезно на практике.

Спасибо

Ответы [ 13 ]

30 голосов
/ 22 июня 2009

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

typedef struct node_t node;
struct
{
    void* data;
    node* prev, next;
} node_t;

typedef struct list_t list;
typedef void* (func)(void*) cpy_func;
typedef void (func)(void*) del_func;
struct
{
   node* head, tail, curr;
   cpy_func copy;
   del_func delete;
} list_t;

initializeLinkedList(cpy_func cpy, del_func del);
//here you keep going defining an API

Здесь, например, вы передадите указатели функций инициализации другим функциям, которые смогут скопировать ваш тип данных в ваш список и впоследствии освободить его. Таким образом, используя void*, вы сделаете свой список более общим.

Я думаю, void* остался в C ++ только из-за обратной совместимости, поскольку в C ++ у вас есть более безопасные и сложные способы достижения того же результата, как шаблоны, функторы и т. Д., И вам не нужно использовать malloc, пока программирование на С ++.

Что касается C ++, у меня нет конкретных полезных примеров.

12 голосов
/ 22 июня 2009

Если вы взаимодействуете с кодом C и вам необходимо пройти через объект C ++, но библиотека C примет только общий указатель, то при получении указателя вам необходимо повторно привести его к нужному типу.

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

9 голосов
/ 11 сентября 2015

В C ++ я обнаружил, что наиболее убедительный пример использования указателей void * состоит в том, чтобы дать коду возможность хранить произвольные «пользовательские данные» на объекте, который они уже используют.

Допустим, вы написали класс, представляющий Car, для использования в программном обеспечении, которое делает полезные вещи с Car объектами (симуляция движения, инвентарь для аренды автомобиля и т. Д.). Теперь предположим, что вы оказались в ситуации, когда ваше приложение хочет отслеживать произвольное содержимое транка Car. Детали того, что хранится в багажнике, не важны для класса Car и могут быть чем угодно - это действительно зависит от цели приложения, использующего класс Car. Введите указатель void *.

class Car
{
    public:

        // Existing methods of your Car class

        void setContentsOfTrunk(void* contentsOfTrunk);
        void* contentsOfTrunk() const;

    private:

        void* m_contentsOfTrunk;
}

Теперь любое приложение, использующее ваш класс Car, имеет возможность присоединить произвольный объект данных к существующему объекту Car, так что его можно получить из любого кода, который имеет объект Car. Содержимое ствола «путешествует» с объектом Car, куда бы он ни шел в вашем коде.

В этом случае есть две альтернативы использованию void *.

Первый - это шаблон вашего класса на основе типа объекта содержимого ствола:

template <class TrunkContentsType>
class Car
{
    public:

        // Existing methods of your Car class

        void setContentsOfTrunk(TrunkContentsType contentsOfTrunk);
        TrunkContentsType contentsOfTrunk() const;

    private:

        TrunkContentsType m_contentsOfTrunk;
}

Это кажется излишне агрессивным. Тип содержимого ствола важен только для приложения. Алгоритмам и структурам данных, работающим с объектами Car, не важно, что находится в багажнике. Шаблонируя класс, вы заставляете приложения, использующие класс, выбирать тип для содержимого внешних линий, но во многих случаях приложения также не заботятся о содержимом внешних линий.

Второй альтернативой является получение нового класса от Car, который добавляет элемент данных и средства доступа для содержимого соединительной линии:

class Car
{
    public:

        // Existing methods of your Car class
        // No methods having anything to do with trunk contents.

    private:

        // No data member representing trunk contents.
}

class CarWithTrunkContents
{
    public:

        // Existing methods of your Car class

        void setContentsOfTrunk(TrunkContentsType contentsOfTrunk);
        TrunkContentsType contentsOfTrunk() const;

    private:

        TrunkContentsType m_contentsOfTrunk;
}

Новый класс CarWithTrunkContents является классом для приложения, который добавляет элемент данных того типа, который требуется приложению для хранения содержимого багажника на автомобиле. Это также кажется излишне тяжеловесным. Почему вы должны создать совершенно новый класс, чтобы добавить дополнительный фрагмент данных, который не влияет на поведение класса? И если приложения, использующие класс Car, довольно часто хотят хранить содержимое соединительных линий, зачем заставлять каждое приложение получать новый класс для своего конкретного типа содержимого соединительных линий?

Наконец, хотя мой надуманный пример содержимого ствола, возможно, рисует яркую картину произвольного содержимого ствола, перемещающегося с объектом Car, на практике вы, скорее всего, предоставите еще более общий механизм для присоединения специфичных для приложения данных к Car:

class Car
{
    public:

        // Existing methods of your Car class

        void setUserData(void* userData);
        void* userData() const;

    private:

        void* m_userData;
}

Таким образом, приложение может прикрепить объект, представляющий содержимое багажника, или объект, представляющий водительские права и регистрацию, или объект, представляющий договор аренды, или что-то еще. Я видел этот тип указателя void *, называемого «userData» (то есть понимаемый пользователем класса), «blindData» (то есть класс слеп по отношению к содержимому объекта, который он несет) или «applicationData» ( т.е. данные типа и цели, определенных приложением).

9 голосов
/ 22 июня 2009

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

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

3 голосов
/ 22 июня 2009

Другим примером таких «обобщений» C, реализованных с помощью void *, является стандартная функция qsort:

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

Вы можете отсортировать массив любого типа: int, long, double, char * или некоторые структурные указатели ...

3 голосов
/ 22 июня 2009

Отличный способ узнать все о пустоте * и других темах C - это посмотреть первую половину фантастической Стэнфордской «Парадигмы программирования» на iTunes-U. Это действительно фантастически объясняет void * (C generics) и указатели! Это определенно помогло мне лучше понять C ...

Одним из наиболее важных применений является использование void *, если вы хотите иметь возможность принимать различные типы данных в функции. (вот пример: http://142.132.30.225/programming/node87.html)

Вот еще один пример того, для чего вы можете использовать их:

  int i;
  char c;
  void *the_data;

  i = 6;
  c = 'a';

  the_data = &i;
  printf("the_data points to the integer value %d\n", *(int*) the_data);

  the_data = &c;
  printf("the_data now points to the character %c\n", *(char*) the_data);

Если вы не хотите смотреть бесплатные уроки в Стэнфорде, я бы порекомендовал поискать пустые указатели и прочитать там весь материал.

2 голосов
/ 26 октября 2009

void * действительно является C-ism, и позволяет C делать некоторые вещи, которые он не мог разумно сделать иначе.

char * нельзя использовать ни для чего, так как разные платформы могут создавать указатели разных типов - char * не обязательно обрабатывается так же (или даже имеет тот же размер), что и void *.

Поэтому, когда тип данных неизвестен в C (или является полиморфным или иным образом динамическим), тогда void * позволяет вам генерировать правильный базовый тип указателя - тот, который может указывать на что-либо правильно.

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

2 голосов
/ 22 июня 2009

Помимо взаимодействия с C, я обнаруживаю, что использую указатели void, только когда мне нужно отладить / отследить некоторый код и узнать адрес определенного указателя.

SomeClass * myInstance;
// ...
std::clog << std::hex << static_cast< void* >(myInstance) << std::endl;

напечатает что-то вроде

0x42A8C410

И, на мой взгляд, хорошо документирует, что я пытаюсь сделать (знаю адрес указателя, а не что-либо об экземпляре)

2 голосов
/ 22 июня 2009

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

double find_root(double x0, double (*f)(double, void*), void* params)
{
/* stuff */
y = f(x, params);
/* other stuff */
}

params приводится f к некоторой структуре, о которой он знает, но find_root нет.

1 голос
/ 14 июля 2015

Существует большое преимущество использования пустого указателя. Переменная-указатель - это переменная, в которой хранится адрес другой переменной. пример:

int a;
int *x= &a;

Теперь в 'x' хранится адрес целочисленной переменной.

Но этот провал:

float f;
int *x = &f;

Поскольку целочисленная переменная-указатель может хранить только адрес целочисленной переменной. таким же образом это относится и к другим типам данных.

Когда вы используете указатель void *, он дает преимущество для хранения адреса любой переменной TYPE.

void *pointer = &i;
void *pointer = &f;

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

*((int*)pointer)

Итак, осторожно используйте пустой указатель.

Это может помочь вам, спасибо.

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