Я вважаю, що спілки 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
об’єкти з кастами, щоб отримати їх, але це нерозумно і так у стилі С ... але ще важливіше, що залишило б нам життя багатьох динамічно виділених об'єктів для управління.
Натомість можна зробити це:
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. Докладні відомості див. У документі, пов’язаному вище або у цій статті .