В 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» ( т.е. данные типа и цели, определенных приложением).