Wann würde jemand eine Gewerkschaft benutzen? Ist es ein Überbleibsel aus den Tagen nur mit C?


133

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?


31
C / C ++ ist keine Sprache. Gewerkschaften sind in C mäßig nützlich und in C ++ weitgehend nutzlos. Es wäre richtig zu sagen, dass sie in C ++ ein "Überrest von C ++ sind, der auf C basiert", aber nicht zu sagen, dass sie "ein Überrest von C nur Tagen" sind, als ob C ++ C.
R .. GitHub ersetzt Hör auf, dem Eis am

12
Können Sie näher erläutern, was C ++ als Ersatz für Gewerkschaften darstellt oder warum sie in C ++ unbrauchbar sind?
Russel

3
C ++ ersetzt Gewerkschaften durch Klassen und Vererbung - Gewerkschaften in C werden fast ausschließlich für typsicheren Polymorphismus verwendet. Etwas, in dem Klassen viel besser sind. (Siehe die Antwort von vz0 für Polymorphismus im C-Stil)
tobyodavies

6
@R ..: Union sind in C ++ immer noch mäßig nützlich. Siehe Antworten unten.
Michael

2
Gewerkschaften können in den Eingeweiden eines Betriebssystems oder beispielsweise in einem Paket, das Audiodateien zusammenstellt / zerlegt, außerordentlich wertvoll sein. In solchen Kontexten werden sie auf verschiedene Arten verwendet - Daten- / Endian-Konvertierung, Low-Level-Polymorphismus et al. Ja, es gibt andere Lösungen für das gleiche Problem (hauptsächlich das Wechseln zwischen Zeigertypen), aber Gewerkschaften sind oft sauberer und besser selbstdokumentierend.
Hot Licks

Antworten:


105

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 :

struct my_variant_t {
    int type;
    union {
        char char_value;
        short short_value;
        int int_value;
        long long_value;
        float float_value;
        double double_value;
        void* ptr_value;
    };
};

Dann würden Sie es verwenden wie:

/* construct a new float variant instance */
void init_float(struct my_variant_t* v, float initial_value) {
    v->type = VAR_FLOAT;
    v->float_value = initial_value;
}

/* Increments the value of the variant by the given int */
void inc_variant_by_int(struct my_variant_t* v, int n) {
    switch (v->type) {
    case VAR_FLOAT:
        v->float_value += n;
        break;

    case VAR_INT:
        v->int_value += n;
        break;
    ...
    }
}

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 eintype 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 des typeFelds überprüfen .

Die Vorteile sind einfach: Es gibt einen einzigen Datentyp, mit dem alle Ereignistypen ohne unnötigen Speicher verarbeitet werden können.


2
Toll! In diesem Fall frage ich mich jetzt, warum die Sdl-Funktion nicht nur als Klassenhierarchie implementiert wurde. Soll das C-kompatibel sein und nicht nur C ++?
Russel

12
@Russel C ++ - Klassen können nicht von einem C-Programm aus verwendet werden, aber C-Strukturen / -Unionen können mithilfe eines 'externen "C" -Blocks leicht von C ++ aus aufgerufen werden.
VZ0

1
Dieses Variantenmuster wird auch häufig für Programmiersprachendolmetscher verwendet, z. B. die Definition von struct objectin github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield

1
Tolle Erklärung. Ich wusste immer, was Gewerkschaften sind, habe aber nie einen realen Grund dafür gesehen, warum jemand verrückt genug wäre, sie zu benutzen :) Danke für das Beispiel.
Riwalk

@ Stargazer712, Googles Codesuche: google.com/…
kagali-san

87

Ich 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:

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?
}

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 fobigen Funktion der Fall . Wenn eine Funktion jedoch eine Union-Instanz als übergebenes Argument erhalten würde, wie dies goben 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:

Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;

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 objectvon Class"

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,

