Ich habe gelernt, bekomme aber keine Gewerkschaften. Jeder C- oder C ++ - Text, den ich durchlaufe, führt sie ein (manchmal im Vorbeigehen), aber sie geben in der Regel nur sehr wenige praktische Beispiele dafür, warum oder wo sie verwendet werden sollen. Wann wären Gewerkschaften in einem modernen (oder sogar alten) Fall nützlich? Meine einzigen beiden Vermutungen wären das Programmieren von Mikroprozessoren, wenn Sie nur sehr wenig Platz zum Arbeiten haben oder wenn Sie eine API (oder ähnliches) entwickeln und den Endbenutzer dazu zwingen möchten, nur eine Instanz mehrerer Objekte / Typen zu haben einmal. Sind diese beiden Vermutungen überhaupt richtig?
133
Antworten:
Gewerkschaften werden normalerweise mit der Gesellschaft eines Diskriminators verwendet: eine Variable, die angibt, welches der Felder der Gewerkschaft gültig ist. Angenommen, Sie möchten eine eigene Variante erstellen :
Dann würden Sie es verwenden wie:
Dies ist eigentlich eine ziemlich verbreitete Redewendung, insbesondere bei Visual Basic-Interna.
Ein reales Beispiel finden Sie in der SDL_Event-Union von SDL . ( aktueller Quellcode hier ). Da ist ein
type
oberen Rand der Union Feld, und dasselbe Feld wird in jeder SDL_ * -Ereignisstruktur wiederholt. Um das richtige Ereignis zu behandeln, müssen Sie den Wert destype
Felds überprüfen .Die Vorteile sind einfach: Es gibt einen einzigen Datentyp, mit dem alle Ereignistypen ohne unnötigen Speicher verarbeitet werden können.
quelle
struct object
in github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.cIch finde C ++ - Gewerkschaften ziemlich cool. Es scheint, dass die Leute normalerweise nur an den Anwendungsfall denken, in dem man den Wert einer Union-Instanz "an Ort und Stelle" ändern möchte (was anscheinend nur dazu dient, Speicherplatz zu sparen oder zweifelhafte Konvertierungen durchzuführen).
In der Tat können Gewerkschaften als Software-Engineering-Tool von großer Bedeutung sein, selbst wenn Sie den Wert einer Gewerkschaftsinstanz niemals ändern .
Anwendungsfall 1: das Chamäleon
Mit Gewerkschaften können Sie eine Reihe beliebiger Klassen unter einer Bezeichnung zusammenfassen, was nicht ohne Ähnlichkeiten mit dem Fall einer Basisklasse und ihren abgeleiteten Klassen ist. Was sich jedoch ändert, ist, was Sie mit einer bestimmten Union-Instanz tun können und was nicht:
Es scheint, dass der Programmierer sicher sein muss, welche Art von Inhalt eine bestimmte Union-Instanz hat, wenn er sie verwenden möchte. Dies ist in der
f
obigen Funktion der Fall . Wenn eine Funktion jedoch eine Union-Instanz als übergebenes Argument erhalten würde, wie diesg
oben der Fall ist , weiß sie nicht, was sie damit tun soll. Gleiches gilt für Funktionen, die eine Union-Instanz zurückgeben, sieheh
: Woher weiß der Aufrufer, was sich darin befindet?Wenn eine Union-Instanz niemals als Argument oder als Rückgabewert übergeben wird, hat sie zwangsläufig ein sehr eintöniges Leben mit Aufregung, wenn der Programmierer seinen Inhalt ändert:
Und das ist der (un) beliebteste Anwendungsfall von Gewerkschaften. Ein weiterer Anwendungsfall ist, wenn eine Union-Instanz etwas enthält, das Ihnen den Typ angibt.
Anwendungsfall 2: "Schön, Sie kennenzulernen, ich bin
object
vonClass
"Angenommen, ein Programmierer hat sich dafür entschieden, eine Union-Instanz immer mit einem Typdeskriptor zu koppeln (ich überlasse es dem Ermessen des Lesers, sich eine Implementierung für ein solches Objekt vorzustellen). Dies macht den Zweck der Union selbst zunichte, wenn der Programmierer Speicher sparen möchte und die Größe des Typdeskriptors in Bezug auf die der Union nicht vernachlässigbar ist. Nehmen wir jedoch an, dass es entscheidend ist, dass die Union-Instanz als Argument oder als Rückgabewert übergeben wird, wenn der Angerufene oder Anrufer nicht weiß, was sich darin befindet.
Dann muss der Programmierer a schreiben
switch
Kontrollflussanweisung , um Bruce Wayne von einem Holzstab oder etwas Ähnlichem zu unterscheiden. Es ist nicht schlecht, wenn es nur zwei Arten von Inhalten in der Union gibt, aber offensichtlich skaliert die Union nicht mehr.Anwendungsfall 3:
Wie die Autoren einer Empfehlung für den ISO C ++ - Standard bereits 2008 formulierten,
Und nun ein Beispiel mit einem UML-Klassendiagramm:
Die Situation im Klartext: Ein Objekt der Klasse A kann Objekte jeder Klasse unter B1, ..., Bn und höchstens eines von jedem Typ haben, wobei n eine ziemlich große Zahl ist, sagen wir mindestens 10.
Wir möchten A keine Felder (Datenelemente) hinzufügen, wie folgt:
weil n variieren kann (wir möchten möglicherweise Bx-Klassen zum Mix hinzufügen) und weil dies ein Durcheinander mit Konstruktoren verursachen würde und weil A-Objekte viel Platz beanspruchen würden.
Wir könnten einen verrückten Container mit
void*
Zeigern aufBx
Objekte mit Casts verwenden, um sie abzurufen, aber das ist flüchtig und so im C-Stil ... aber was noch wichtiger ist, das würde uns die Lebensdauer vieler dynamisch zugeordneter Objekte überlassen, die verwaltet werden müssen.Stattdessen kann Folgendes getan werden:
Um den Inhalt einer Union-Instanz abzurufen
data
, verwenden Siea.get(TYPE_B2).b2
und dergleichen, wobeia
sich eine Klasseninstanz befindetA
.Dies ist umso leistungsfähiger, als die Gewerkschaften in C ++ 11 uneingeschränkt sind. Weitere Informationen finden Sie im oben verlinkten Dokument oder in diesem Artikel .
quelle
Ein Beispiel ist der eingebettete Bereich, in dem jedes Bit eines Registers etwas anderes bedeuten kann. Beispielsweise können Sie durch die Vereinigung einer 8-Bit-Ganzzahl und einer Struktur mit 8 separaten 1-Bit-Bitfeldern entweder ein Bit oder das gesamte Byte ändern.
quelle
void*
s oder Masken und Verschiebungen beinhalten.REG |= MASK
undREG &= ~MASK
. Wenn das fehleranfällig ist, setzen Sie sie in ein#define SETBITS(reg, mask)
und#define CLRBITS(reg, mask)
. Verlassen Sie sich nicht darauf, dass der Compiler die Bits in einer bestimmten Reihenfolge abruft ( stackoverflow.com/questions/1490092/… )Herb Sutter schrieb vor ungefähr sechs Jahren in GOTW , mit Schwerpunkt :
Ein weniger nützliches Beispiel finden Sie in der langen, aber nicht schlüssigen Frage gcc, striktes Aliasing und Casting durch eine Gewerkschaft .
quelle
Ein Anwendungsfall, den ich mir vorstellen kann, ist folgender:
Sie können dann auf die separaten 8-Bit-Teile dieses 32-Bit-Datenblocks zugreifen. Bereiten Sie sich jedoch darauf vor, möglicherweise von Endianness gebissen zu werden.
Dies ist nur ein hypothetisches Beispiel. Wenn Sie jedoch Daten in einem Feld in solche Komponenten aufteilen möchten, können Sie eine Union verwenden.
Es gibt jedoch auch eine Methode, die endian-sicher ist:
Zum Beispiel, da diese binäre Operation vom Compiler in die richtige Endianness konvertiert wird.
quelle
Einige Verwendungszwecke für Gewerkschaften:
Speicherplatz sparen, wenn Felder von bestimmten Werten abhängig sind:
Grep die Include-Dateien zur Verwendung mit deinem Compiler. Sie finden Dutzende bis Hunderte von Anwendungen von
union
:quelle
Gewerkschaften sind nützlich, wenn Sie mit Daten auf Byte-Ebene (niedriger Ebene) arbeiten.
Eine meiner jüngsten Anwendungen war die Modellierung von IP-Adressen, die wie folgt aussieht:
quelle
Ein Beispiel, wenn ich eine Gewerkschaft verwendet habe:
Dadurch kann ich als Array oder als Elemente auf meine Daten zugreifen.
Ich habe eine Union verwendet, damit die verschiedenen Begriffe auf denselben Wert verweisen. Bei der Bildverarbeitung kann es verwirrend werden, ob ich an Spalten oder der Breite oder der Größe in X-Richtung gearbeitet habe. Um dieses Problem zu lösen, verwende ich eine Gewerkschaft, damit ich weiß, welche Beschreibungen zusammenpassen.
quelle
Gewerkschaften sorgen für Polymorphismus in C.
quelle
void*
getan ^^Eine brillante Verwendung von Union ist die Speicherausrichtung, die ich im PCL-Quellcode (Point Cloud Library) gefunden habe. Die einzelne Datenstruktur in der API kann auf zwei Architekturen abzielen: CPU mit SSE-Unterstützung sowie CPU ohne SSE-Unterstützung. Zum Beispiel: Die Datenstruktur für PointXYZ ist
Die 3 Schwimmer sind mit einem zusätzlichen Schwimmer für die SSE-Ausrichtung gepolstert. So für
Der Benutzer kann entweder auf point.data [0] oder point.x (abhängig von der SSE-Unterstützung) zugreifen, um beispielsweise auf die x-Koordinate zuzugreifen. Weitere ähnliche Details zur besseren Verwendung finden Sie unter folgendem Link: PCL-Dokumentation PointT-Typen
quelle
Das
union
Schlüsselwort, das noch in C ++ 03 1 verwendet wird , ist größtenteils ein Rest der C-Tage. Das auffälligste Problem ist, dass es nur mit POD 1 funktioniert .Die Idee der Gewerkschaft ist jedoch immer noch vorhanden, und tatsächlich verfügen die Boost-Bibliotheken über eine gewerkschaftsähnliche Klasse:
Welches hat die meisten Vorteile der
union
(wenn nicht alle) und fügt hinzu:In der Praxis wurde gezeigt, dass es einer Kombination von
union
+ entsprichtenum
, und es wurde ein Benchmark durchgeführt, dass es genauso schnell war (währendboost::any
es mehr zum Bereich von gehörtdynamic_cast
, da es RTTI verwendet).1 Gewerkschaften wurden in C ++ 11 ( uneingeschränkte Gewerkschaften ) aktualisiert und können jetzt Objekte mit Destruktoren enthalten, obwohl der Benutzer den Destruktor manuell aufrufen muss (für das derzeit aktive Gewerkschaftsmitglied). Es ist immer noch viel einfacher, Varianten zu verwenden.
quelle
boost::variant
als zu versuchen , Gewerkschaften zu verwenden , um auf eigene Faust. Es gibt viel zu viel undefiniertes Verhalten in Bezug auf Gewerkschaften, als dass Ihre Chancen, es richtig zu machen, miserabel sind.Aus dem Wikipedia-Artikel über Gewerkschaften :
quelle
In den frühesten Tagen von C (z. B. wie 1974 dokumentiert) hatten alle Strukturen einen gemeinsamen Namespace für ihre Mitglieder. Jeder Mitgliedsname war einem Typ und einem Offset zugeordnet. Wenn "wd_woozle" ein "int" bei Offset 12 wäre
p
,p->wd_woozle
wäre ein Zeiger eines beliebigen Strukturtyps äquivalent zu*(int*)(((char*)p)+12)
. Die Sprache erforderte, dass alle Mitglieder aller Strukturtypen eindeutige Namen haben, mit der Ausnahme, dass die Wiederverwendung von Mitgliedsnamen ausdrücklich zulässig war, wenn jede Struktur, in der sie verwendet wurden, sie als gemeinsame Anfangssequenz behandelte.Die Tatsache, dass Strukturtypen promisku verwendet werden konnten, ermöglichte es, dass sich Strukturen so verhalten, als ob sie überlappende Felder enthalten. Zum Beispiel gegebene Definitionen:
Code könnte eine Struktur vom Typ "float1" deklarieren und dann "Mitglieder" b0 ... b3 verwenden, um auf die einzelnen Bytes darin zuzugreifen. Wenn die Sprache so geändert wurde, dass jede Struktur einen separaten Namespace für ihre Mitglieder erhielt, brach Code, der auf der Fähigkeit beruhte, auf verschiedene Arten auf Dinge zuzugreifen, zusammen. Die Werte zum Trennen von Namespaces für verschiedene Strukturtypen reichten aus, um eine Änderung des Codes zu erfordern, aber der Wert solcher Techniken reichte aus, um eine Erweiterung der Sprache zu rechtfertigen, um sie weiterhin zu unterstützen.
Code, der geschrieben wurde, um die Fähigkeit zu nutzen , um die Speicherung innerhalb einer zuzugreifen ,
struct float1
als ob es sich um eine wurdenstruct byte4
durch Zugabe einer Erklärung an die Arbeit in der neuen Sprache gemacht werden könnten:union f1b4 { struct float1 ff; struct byte4 bb; };
, Objekte als Typ deklarierenunion f1b4;
stattstruct float1
und Ersetzen Zugriffen auff0
,b0
,b1
, usw. mit.ff.f0
,bb.b0
,bb.b1
usw. Zwar gibt es bessere Möglichkeiten , einen solchen Code sind , haben unterstützt werden können, dieunion
war Ansatz zumindest etwas bearbeitbar, zumindest mit C89-Ära Interpretationen der Aliasing - Regeln.quelle
Nehmen wir an, Sie haben n verschiedene Arten von Konfigurationen (nur eine Reihe von Variablen, die Parameter definieren). Mithilfe einer Aufzählung der Konfigurationstypen können Sie eine Struktur definieren, die die ID des Konfigurationstyps sowie eine Vereinigung aller verschiedenen Konfigurationstypen enthält.
Auf diese Weise können Sie überall dort, wo Sie die Konfiguration übergeben, anhand der ID bestimmen, wie die Konfigurationsdaten interpretiert werden sollen. Wenn die Konfigurationen jedoch sehr groß wären, müssten Sie nicht für jeden potenziellen Typ parallele Strukturen haben, die Speicherplatz verschwenden.
quelle
Ein jüngster Schub für die bereits erhöhte Bedeutung der Gewerkschaften wurde durch die in der jüngsten Version des C-Standards eingeführte strikte Aliasing-Regel gegeben .
Sie können Gewerkschaften verwenden, um zu tippen, ohne den C-Standard zu verletzen.
Dieses Programm hat ein nicht spezifiziertes Verhalten (weil ich das angenommen habe
float
undunsigned int
die gleiche Länge habe), aber kein undefiniertes Verhalten (siehe hier ).quelle
Ich möchte ein gutes praktisches Beispiel für die Verwendung von union hinzufügen - das Implementieren eines Formelrechners / -interpreten oder das Verwenden einer Art davon bei der Berechnung (zum Beispiel möchten Sie modifizierbare Teile Ihrer Computerformeln zur Laufzeit verwenden - Gleichungen numerisch lösen - einfach beispielsweise). Daher möchten Sie möglicherweise Zahlen / Konstanten verschiedener Typen (Ganzzahlen, Gleitkommazahlen, sogar komplexe Zahlen) wie folgt definieren:
Sie sparen also Speicher und was noch wichtiger ist: Sie vermeiden dynamische Zuweisungen für wahrscheinlich extreme Mengen (wenn Sie viele zur Laufzeit definierte Zahlen verwenden) kleiner Objekte (im Vergleich zu Implementierungen durch Klassenvererbung / Polymorphismus). Interessanter ist jedoch, dass Sie mit dieser Art von Struktur immer noch die Kraft des C ++ - Polymorphismus nutzen können (wenn Sie beispielsweise ein Fan von Double Dispatching sind;). Fügen Sie einfach einen "Dummy" -Schnittstellenzeiger zur übergeordneten Klasse aller Zahlentypen als Feld dieser Struktur hinzu und zeigen Sie auf diese Instanz anstelle von / zusätzlich zum Rohtyp, oder verwenden Sie gute alte C-Funktionszeiger.
Sie können also Polymorphismus anstelle von Typprüfungen mit switch (type) verwenden - mit speichereffizienter Implementierung (keine dynamische Zuordnung kleiner Objekte) - wenn Sie dies benötigen.
quelle
Von http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :
quelle
Gewerkschaften bieten die Möglichkeit, verschiedene Arten von Daten in einem einzigen Speicherbereich zu bearbeiten, ohne maschinenunabhängige Informationen in das Programm einzubetten. Sie sind analog zu Variantendatensätzen in Pascal
Nehmen wir als Beispiel an, wie es in einem Compiler-Symboltabellenmanager zu finden ist, dass eine Konstante ein int, ein float oder ein Zeichenzeiger sein kann. Der Wert einer bestimmten Konstante muss in einer Variablen des richtigen Typs gespeichert werden. Für die Tabellenverwaltung ist es jedoch am bequemsten, wenn der Wert dieselbe Speichermenge belegt und unabhängig von seinem Typ an derselben Stelle gespeichert wird. Dies ist der Zweck einer Union - eine einzelne Variable, die einen von mehreren Typen rechtmäßig enthalten kann. Die Syntax basiert auf Strukturen:
Die Variable u ist groß genug, um den größten der drei Typen aufzunehmen. Die spezifische Größe ist implementierungsabhängig. Jeder dieser Typen kann u zugewiesen und dann in Ausdrücken verwendet werden, solange die Verwendung konsistent ist
quelle