Nauczyłem się, ale tak naprawdę nie mam związków zawodowych. Każdy tekst w C lub C ++, przez który przechodzę, wprowadza je (czasami mimochodem), ale zwykle podają bardzo niewiele praktycznych przykładów, dlaczego i gdzie ich używać. Kiedy związki byłyby przydatne w nowoczesnym (lub nawet starszym) przypadku? Moje jedyne dwa przypuszczenia to programowanie mikroprocesorów, gdy masz bardzo ograniczoną przestrzeń do pracy lub gdy opracowujesz API (lub coś podobnego) i chcesz zmusić użytkownika końcowego do posiadania tylko jednej instancji kilku obiektów / typów w jeden raz. Czy te dwa przypuszczenia są choćby bliskie słuszności?
133
Odpowiedzi:
Związki są zwykle używane w towarzystwie dyskryminatora: zmiennej wskazującej, które z pól związku jest ważne. Na przykład, powiedzmy, że chcesz utworzyć własny typ wariantu :
Wtedy użyłbyś go na przykład:
W rzeczywistości jest to dość powszechny idiom, szczególnie w przypadku wewnętrznych elementów języka Visual Basic.
Prawdziwy przykład można znaleźć w unii SDL_Event firmy SDL . ( rzeczywisty kod źródłowy tutaj ). Na
type
górze unii znajduje się pole, które jest powtarzane w każdej strukturze zdarzenia SDL_ *. Następnie, aby obsłużyć prawidłowe zdarzenie, należy sprawdzić wartośćtype
pola.Korzyści są proste: istnieje jeden typ danych do obsługi wszystkich typów zdarzeń bez niepotrzebnego wykorzystywania pamięci.
źródło
struct object
w github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.cUważ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:
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 przypadkug
powyższego, nie wiedziałaby, co z nią zrobić. To samo dotyczy funkcji zwracających instancję unii, zobaczh
: 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ść:
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
zClass
”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,
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:
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 doBx
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:
Następnie, aby pobrać zawartość instancji unii
data
, należy użyća.get(TYPE_B2).b2
i polubień, gdziea
jestA
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.
źródło
Jednym z przykładów jest dziedzina osadzona, w której każdy bit rejestru może oznaczać coś innego. Na przykład suma 8-bitowej liczby całkowitej i struktury z 8 oddzielnymi 1-bitowymi polami bitowymi umożliwia zmianę jednego bitu lub całego bajtu.
źródło
void*
s lub masek i przesunięć.REG |= MASK
iREG &= ~MASK
. Jeśli jest to podatne na błędy, umieść je w#define SETBITS(reg, mask)
i#define CLRBITS(reg, mask)
. Nie polegaj na kompilatorze, aby uzyskać bity w określonej kolejności ( stackoverflow.com/questions/1490092/ ... )Herb Sutter napisał w GOTW około sześć lat temu, z podkreśleniem :
A dla mniej użytecznego przykładu, zobacz długie, ale niejednoznaczne pytanie gcc, ścisłe aliasing i rzutowanie przez union .
źródło
Cóż, jeden przykład użycia, który przychodzi mi do głowy, to:
Następnie można uzyskać dostęp do 8-bitowych oddzielnych części tego 32-bitowego bloku danych; jednak przygotuj się na potencjalne ugryzienie przez endianię.
To tylko jeden hipotetyczny przykład, ale ilekroć chcesz podzielić dane w polu na takie części składowe, możesz użyć unii.
To powiedziawszy, istnieje również metoda, która jest bezpieczna dla endian:
Na przykład, ponieważ ta operacja binarna zostanie przekonwertowana przez kompilator na poprawny endianness.
źródło
Niektóre zastosowania dla związków:
Oszczędność miejsca w pamięci, gdy pola są zależne od pewnych wartości:
Grep pliki dołączane do użytku z kompilatorem. Znajdziesz dziesiątki, a nawet setki zastosowań
union
:źródło
Związki są przydatne w przypadku danych na poziomie bajtów (niskiego poziomu).
Jednym z moich ostatnich zastosowań było modelowanie adresów IP, które wygląda jak poniżej:
źródło
Przykład, gdy użyłem unii:
umożliwia mi to dostęp do moich danych w postaci tablicy lub elementów.
Użyłem związku, aby różne terminy wskazywały na tę samą wartość. Podczas przetwarzania obrazu, niezależnie od tego, czy pracowałem na kolumnach, szerokości czy rozmiarze w kierunku X, może to być mylące. Aby rozwiązać ten problem, używam unii, więc wiem, które opisy pasują do siebie.
źródło
Związki zapewniają polimorfizm w C.
źródło
void*
zrobiłem to ^^Genialnym zastosowaniem unii jest wyrównanie pamięci, które znalazłem w kodzie źródłowym PCL (Point Cloud Library). Pojedyncza struktura danych w API może być ukierunkowana na dwie architektury: procesor z obsługą SSE oraz procesor bez obsługi SSE. Na przykład: struktura danych dla PointXYZ to
3 pływaki są wyściełane dodatkowym pływakiem do wyrównania SSE. Więc dla
Użytkownik może uzyskać dostęp do point.data [0] lub point.x (w zależności od obsługi SSE) w celu uzyskania dostępu, powiedzmy, współrzędnej x. Więcej podobnych, lepszych szczegółów użytkowania znajduje się pod następującym linkiem: Dokumentacja PCL Typy PointT
źródło
Słowo
union
kluczowe, choć nadal używane w C ++ 03 1 , jest w większości pozostałością po czasach C. Najbardziej rażącym problemem jest to, że działa tylko z POD 1 .Idea unii jest jednak nadal obecna i rzeczywiście biblioteki Boost zawierają klasę podobną do unii:
Który ma większość zalet
union
(jeśli nie wszystkie) i dodaje:W praktyce wykazano, że jest to równoważne kombinacji
union
+enum
, i porównano, że jest tak samo szybkie (chociażboost::any
jest bardziej prawdopodobnedynamic_cast
, ponieważ wykorzystuje RTTI).1 Unions zostały zaktualizowane w C ++ 11 ( nieograniczone związki ) i mogą teraz zawierać obiekty z destruktorami, chociaż użytkownik musi ręcznie wywołać destruktor (na aktualnie aktywnym składniku unii). Wciąż dużo łatwiej jest używać wariantów.
źródło
boost::variant
Próbować używać ich samodzielnie. Wokół związków zawodowych jest zbyt wiele nieokreślonych zachowań, więc szanse na to, że uda ci się zrobić dobrze, są ogromne.Z artykułu w Wikipedii o związkach :
źródło
We wczesnych dniach C (np. Jak udokumentowano w 1974 r.), Wszystkie struktury miały wspólną przestrzeń nazw dla swoich członków. Nazwa każdego członka była skojarzona z typem i przesunięciem; jeśli "wd_woozle" było "int" na przesunięciu 12, to mając wskaźnik
p
dowolnego typu struktury,p->wd_woozle
byłoby równoważne*(int*)(((char*)p)+12)
. Język wymagał, aby wszyscy członkowie wszystkich typów struktur mieli unikalne nazwy, z wyjątkiem tego, że jawnie zezwalał na ponowne użycie nazw członków w przypadkach, gdy każda struktura, w której zostały użyte, traktowała je jako wspólną sekwencję początkową.Fakt, że typy struktur można było swobodnie stosować, umożliwił zachowanie struktur tak, jakby zawierały nachodzące na siebie pola. Na przykład podane definicje:
kod mógłby zadeklarować strukturę typu „float1”, a następnie użyć „składowych” b0 ... b3, aby uzyskać dostęp do poszczególnych bajtów w niej zawartych. Gdy język został zmieniony tak, aby każda struktura otrzymywała oddzielną przestrzeń nazw dla swoich członków, kod, który opierał się na możliwości dostępu do rzeczy na wiele sposobów, uległby awarii. Wartości wydzielenia przestrzeni nazw dla różnych typów struktur były wystarczające, aby wymagać zmiany takiego kodu, aby go dostosować, ale wartość takich technik była wystarczająca, aby uzasadnić rozszerzenie języka, aby nadal go wspierać.
Kod, który został napisany, aby wykorzystać możliwość dostępu do magazynowania w
struct float1
jakby to byłastruct byte4
może być wykonany do pracy w nowym języku, dodając oświadczenie:union f1b4 { struct float1 ff; struct byte4 bb; };
deklarując przedmioty jako typunion f1b4;
, a niestruct float1
, i zastąpienie dostępy dof0
,b0
,b1
, etc . zff.f0
,bb.b0
,bb.b1
, itd. Chociaż istnieją lepsze sposoby taki kod mógł być wspierany Theunion
podejście było przynajmniej w pewnym stopniu wykonalne, przynajmniej w interpretacji C89-era zasad aliasingu.źródło
Powiedzmy, że masz n różnych typów konfiguracji (po prostu będąc zbiorem zmiennych definiujących parametry). Używając wyliczenia typów konfiguracji, można zdefiniować strukturę, która ma identyfikator typu konfiguracji, wraz z sumą wszystkich różnych typów konfiguracji.
W ten sposób, gdziekolwiek przekażesz konfigurację, możesz użyć identyfikatora do określenia sposobu interpretacji danych konfiguracyjnych, ale gdyby konfiguracje były ogromne, nie byłbyś zmuszony do tworzenia równoległych struktur dla każdego potencjalnego typu marnowania przestrzeni.
źródło
Niedawny impuls do, już podwyższonego znaczenia związków , dał rygorystyczna reguła aliasingu wprowadzona w najnowszej wersji standardu C.
Możesz użyć związków zrobić do pisania na klawiaturze bez naruszania standardu C.
Ten program ma nieokreślone zachowanie (ponieważ założyłem to
float
iunsigned int
ma taką samą długość), ale nie ma nieokreślonego zachowania (patrz tutaj ).źródło
Chciałbym dodać jeden dobry praktyczny przykład użycia sumy - implementacja kalkulatora / interpretera formuł lub użycie jakiegoś jej rodzaju w obliczeniach (na przykład chcesz używać modyfikowalnych w czasie wykonywania części twoich formuł obliczeniowych - rozwiązywanie równania numerycznie - po prostu na przykład). Możesz więc chcieć zdefiniować liczby / stałe różnych typów (liczby całkowite, zmiennoprzecinkowe, a nawet zespolone) w następujący sposób:
Więc oszczędzasz pamięć i co ważniejsze - unikasz wszelkich dynamicznych alokacji dla prawdopodobnie ekstremalnych ilości (jeśli używasz wielu liczb zdefiniowanych w czasie wykonywania) małych obiektów (w porównaniu do implementacji poprzez dziedziczenie klas / polimorfizm). Ale co ciekawsze, nadal możesz korzystać z potęgi polimorfizmu C ++ (na przykład jeśli jesteś fanem podwójnego wysyłania;) z tego typu strukturami. Po prostu dodaj "dummy" wskaźnik interfejsu do klasy nadrzędnej wszystkich typów liczb jako pole tej struktury, wskazując na tę instancję zamiast / oprócz typu surowego, lub użyj starych dobrych wskaźników funkcji C.
więc możesz użyć polimorfizmu zamiast sprawdzania typów z przełącznikiem (typ) - z implementacją wydajną pod kątem pamięci (bez dynamicznej alokacji małych obiektów) - oczywiście, jeśli tego potrzebujesz.
źródło
Od http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :
źródło
Związki umożliwiają manipulowanie różnymi rodzajami danych w jednym obszarze pamięci bez osadzania w programie jakichkolwiek informacji niezależnych od maszyny Są analogiczne do wariantów rekordów w pascalu
Jako przykład, taki jak można znaleźć w menedżerze tablicy symboli kompilatora, załóżmy, że stała może być int, float lub wskaźnik znakowy. Wartość określonej stałej musi być przechowywana w zmiennej odpowiedniego typu, jednak do zarządzania tabelą najwygodniej jest, jeśli zajmuje taką samą ilość pamięci i jest przechowywana w tym samym miejscu niezależnie od jej typu. Taki jest cel unii - pojedynczej zmiennej, która może legalnie zawierać jeden z kilku typów. Składnia oparta jest na strukturach:
Zmienna u będzie wystarczająco duża, aby pomieścić największy z trzech typów; konkretny rozmiar zależy od implementacji. Każdy z tych typów może być przypisany do u, a następnie używany w wyrażeniach, o ile użycie jest spójne
źródło