Viele wichtige Problemdomänen erfordern entweder eine große Anzahl von Objekten oder begrenzte Speicherressourcen. In diesen Situationen ist es sehr wichtig, Platz zu sparen, und eine Gewerkschaft ist oft der perfekte Weg, dies zu tun. In der Tat ist ein häufiger Anwendungsfall die Situation, in der eine Gewerkschaft während ihres Lebens niemals ihr aktives Mitglied wechselt. Es kann so konstruiert, kopiert und zerstört werden, als wäre es eine Struktur, die nur ein Mitglied enthält. Eine typische Anwendung hierfür wäre die Erstellung einer heterogenen Sammlung nicht verwandter Typen, die nicht dynamisch zugewiesen werden (möglicherweise sind sie direkt in einer Karte oder in Mitgliedern eines Arrays erstellt).

Und nun ein Beispiel mit einem UML-Klassendiagramm:

viele Kompositionen für Klasse A.

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:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

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 auf BxObjekte 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:

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
};

Um den Inhalt einer Union-Instanz abzurufen data, verwenden Sie a.get(TYPE_B2).b2und dergleichen, wobei asich eine Klasseninstanz befindet A.

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 .


Dies war sehr hilfreich und die Reihe des zweiten Artikels war sehr informativ. Vielen Dank.
Andrew

38

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.


7
Dies ist auch bei Gerätetreibern sehr häufig. Vor ein paar Jahren habe ich mit solchen Gewerkschaften viel Code für ein Projekt geschrieben. Es wird normalerweise nicht empfohlen und kann in einigen Fällen compilerspezifisch sein, funktioniert aber.
Thkala

11
Ich würde das nicht "nicht empfohlen" nennen. Im eingebetteten Raum ist es oft viel sauberer und weniger fehleranfällig als die Alternativen, die normalerweise entweder viele explizite Besetzungen und void*s oder Masken und Verschiebungen beinhalten.
bta

heh? Viele explizite Casts? Scheint mir einfache Aussagen wie REG |= MASKund REG &= ~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/… )
Michael

26

Herb Sutter schrieb vor ungefähr sechs Jahren in GOTW , mit Schwerpunkt :

"Aber denken Sie nicht, dass Gewerkschaften nur ein Überbleibsel aus früheren Zeiten sind. Gewerkschaften sind vielleicht am nützlichsten, um Platz zu sparen, indem sie Daten überlappen lassen, und dies ist in C ++ und in der heutigen modernen Welt immer noch wünschenswert . Zum Beispiel einige der meisten fortgeschrittenes C ++Standardbibliotheksimplementierungen in der Welt verwenden jetzt genau diese Technik, um die "Optimierung kleiner Zeichenfolgen" zu implementieren, eine großartige Optimierungsalternative, die den Speicher innerhalb eines Zeichenfolgenobjekts selbst wiederverwendet: Bei großen Zeichenfolgen speichert der Speicherplatz innerhalb des Zeichenfolgenobjekts den üblichen Zeiger auf die dynamische zugewiesene Puffer- und Reinigungsinformationen wie die Größe des Puffers; Bei kleinen Zeichenfolgen wird stattdessen derselbe Speicherplatz wiederverwendet, um den Inhalt der Zeichenfolge direkt zu speichern und eine dynamische Speicherzuweisung vollständig zu vermeiden. Weitere Informationen zur Optimierung kleiner Zeichenfolgen (und zu anderen Optimierungen und Pessimierungen von Zeichenfolgen in beträchtlicher Tiefe) finden Sie unter .... "

Ein weniger nützliches Beispiel finden Sie in der langen, aber nicht schlüssigen Frage gcc, striktes Aliasing und Casting durch eine Gewerkschaft .


23

Ein Anwendungsfall, den ich mir vorstellen kann, ist folgender:

typedef union
{
    struct
    {
        uint8_t a;
        uint8_t b;
        uint8_t c;
        uint8_t d;
    };
    uint32_t x;
} some32bittype;

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:

uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;

