Uważam, że związki C ++ są całkiem fajne. Wydaje się, że ludzie zwykle myślą tylko o przypadku użycia, w którym chce się zmienić wartość instancji unii „na miejscu” (co, jak się wydaje, służy jedynie do oszczędzania pamięci lub wykonywania wątpliwych konwersji).
W rzeczywistości związki zawodowe mogą mieć wielką moc jako narzędzie inżynierii oprogramowania, nawet jeśli nigdy nie zmienisz wartości żadnego wystąpienia związku .
Przypadek użycia 1: kameleon
Dzięki uniom można przegrupować wiele dowolnych klas pod jednym nominałem, co nie jest pozbawione podobieństw w przypadku klasy bazowej i jej klas pochodnych. Zmiany jednak dotyczą tego, co można, a czego nie można zrobić z daną instancją unii:
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?
}
Wydaje się, że programista musi mieć pewność co do typu zawartości danej instancji unii, gdy chce z niej skorzystać. Tak jest w przypadku f
powyższej funkcji . Gdyby jednak funkcja otrzymała instancję unii jako przekazany argument, tak jak w przypadku g
powyższego, nie wiedziałaby, co z nią zrobić. To samo dotyczy funkcji zwracających instancję unii, zobacz h
: skąd wywołujący wie, co jest w środku?
Jeśli instancja unii nigdy nie zostanie przekazana jako argument lub jako wartość zwracana, to będzie miała bardzo monotonne życie, ze skokami ekscytacji, gdy programista zdecyduje się zmienić jej zawartość:
Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;
I to jest najbardziej (nie) popularny przypadek użycia związków. Innym przypadkiem użycia jest sytuacja, gdy instancja union pojawia się wraz z czymś, co mówi ci o jej typie.
Przykład zastosowania 2: „Miło cię poznać, jestem object
z Class
”
Załóżmy, że programista wybrany tak, aby zawsze łączyć instancję unii w parę z deskryptorem typu (pozostawię czytelnikowi swobodę wyobrażenia sobie implementacji dla jednego takiego obiektu). To niweczy cel samej unii, jeśli programista chce oszczędzać pamięć, a rozmiar deskryptora typu nie jest bez znaczenia w stosunku do rozmiaru unii. Załóżmy jednak, że kluczowe jest, aby instancja union mogła zostać przekazana jako argument lub jako wartość zwracana, gdy wywoływany lub wywołujący nie wie, co jest w środku.
Następnie programista musi napisać switch
instrukcję sterowania przepływem, aby odróżnić Bruce'a Wayne'a od drewnianego patyka lub czegoś równoważnego. Nie jest tak źle, gdy w związku są tylko dwa rodzaje treści, ale oczywiście związek już się nie skaluje.
Przypadek użycia 3:
Jak autorzy rekomendacji dla normy ISO C ++ sformułowali ją w 2008 roku,
Wiele ważnych domen problemowych wymaga dużej liczby obiektów lub ograniczonych zasobów pamięci. W takich sytuacjach zachowanie miejsca jest bardzo ważne, a związek jest często doskonałym sposobem na zrobienie tego. W rzeczywistości typowym przypadkiem użycia jest sytuacja, w której związek nigdy nie zmienia swojego aktywnego członka podczas swojego życia. Może być konstruowana, kopiowana i niszczona tak, jakby była strukturą zawierającą tylko jeden element. Typowym zastosowaniem tego byłoby utworzenie heterogenicznej kolekcji niepowiązanych typów, które nie są alokowane dynamicznie (być może są konstruowane lokalnie na mapie lub składowe tablicy).
A teraz przykład z diagramem klas UML:
Sytuacja w prostym języku angielskim: obiekt klasy A może mieć obiekty dowolnej klasy spośród B1, ..., Bn i co najwyżej po jednym każdego typu, gdzie n to całkiem duża liczba, powiedzmy co najmniej 10.
Nie chcemy dodawać pól (członków danych) do A w ten sposób:
private:
B1 b1;
.
.
.
Bn bn;
ponieważ n może się różnić (możemy chcieć dodać klasy Bx do miksu) i ponieważ spowodowałoby to bałagan z konstruktorami i ponieważ obiekty A zajmowałyby dużo miejsca.
Moglibyśmy użyć zwariowanego kontenera void*
wskaźników do Bx
obiektów z rzutami, aby je odzyskać, ale to jest niezłe i takie w stylu C ... ale co ważniejsze, dałoby nam to czas życia wielu dynamicznie przydzielonych obiektów do zarządzania.
Zamiast tego można zrobić tak:
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
};
Następnie, aby pobrać zawartość instancji unii data
, należy użyć a.get(TYPE_B2).b2
i polubień, gdzie a
jest A
instancja klasy .
Jest to tym bardziej wydajne, że związki są nieograniczone w C ++ 11. Zobacz dokument, do którego link znajduje się powyżej, lub ten artykuł, aby uzyskać szczegółowe informacje.