Я считаю, что союзы C ++ довольно крутые. Кажется, что люди обычно думают только о случае использования, когда кто-то хочет изменить значение экземпляра объединения «на месте» (которое, похоже, служит только для экономии памяти или выполнения сомнительных преобразований).
На самом деле, объединения могут иметь большую силу как инструмент разработки программного обеспечения, , даже если вы никогда не меняете значение какого-либо экземпляра объединения .
Вариант использования 1: хамелеон
С помощью объединений вы можете перегруппировать несколько произвольных классов под одним наименованием, что не лишено сходства со случаем базового класса и его производных классов. Однако, что изменится, это то, что вы можете и не можете делать с данным экземпляром объединения:
struct Batman;
struct BaseballBat;
union Bat
{
Batman brucewayne;
BaseballBat club;
};
ReturnType1 f(void)
{
BaseballBat bb = {/* */};
Bat b;
b.club = bb;
// do something with b.club
}
ReturnType2 g(Bat& b)
{
// do something with b, but how do we know what's inside?
}
Bat returnsBat(void);
ReturnType3 h(void)
{
Bat b = returnsBat();
// do something with b, but how do we know what's inside?
}
Похоже, что программист должен быть уверен в типе содержимого данного экземпляра объединения, когда он хочет его использовать. Дело обстоит так в функции f
выше. Однако, если функция получит экземпляр объединения в качестве переданного аргумента, как в случае с g
выше, то она не будет знать, что с ним делать. То же самое относится и к функциям, возвращающим экземпляр объединения, см. h
: как вызывающая сторона узнает, что находится внутри?
Если экземпляр объединения никогда не передается в качестве аргумента или в качестве возвращаемого значения, то он должен иметь очень монотонную жизнь с всплесками возбуждения, когда программист решает изменить свое содержимое:
Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;
И это самый (не) популярный вариант использования союзов. Другой вариант использования - это когда экземпляр объединения приходит с чем-то, что сообщает вам его тип.
Вариант использования 2: «Приятно познакомиться, я object
, от Class
»
Предположим, программист решил всегда связывать экземпляр объединения с дескриптором типа (я оставляю на усмотрение читателя представить реализацию одного такого объекта). Это противоречит цели самого объединения, если программист хочет сохранить память и что размер дескриптора типа не является ничтожно малым по сравнению с размером объединения. Но давайте предположим, что крайне важно, чтобы экземпляр объединения мог быть передан как аргумент или как возвращаемое значение, когда вызывающий или вызывающий не знает, что находится внутри.
Затем программист должен написать switch
оператор потока управления, чтобы отличить Брюса Уэйна от деревянной палочки или чего-то подобного. Это не так уж плохо, когда в объединении есть только два типа содержимого, но очевидно, что объединение больше не масштабируется.
Вариант использования 3:
Как утверждают авторы , рекомендация по стандарту ISO C ++ еще в 2008 году
Многие важные проблемные области требуют либо большого количества объектов, либо ограниченной памяти
Ресурсы. В этих ситуациях сохранение пространства очень важно, и объединение часто является идеальным способом сделать это. Фактически, общий случай использования - это ситуация, когда профсоюз никогда не меняет своего активного члена в течение срока его службы. Его можно создавать, копировать и разрушать, как если бы это была структура, содержащая только один член. Типичным применением этого было бы создание гетерогенной коллекции несвязанных типов, которые не распределяются динамически (возможно, они создаются на месте на карте или являются элементами массива).
А теперь пример с диаграммой классов UML:
![many compositions for class A](https://i.stack.imgur.com/hyiAc.png)
Ситуация на простом английском языке: объект класса A может иметь объекты любого класса из B1, ..., Bn и не более одного из каждого типа с n будучи довольно большим числом, скажем, по крайней мере 10.
Мы не хотим добавлять поля (элементы данных) в A следующим образом:
private:
B1 b1;
.
.
.
Bn bn;
потому что n может отличаться (мы могли бы захотеть добавить классы Bx к смеси), и потому что это могло бы вызвать беспорядок с конструкторами и потому что объекты A занимали бы много места.
Мы могли бы использовать дурацкий контейнер из void*
указателей на Bx
объектов с приведениями для их извлечения, но это неправильно и так в стиле C ... но что более важно, это оставило бы нам время жизни многих динамически распределенныхобъекты для управления.
Вместо этого можно сделать следующее:
union Bee
{
B1 b1;
.
.
.
Bn bn;
};
enum BeesTypes { TYPE_B1, ..., TYPE_BN };
class A
{
private:
std::unordered_map<int, Bee> data; // C++11, otherwise use std::map
public:
Bee get(int); // the implementation is obvious: get from the unordered map
};
Затем, чтобы получить содержимое экземпляра объединения из data
, вы используете a.get(TYPE_B2).b2
илайки, где a
- это экземпляр класса A
.
Это тем более мощно, что профсоюзы не ограничены в C ++ 11.Подробнее см. документ, на который есть ссылка или в этой статье .