Zum Beispiel, da diese binäre Operation vom Compiler in die richtige Endianness konvertiert wird.


Ich denke, die Frage ist am besten zu beantworten, wann man Gewerkschaften einsetzen sollte. Sie haben eine Antwort gegeben, wo eine Gewerkschaft nicht das richtige Werkzeug ist, was meiner Meinung nach in dieser Antwort klarer gemacht werden sollte.
Michael

15

Einige Verwendungszwecke für Gewerkschaften:

  • Bereitstellung einer allgemeinen Endianness-Schnittstelle für einen unbekannten externen Host.
  • Bearbeiten Sie Gleitkommadaten der fremden CPU-Architektur, z. B. das Akzeptieren von VAX G_FLOATS von einer Netzwerkverbindung und das Konvertieren dieser zur Verarbeitung in IEEE 754 Long Reals .
  • Bieten Sie einen einfachen Bit-Twiddling-Zugriff auf einen übergeordneten Typ.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

Mit dieser Deklaration ist es einfach, die Hex-Byte-Werte von a anzuzeigen, das long doubleVorzeichen des Exponenten zu ändern, festzustellen, ob es sich um einen Denormalwert handelt, oder eine lange Doppelarithmetik für eine CPU zu implementieren, die dies nicht unterstützt usw.

  • Speicherplatz sparen, wenn Felder von bestimmten Werten abhängig sind:

    class person {  
        string name;  
    
        char gender;   // M = male, F = female, O = other  
        union {  
            date  vasectomized;  // for males  
            int   pregnancies;   // for females  
        } gender_specific_data;
    }
  • Grep die Include-Dateien zur Verwendung mit deinem Compiler. Sie finden Dutzende bis Hunderte von Anwendungen von union:

    [wally@zenetfedora ~]$ cd /usr/include
    [wally@zenetfedora include]$ grep -w union *
    a.out.h:  union
    argp.h:   parsing options, getopt is called with the union of all the argp
    bfd.h:  union
    bfd.h:  union
    bfd.h:union internal_auxent;
    bfd.h:  (bfd *, struct bfd_symbol *, int, union internal_auxent *);
    bfd.h:  union {
    bfd.h:  /* The value of the symbol.  This really should be a union of a
    bfd.h:  union
    bfd.h:  union
    bfdlink.h:  /* A union of information depending upon the type.  */
    bfdlink.h:  union
    bfdlink.h:       this field.  This field is present in all of the union element
    bfdlink.h:       the union; this structure is a major space user in the
    bfdlink.h:  union
    bfdlink.h:  union
    curses.h:    union
    db_cxx.h:// 4201: nameless struct/union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:typedef union
    _G_config.h:typedef union
    gcrypt.h:  union
    gcrypt.h:    union
    gcrypt.h:    union
    gmp-i386.h:  union {
    ieee754.h:union ieee754_float
    ieee754.h:union ieee754_double
    ieee754.h:union ieee854_long_double
    ifaddrs.h:  union
    jpeglib.h:  union {
    ldap.h: union mod_vals_u {
    ncurses.h:    union
    newt.h:    union {
    obstack.h:  union
    pi-file.h:  union {
    resolv.h:   union {
    signal.h:extern int sigqueue (__pid_t __pid, int __sig, __const union sigval __val)
    stdlib.h:/* Lots of hair to allow traditional BSD use of `union wait'
    stdlib.h:  (__extension__ (((union { __typeof(status) __in; int __i; }) \
    stdlib.h:/* This is the type of the argument to `wait'.  The funky union
    stdlib.h:   causes redeclarations with either `int *' or `union wait *' to be
    stdlib.h:typedef union
    stdlib.h:    union wait *__uptr;
    stdlib.h:  } __WAIT_STATUS __attribute__ ((__transparent_union__));
    thread_db.h:  union
    thread_db.h:  union
    tiffio.h:   union {
    wchar.h:  union
    xf86drm.h:typedef union _drmVBlank {

5
TSK tsk! Zwei Abstimmungen und keine Erklärungen. Das ist enttäuschend.
Wallyk

Das Beispiel mit einer Person, die einen Mann und eine Frau halten kann, ist in meinen Augen ein sehr schlechtes Design. Warum nicht eine Personenbasisklasse und ein Mann und eine Frau eine abgeleitet? Entschuldigung, aber die manuelle Suche nach einer Variablen zur Bestimmung des gespeicherten Typs in einem Datenfeld ist überhaupt keine gute Idee. Dies ist handgefertigter C-Code, der seit Jahren nicht mehr gesehen wurde. Aber keine Gegenstimme, es ist nur mein Standpunkt :-)
Klaus

4
Ich denke, Sie haben die Gegenstimmen für die Gewerkschaft "kastriert" oder "Schwangerschaften" erhalten. Es ist ein bisschen krank.
Akaltar

2
Ja, ich denke es war ein dunkler Tag.
Wallyk

14

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:

// Composite structure for IP address storage
union
{
    // IPv4 @ 32-bit identifier
    // Padded 12-bytes for IPv6 compatibility
    union
    {
        struct
        {
            unsigned char _reserved[12];
            unsigned char _IpBytes[4];
        } _Raw;

        struct
        {
            unsigned char _reserved[12];
            unsigned char _o1;
            unsigned char _o2;
            unsigned char _o3;
            unsigned char _o4;    
        } _Octet;    
    } _IPv4;

    // IPv6 @ 128-bit identifier
    // Next generation internet addressing
    union
    {
        struct
        {
            unsigned char _IpBytes[16];
        } _Raw;

        struct
        {
            unsigned short _w1;
            unsigned short _w2;
            unsigned short _w3;
            unsigned short _w4;
            unsigned short _w5;
            unsigned short _w6;
            unsigned short _w7;
            unsigned short _w8;   
        } _Word;
    } _IPv6;
} _IP;

7
Beachten Sie jedoch, dass der Zugriff auf solche Rohstoffe nicht Standard ist und möglicherweise nicht bei allen Compilern wie erwartet funktioniert.
Nr.

3
Es ist auch sehr häufig, dass dies so verwendet wird, dass die Ausrichtung nicht garantiert wird. Dies ist ein undefiniertes Verhalten.
Mooing Duck

10

Ein Beispiel, wenn ich eine Gewerkschaft verwendet habe:

class Vector
{
        union 
        {
            double _coord[3];
            struct 
            {
                double _x;
                double _y; 
                double _z;
            };

        };
...
}

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.

   union {   // dimension from left to right   // union for the left to right dimension
        uint32_t            m_width;
        uint32_t            m_sizeX;
        uint32_t            m_columns;
    };

    union {   // dimension from top to bottom   // union for the top to bottom dimension
        uint32_t            m_height;
        uint32_t            m_sizeY;
        uint32_t            m_rows;
    };

12
Beachten Sie, dass, obwohl diese Lösung auf den meisten beobachtbaren Plattformen funktioniert, das Setzen von Werten auf _x, _y, _z und der Zugriff auf _coord ein undefiniertes Verhalten ist. Der Hauptzweck der Gewerkschaften ist die Erhaltung des Weltraums. Sie müssen auf genau dasselbe Vereinigungselement zugreifen, das Sie zuvor festgelegt haben.
anxieux

1
So benutze ich es auch, obwohl ich ein std :: array forr coords und einige static_asserts benutze
Viktor Sehr

1
Dieser Code verstößt gegen die strengen Aliasing-Regeln und darf nicht empfohlen werden.
Walter

Gibt es vielleicht eine Möglichkeit, die Gewerkschaft so zu verbessern, dass dies zuverlässig wäre?
Andrew

8

Gewerkschaften sorgen für Polymorphismus in C.


18
Ich dachte das void*getan ^^

2
@ user166390 Polymorphismus verwendet dieselbe Schnittstelle, um mehrere Typen zu bearbeiten. void * hat keine Schnittstelle.
Alice

2
In C wird Polymorphismus üblicherweise durch undurchsichtige Typen und / oder Funktionszeiger implementiert. Ich habe keine Ahnung, wie oder warum Sie eine Gewerkschaft einsetzen würden, um dies zu erreichen. Es klingt nach einer wirklich schlechten Idee.
Lundin

7

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

typedef union
{
  float data[4];
  struct
  {
    float x;
    float y;
    float z;
  };
} PointXYZ;

Die 3 Schwimmer sind mit einem zusätzlichen Schwimmer für die SSE-Ausrichtung gepolstert. So für

PointXYZ point;

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


7

Das unionSchlü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:

boost::variant<std::string, Foo, Bar>

Welches hat die meisten Vorteile der union(wenn nicht alle) und fügt hinzu:

  • Fähigkeit, Nicht-POD-Typen korrekt zu verwenden
  • statische Sicherheit

In der Praxis wurde gezeigt, dass es einer Kombination von union+ entspricht enum, und es wurde ein Benchmark durchgeführt, dass es genauso schnell war (während boost::anyes mehr zum Bereich von gehört dynamic_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.


Dies gilt nicht mehr für neuere Versionen von c ++. Siehe zum Beispiel jrsalas Antwort.
Andrew

@ Andrew: Ich habe die Antwort aktualisiert, um zu erwähnen, dass C ++ 11 mit uneingeschränkten Vereinigungen das Speichern von Typen mit Destruktoren in Union ermöglichte. Ich noch von meiner Haltung stehen , dass Sie wirklich viel besser dran mit getaggt Gewerkschaften wie boost::variantals 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.
Matthieu M.

3

Aus dem Wikipedia-Artikel über Gewerkschaften :

Der Hauptnutzen einer Union besteht darin , Platz zu sparen , da so viele verschiedene Typen im selben Raum gespeichert werden können. Gewerkschaften sorgen auch für groben Polymorphismus . Es gibt jedoch keine Überprüfung der Typen, daher muss der Programmierer sicherstellen, dass in verschiedenen Kontexten auf die richtigen Felder zugegriffen wird. Das relevante Feld einer Vereinigungsvariablen wird typischerweise durch den Zustand anderer Variablen bestimmt, möglicherweise in einer umschließenden Struktur.

Eine gängige C-Programmiersprache verwendet Unions, um das auszuführen, was C ++ als reinterpret_cast bezeichnet, indem sie einem Feld einer Union zugewiesen und aus einem anderen gelesen wird, wie dies in Code erfolgt, der von der Rohdarstellung der Werte abhängt.


2

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_woozlewä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:

struct float1 { float f0;};
struct byte4  { char b0,b1,b2,b3; }; /* Unsigned didn't exist yet */

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 float1als ob es sich um eine wurden struct byte4durch 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 deklarieren union f1b4;statt struct float1und Ersetzen Zugriffen auf f0, b0, b1, usw. mit. ff.f0, bb.b0, bb.b1usw. Zwar gibt es bessere Möglichkeiten , einen solchen Code sind , haben unterstützt werden können, die unionwar Ansatz zumindest etwas bearbeitbar, zumindest mit C89-Ära Interpretationen der Aliasing - Regeln.


1

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.


1

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 floatund unsigned intdie gleiche Länge habe), aber kein undefiniertes Verhalten (siehe hier ).

#include <stdio.h> 

union float_uint
{
    float f;
    unsigned int ui;
};

int main()
{
    float v = 241;
    union float_uint fui = {.f = v};

    //May trigger UNSPECIFIED BEHAVIOR but not UNDEFINED BEHAVIOR 
    printf("Your IEEE 754 float sir: %08x\n", fui.ui);

    //This is UNDEFINED BEHAVIOR as it violates the Strict Aliasing Rule
    unsigned int* pp = (unsigned int*) &v;

    printf("Your IEEE 754 float, again, sir: %08x\n", *pp);

    return 0;
}

Die Typzugriffsregeln gelten nicht nur für "aktuelle" Versionen des Standards. Jede Version des C enthält im Wesentlichen die gleichen Regeln. Was sich geändert hat, ist, dass Compiler verwendet wurden, um die Fußnote zu betrachten. "Mit dieser Liste sollen die Umstände angegeben werden, unter denen ein Objekt möglicherweise einen Alias ​​aufweist oder nicht." als Hinweis darauf, dass die Regel nicht für Fälle gelten sollte, in denen kein Aliasing wie geschrieben durchgeführt wurde , sondern sie nun als Aufforderung behandelt wird, Code neu zu schreiben, um ein Aliasing zu erstellen, bei dem es kein Aliasing gegeben hat.
Supercat

1

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:

struct Number{
enum NumType{int32, float, double, complex}; NumType num_t;
union{int ival; float fval; double dval; ComplexNumber cmplx_val}
}

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.

struct NumberBase
{
virtual Add(NumberBase n);
...
}
struct NumberInt: Number
{
//implement methods assuming Number's union contains int
NumberBase Add(NumberBase n);
...
}
struct NumberDouble: Number
{
 //implement methods assuming Number's union contains double
 NumberBase Add(NumberBase n);
 ...
}
//e.t.c. for all number types/or use templates
struct Number: NumberBase{
 union{int ival; float fval; double dval; ComplexNumber cmplx_val;}
 NumberBase* num_t;
 Set(int a)
 {
 ival=a;
  //still kind of hack, hope it works because derived classes of   Number    dont add any fields
 num_t = static_cast<NumberInt>(this);
 }
}

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.


Dies kann nützlich sein, wenn Sie eine dynamische Sprache erstellen. Das Problem, von dem ich denke, dass es es lösen wird, besteht darin, eine Variable unbekannten Typs in der Masse zu modifizieren, ohne diese Modifikation N-mal zu implementieren. Makros sind dafür schrecklich und Vorlagen sind praktisch unmöglich.
Andrew

0

Von http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :

Die Verwendung von Gewerkschaften ist selten. Auf den meisten Computern sind die Größe eines Zeigers und eines Int normalerweise gleich. Dies liegt daran, dass beide normalerweise in ein Register in der CPU passen. Wenn Sie also einen Zeiger schnell und schmutzig auf ein int oder auf andere Weise umwandeln möchten, deklarieren Sie eine Union.

union intptr {   int i;   int * p; }; 
union intptr x; x.i = 1000; 
/* puts 90 at location 1000 */ 
*(x.p)=90; 

Eine andere Verwendung einer Union besteht in einem Befehl oder Nachrichtenprotokoll, in dem Nachrichten unterschiedlicher Größe gesendet und empfangen werden. Jeder Nachrichtentyp enthält unterschiedliche Informationen, aber jeder hat einen festen Teil (wahrscheinlich eine Struktur) und ein variables Teilbit. So können Sie es implementieren.

struct head {   int id;   int response;   int size; }; struct msgstring50 {    struct head fixed;    char message[50]; } struct

struct msgstring80 {struct head fixed; char message [80]; }
struct msgint10 {struct head fixed; int message [10]; } struct msgack {struct head fixed; int ok; } union messagetype {
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ack; }}

In der Praxis ist es sinnvoll, nur die aussagekräftigen Daten und nicht den verschwendeten Speicherplatz zu senden, obwohl die Gewerkschaften gleich groß sind. Ein msgack ist nur 16 Byte groß, während ein msgstring80 92 Byte groß ist. Wenn also eine Messagetypvariable initialisiert wird, wird das Größenfeld entsprechend dem Typ festgelegt. Dies kann dann von anderen Funktionen verwendet werden, um die richtige Anzahl von Bytes zu übertragen.


0

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:

union u_tag {
     int ival;
     float fval;
     char  *sval;
} u;

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

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.