Я нахожу профсоюзы 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:
Ситуация на простом английском языке: у объекта класса 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. См. Документ, связанный с выше или этой статьей для деталей.