Ne zaman sendika kullanılır? Sadece C günlerinden kalanlar mı?


133

Öğrenmiştim ama gerçekten sendika alamıyorum. Geçtiğim her C veya C ++ metni onları (bazen geçerken) tanıtır, ancak bunları neden veya nerede kullanacaklarına dair çok az pratik örnek verme eğilimindedirler. Sendikalar modern (hatta eski) bir durumda ne zaman yararlı olurlar? Benim sadece iki tahminim, çalışmak için çok sınırlı bir alana sahip olduğunuzda veya bir API (veya benzer bir şey) geliştirdiğinizde ve son kullanıcıyı, yalnızca birkaç nesne / tür örneğinin bir kere. Bu iki tahmin sağa daha yakın mı?


31
C / C ++ bir dil değildir. Sendikalar C'de orta derecede yararlıdır ve C ++ 'da büyük ölçüde işe yaramaz. C ++ 'da "C +' dan C'ye dayanan bir kalıntı" olduklarını söylemek doğru olur, ama sanki C ++ supercedes C.
R .. GitHub BUZA YARDIMCI OLMAK DUR

12
C ++ 'ın sendikaların yerine ne olduğunu veya c ++' da neden yararsız olduğunu açıklayabilir misiniz?
Russel

3
C ++ 'ın sendikaların yerini sınıflar ve miras alır - C'deki sendikalar neredeyse tamamen tip güvenli polimorfizm için kullanılır. Bir şey derslerde çok daha iyi. (Bkz. Vz0'ın C stili polimorfizm cevabı)
tobyodavies

6
@R ..: birleşim hala C ++ 'da orta derecede yararlıdır. Aşağıdaki cevaplara bakın.
Michael

2
Sendikalar bir işletim sisteminin bağırsaklarında veya örneğin ses dosyalarını birleştiren / söken bir pakette olağanüstü derecede değerli olabilir. Bu bağlamlarda çok farklı şekillerde kullanılırlar - veri / endian dönüşümü, düşük seviyeli polimorfizm, et al. Evet, aynı soruna başka çözümler de var (esas olarak işaretçi türleri arasında döküm), ancak sendikalar genellikle daha temiz ve daha iyi kendi kendini belgeliyor.
Hot Licks

Yanıtlar:


105

Sendikalar genellikle bir ayrımcı şirket ile birlikte kullanılır: Birliğin alanlarından hangisinin geçerli olduğunu belirten bir değişken. Örneğin, kendi Varyant türünüzü oluşturmak istediğinizi varsayalım :

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

Sonra şöyle kullanırsınız:

/* 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;
    ...
    }
}

Bu aslında oldukça yaygın bir deyim, özellikle Visual Basic dahili.

Gerçek bir örnek için bkz. SDL'nin SDL_Event birliği . ( burada gerçek kaynak kodu ). typeBirliğin üstünde bir alan vardır ve aynı alan her SDL_ * Etkinlik yapısında tekrarlanır. Ardından, doğru olayı işlemek için typealanın değerini kontrol etmeniz gerekir .

Avantajları basittir: gereksiz bellek kullanmadan tüm olay türlerini işlemek için tek bir veri türü vardır.


2
Harika! Bu durumda, şimdi Sdl işlevinin neden sadece bir sınıf hiyerarşisi olarak uygulanmadığını merak ediyorum. Sadece C ++ değil C uyumlu mu?
Russel

12
@Russel C ++ sınıfları bir C programından kullanılamaz, ancak C yapıları / birlikleri bir 'extern "C"' bloğu kullanılarak C ++ 'dan kolayca erişilebilir.
vz0

1
Bu varyant desen de sık sık, örneğin tanımı, programlama dili tercümanlar için kullanılmaktadır struct objectiçinde github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield

1
Harika bir açıklama. Sendikaların ne olduğunu hep biliyordum, ama hiç kimsenin onları kullanmak için yeterince çılgınca olmasının gerçek dünya nedenini hiç görmedim :) Örnek için teşekkürler.
riwalk

@ Stargazer712, Google'ın kod araması: google.com/…
kagali-san

87

C ++ sendikalarını oldukça havalı buluyorum. Görünüşe göre, insanlar sadece bir kişinin "yerinde" bir sendika örneğinin değerini değiştirmek istediği kullanım örneğini düşünüyor gibi görünüyor (ki bu sadece hafızadan tasarruf etmeye veya şüpheli dönüşümler gerçekleştirmeye hizmet ediyor).

Aslında sendikalar , hiçbir sendika örneğinin değerini asla değiştirmediğinizde bile , bir yazılım mühendisliği aracı olarak büyük bir güce sahip olabilirler .

Kullanım örneği 1: bukalemun

Sendikalarla, bir temel sınıf ve onun türetilmiş sınıfları ile benzerlik göstermeyen bir mezhep altında bir dizi rasgele sınıfı yeniden gruplandırabilirsiniz. Bununla birlikte, değişen birlik, belirli bir sendika örneğiyle yapabileceğiniz ve yapamayacağınız şeydir:

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

Programcının kullanmak istediği belirli bir sendika örneğinin içeriğinden emin olması gerektiği anlaşılıyor. fYukarıdaki işlevde durum böyledir . Bununla birlikte, bir işlev gyukarıdaki gibi olduğu gibi geçirilmiş bir argüman olarak bir birleşim örneği alacak olsaydı, bununla ne yapacağını bilemezdi. Aynısı sendika örneğini döndüren işlevler için de geçerlidir, bkz h: arayan kişi içeride ne olduğunu nasıl bilebilir?

Bir sendika örneği asla bir argüman veya dönüş değeri olarak geçilmezse, programcı içeriğini değiştirmeyi seçtiğinde çok monoton bir hayata sahip olmak heyecanlıdır:

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

Ve bu, sendikaların en yaygın kullanım örneğidir. Bir başka kullanım örneği, sendika örneğinin size türünü söyleyen bir şeyle birlikte gelmesidir.

Kullanım örneği 2: "Güzel Ben, tanıştığımıza objectgelen, Class"

Bir sendika örneğini her zaman tür tanımlayıcıyla eşleştirmek için seçilmiş bir programcı olduğunu varsayalım (bu tür bir nesne için bir uygulama hayal etmek için onu okuyucunun takdirine bırakacağım). Programlayıcının istediği şey hafızayı korumaksa ve tür tanımlayıcısının büyüklüğünün sendikaya göre ihmal edilemez olması durumunda, bu sendikanın amacını yener. Ancak diyelim ki sendika örneğinin, içeride ne olduğunu bilmeyen callee veya arayan ile bir argüman olarak veya bir dönüş değeri olarak geçirilebilmesi çok önemlidir.

Sonra programcı switchBruce Wayne'e tahta bir çubuktan ya da eşdeğeri bir şeyden bahsetmek için bir kontrol akışı bildirimi yazmalıdır. Birlikte yalnızca iki tür içerik olduğunda çok kötü değil, ama açıkça, birlik artık ölçeklenmiyor.

Kullanım örneği 3:

ISO C ++ Standardı için bir tavsiyenin yazarlarının 2008'de tekrar belirttiği gibi,

Birçok önemli sorun alanı, çok sayıda nesne veya sınırlı bellek kaynağı gerektirir. Bu durumlarda alandan tasarruf etmek çok önemlidir ve bir birlik genellikle bunu yapmak için mükemmel bir yoldur. Aslında, yaygın bir kullanım örneği, bir birliğin yaşamı boyunca hiçbir zaman aktif üyesini değiştirmediği durumdur. Sadece bir üye içeren bir yapı gibi inşa edilebilir, kopyalanabilir ve imha edilebilir. Bunun tipik bir uygulaması, dinamik olarak tahsis edilmemiş (belki de bir haritada veya bir dizinin üyelerinde yerinde oluşturulmuş) ilişkisiz türlerin heterojen bir koleksiyonunu oluşturmak olacaktır.

Ve şimdi, bir örnek, UML sınıf diyagramı ile:

A sınıfı için birçok kompozisyon

Düz İngilizce durum: A sınıfı bir nesne olabilir B1 arasında herhangi sınıfın nesneleri, ..., Bn ve her tür çoğu biri de n oldukça büyük bir sayı olmak en az 10 söylerler.

A'ya aşağıdaki gibi alanlar (veri üyeleri) eklemek istemiyoruz:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

çünkü n değişebilir (karışıma Bx sınıfları eklemek isteyebiliriz), çünkü bu yapıcılar için karışıklığa neden olacağından ve A nesneleri çok fazla yer kaplayacağından.

Onları almak için dökümleri void*olan Bxnesnelere tuhaf bir işaretçi konteyneri kullanabiliriz , ancak bu çirkin ve çok C tarzı ... ama daha da önemlisi, bizi yönetmek için dinamik olarak tahsis edilen birçok nesnenin ömrünü bırakacaktı.

Bunun yerine ne yapılabilir:

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

Daha sonra, bir birleşim örneğinin içeriğini almak için şunu datakullanın a.get(TYPE_B2).b2ve benzerleri, burada asınıf Aörneği.

C ++ 11'de sendikalar sınırsız olduğu için bu daha da güçlü. Ayrıntılar için yukarıdaki belgeye veya bu makaleye bakın.


Bu çok yardımcı oldu ve ikinci makalenin dizisi çok bilgilendiriciydi. Teşekkürler.
Andrew

38

Bir örnek, bir kaydın her bitinin farklı bir şey ifade edebileceği gömülü alandadır. Örneğin, 8 bitlik bir tamsayı ve 8 ayrı 1 bitlik bit alanı olan bir yapı, bir biti veya tüm baytı değiştirmenize olanak tanır.


7
Bu, aygıt sürücülerinde de çok yaygındır. Birkaç yıl önce bir proje için böyle birlikler kullanarak bir sürü kod yazdım . Normalde önerilmez ve bazı durumlarda derleyiciye özgü olabilir, ancak işe yarar.
thkala

11
Ben "tavsiye edilmez" demezdim. Gömülü alanda, genellikle çok fazla açık kadro ve void*s veya maske ve vardiya içeren alternatiflerden çok daha temiz ve daha az hataya yatkındır .
bta

heh? Çok açık oyuncular mı? Gibi basit ifadeleri Bana öyle geliyor REG |= MASKve REG &= ~MASK. Bu hata eğilimli ise, onları a #define SETBITS(reg, mask)ve öğelerine koyun #define CLRBITS(reg, mask).
Michael

26

Herb Sutter yazdığı GOTW ile, altı hakkında yıllar önce vurgu ekledi:

"Ancak sendikaların sadece eski zamanlardan kalma bir dayanak olduğunu düşünmeyin. Sendikalar, verilerin örtüşmesine izin vererek yerden tasarruf etmek için belki de çok yararlıdır ve bu hala C ++ ve bugünün modern dünyasında arzu edilmektedir . Örneğin, gelişmiş C ++Dünyadaki standart kütüphane uygulamaları artık sadece bu tekniği, "küçük dize optimizasyonu" nu uygulamak için kullanmaktadır. Bu, dize nesnesinin içindeki depolamayı yeniden kullanan harika bir optimizasyon alternatifidir: büyük dizeler için dize nesnesinin içindeki boşluk, normal işaretçiyi dinamik olarak depolar tahsis edilen tampon ve tamponun büyüklüğü gibi temizlik bilgileri; küçük dizeler için, dize içeriğini doğrudan saklamak ve dinamik bellek ayırmadan tamamen kaçınmak için aynı alan yeniden kullanılır. Küçük dizi optimizasyonu (ve diğer derinlik optimizasyonları ve hatırı sayılır derinlikteki optimizasyonlar) hakkında daha fazla bilgi için, bkz. ... "

Daha az kullanışlı bir örnek için, uzun ama net olmayan soru gcc, katı takma adlandırma ve bir sendika aracılığıyla yayınlama konusuna bakın .


23

Düşünebileceğim bir örnek kullanım örneği şudur:

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

Daha sonra, bu 32 bit veri bloğunun 8 bit ayrı bölümlerine erişebilirsiniz; ancak, potansiyel olarak endianness tarafından ısırılmaya hazır olun.

Bu sadece bir varsayımsal örnektir, ancak bir alandaki verileri böyle bileşen parçalara bölmek istediğinizde, bir birleşim kullanabilirsiniz.

Bununla birlikte, endian güvenli olan bir yöntem de var:

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

Örneğin, bu ikili işlem derleyici tarafından doğru endianiteye dönüştürüleceğinden.


Bence en iyi soru sendikaların ne zaman kullanılması gerektiğidir. Birliğin doğru araç olmadığı konusunda bir cevap verdiniz , ki bu cevapta daha açık olması gerektiğini düşünüyorum.
Michael

15

Sendikaların bazı kullanımları:

  • Bilinmeyen bir harici ana bilgisayara genel bir endianness arayüzü sağlayın.
  • VAX G_FLOATS'i bir ağ bağlantısından kabul etmek ve bunları işlenmek üzere IEEE 754 uzun realitesine dönüştürmek gibi yabancı CPU mimarisi kayan nokta verilerini işleyin .
  • Daha üst düzey bir türe basit bir bit çevirme erişimi sağlayın.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

Bu bildirimle, a'nın onaltılık bayt değerlerini görüntülemek, long doubleüssün işaretini değiştirmek, denormal bir değer olup olmadığını belirlemek veya desteklemeyen bir CPU için uzun çift aritmetik uygulamak kolaydır .

  • Alanlar belirli değerlere bağımlı olduğunda depolama alanından tasarruf:

    class person {  
        string name;  
    
        char gender;   // M = male, F = female, O = other  
        union {  
            date  vasectomized;  // for males  
            int   pregnancies;   // for females  
        } gender_specific_data;
    }
  • Derleyicinizle kullanmak için içerme dosyalarını kaydedin. Düzinelerce ila yüzlerce kullanım bulacaksınız 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! İki downvotes ve açıklama yok. Bu hayal kırıklığı yaratıyor.
wallyk

Bir erkeği ve bir kadını tutabilecek bir kişiyle örnek, gözlerimde çok kötü bir tasarım. Neden bir kişi temel sınıfı ve bir erkek ve kadın bir tane türetmiyor? Üzgünüz, ancak bir veri alanında saklanan türü belirlemek için manuel olarak bir değişken aramak hiç de kötü bir fikir değil. Bu el yapımı c kodu yıllardır hiç görülmedi. Ama yok oy yok, bu sadece benim bakış açım :-)
Klaus

4
Sanırım "hadım edilmiş" ya da "hamilelikler" birliği için aşağı oylar aldınız. Biraz hasta.
akaltar

2
Evet, sanırım karanlık bir gündü.
wallyk

14

Sendikalar, bayt seviyesi (düşük seviye) verilerle çalışırken faydalıdır.

Son kullanımımlardan biri, aşağıdaki gibi görünen IP adresi modellemesiydi:

// 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
Ancak, bu gibi ham öğelere erişmenin standart olmadığını ve tüm derleyicilerle beklendiği gibi çalışmayabileceğini unutmayın.
nos

3
Ayrıca, bunun, tanımlanmamış davranış olan hizalamayı garanti etmeyen bir şekilde kullanıldığını görmek çok yaygındır.
Mooing Duck

10

Birliği kullandığımda bir örnek:

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

        };
...
}

bu, verilerime bir dizi veya öğeler olarak erişmeme olanak tanır.

Farklı terimlerin aynı değere işaret etmesi için bir birlik kullandım. Görüntü işlemede, ister sütunlar, genişlik veya X yönündeki boyut üzerinde çalışıyordum, kafa karıştırıcı olabilir. Bu sorunu hafifletmek için bir birlik kullanıyorum, böylece hangi açıklamaların bir araya geldiğini biliyorum.

   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
Bu çözüm, gözlemlenebilir platformların çoğunda çalışsa da, değerleri _x, _y, _z olarak ayarlamanın ve _coord'a erişmenin tanımsız bir davranış olduğunu unutmayın. Sendikaların temel amacı alanın korunmasıdır. Daha önce ayarladığınız birleşim elemanına tam olarak erişmeniz gerekir.
anxieux

1
ben de nasıl bu şekilde, althrough Ben bir std :: dizi forr coords kullanın ve bazı static_asserts
Viktor Sehr

1
Bu kod katı takma adlandırma kurallarını ihlal eder ve önerilmez.
Walter

Sendikayı, bunu yapmanın güvenilir olacağı şekilde geliştirmenin bir yolu var mı?
Andrew

8

Sendikalar C'de polimorfizm sağlar.


18
Bunu düşündüm void*^^

2
@ user166390 Çok biçimlilik aynı arabirimi birden çok türü işlemek için kullanıyor; void * 'in arayüzü yok.
Alice

2
C'de polimorfizm yaygın olarak opak tipler ve / veya fonksiyon göstergeleri vasıtasıyla uygulanır. Bunu başarmak için bir birliği nasıl veya neden kullanacağınız hakkında hiçbir fikrim yok. Kulağa gerçekten kötü bir fikir gibi geliyor.
Lundin

7

Birleşmenin parlak bir kullanımı, PCL (Point Cloud Library) kaynak kodunda bulduğum bellek hizalamasıdır. API'deki tek veri yapısı iki mimariyi hedefleyebilir: SSE destekli CPU ve SSE desteksiz CPU. Örneğin: PointXYZ için veri yapısı

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

3 şamandıra SSE hizalaması için ek bir şamandıra ile doldurulur. İçin böylece

PointXYZ point;

Kullanıcı x koordinatına erişmek için point.data [0] veya point.x'e (SSE desteğine bağlı olarak) erişebilir. Daha iyi daha iyi kullanım ayrıntıları aşağıdaki bağlantıda bulunmaktadır: PCL belgeleri PointT türleri


7

unionHala C ++ 03 kullanıldığı süre anahtar kelime, 1 , çoğunlukla C günlerin bir kalıntısıdır. En göze çarpan sorun, sadece POD 1 ile çalışmasıdır .

Bununla birlikte, sendika fikri hala mevcuttur ve aslında Boost kütüphaneleri sendika benzeri bir sınıfa sahiptir:

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

Hangi union(hepsi değilse de) faydalarının çoğuna sahiptir ve ekler:

  • POD olmayan türleri doğru kullanma yeteneği
  • statik tip güvenlik

Uygulamada, union+ 'nın bir kombinasyonuna eşdeğer olduğu enumve bunun (hızlı RTTI kullandığı boost::anyiçin daha alem olduğu halde) olduğu gibi karşılaştırıldığı gösterilmiştir dynamic_cast.

1 Sendikalar C ++ 11'de ( sınırsız sendikalar ) yükseltildi ve kullanıcı yıkıcıyı manuel olarak çağırmak zorunda olmasına rağmen (şu anda aktif olan sendika üyesinde) yıkıcılar içeren nesneler içerebilir. Varyantları kullanmak hala çok daha kolay.


Bu artık c ++ 'ın daha yeni sürümlerinde geçerli değildir. Örneğin jrsala'nın cevabına bakınız.
Andrew

@Andrew: Kısıtlamasız sendikalara sahip C ++ 11'in, yıkıcılı tiplerin bir arada depolanmasına izin verdiğini belirtmek için cevabı güncelledim. Hala sendikaları kendi başlarına kullanmayı denemek gibi etiketli sendikaları kullanmaktan çok daha iyi olduğunuzu düşünüyorum boost::variant. Sendikaları çevreleyen çok fazla tanımlanmamış davranış var, onu doğru yapma şansınız uçsuz bucaksız.
Matthieu

3

Sendikalarla ilgili Wikipedia makalesinden :

Bir birliğin birincil faydası , aynı alanda birçok farklı türün depolanmasına izin vermenin bir yolunu sağladığı için alanı korumaktır . Sendikalar ayrıca ham polimorfizm sağlar . Bununla birlikte, türlerin kontrolü yoktur, bu nedenle uygun alanlara farklı bağlamlarda erişildiğinden emin olmak programcıya bağlıdır. Bir birleşim değişkeninin ilgili alanı, tipik olarak, muhtemelen bir kapalı yapıda bulunan diğer değişkenlerin durumu tarafından belirlenir.

Bir ortak C programlama deyimi, C ++ 'ın reinterpret_cast dediği şeyi gerçekleştirmek için birlikler kullanır; değerlerin ham temsiline dayanan kodda yapıldığı gibi, bir birliğin bir alanına atayarak ve diğerinden okuyarak.


2

C'nin ilk günlerinde (örneğin 1974'te belgelendiği gibi), tüm yapılar üyeleri için ortak bir ad alanı paylaştı. Her üye adı bir tür ve bir ofset ile ilişkilendirilmiştir; "wd_woozle" ofset 12'de bir "int" olsaydı p, herhangi bir yapı tipinde bir işaretçi verildiğinde , p->wd_woozlebuna eşdeğer olurdu *(int*)(((char*)p)+12). Dil, tüm yapı türlerindeki tüm üyelerin benzersiz adlara sahip olmasını gerektiriyordu, ancak kullanıldıkları her yapının ortak bir başlangıç ​​sırası olarak davrandığı durumlarda üye adlarının yeniden kullanılmasına izin verdiler.

Yapı türlerinin karışık bir şekilde kullanılabilmesi, yapıların örtüşen alanlar içeriyormuş gibi davranmasını mümkün kıldı. Örneğin, verilen tanımlar:

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

kodu "float1" türünde bir yapı bildirebilir ve daha sonra buradaki baytlara erişmek için "b0 ... b3" üyelerini kullanabilir. Dil, her yapının üyeleri için ayrı bir ad alanı alacak şekilde değiştirildiğinde, birçok şeye erişme yeteneğine dayanan kod kırılacaktır. Farklı yapı türleri için ad alanlarını ayırmanın değerleri, bu kodun ona uyacak şekilde değiştirilmesini gerektirmek için yeterliydi, ancak bu tekniklerin değeri, dili desteklemeye devam etmek için genişletmeyi haklı çıkarmak için yeterliydi.

Bir dahilinde depolama erişme yeteneğini yararlanmak için yazıldığını Kod struct float1bir sanki struct byte4: bir deklarasyon ekleyerek yeni dilde çalışmalarına yapılmış olabilir union f1b4 { struct float1 ff; struct byte4 bb; };, ilan nesneleri türü olarak union f1b4;yerine struct float1, ve erişimleri değiştirilmesi f0, b0, b1vb . ile ff.f0, bb.b0, bb.b1bu tür kod desteklenen olabilirdi iyi yolu olsa da, vb unionyaklaşım en azından tekit kurallarının C89 dönemi yorumlarla, en azından biraz çalışılabilir oldu.


1

Diyelim ki n farklı konfigürasyonunuz var (sadece parametreleri tanımlayan bir değişkenler kümesi). Yapılandırma türlerinin numaralandırmasını kullanarak, tüm farklı yapılandırma türlerinin birleşimiyle birlikte yapılandırma türünün kimliğine sahip bir yapı tanımlayabilirsiniz.

Bu şekilde, yapılandırmayı geçtiğiniz her yerde, yapılandırma verilerinin nasıl yorumlanacağını belirlemek için kimliği kullanabilir, ancak yapılandırmalar çok büyükse, her bir potansiyel tür israf alanı için paralel yapılara sahip olmak zorunda kalmazsınız.


1

Sendikanın önemi üzerinde son zamanlarda bir artış , C standardının son versiyonunda getirilen Sıkı Takma Adlandırma Kuralı tarafından verilmiştir .

C standardını ihlal etmeden yazışmak için sendikaları kullanabilirsiniz .
Bu program belirtilmemiş bir davranışa sahiptir (çünkü bunu kabul ettim floatve unsigned intaynı uzunluğa sahibim ), ancak tanımlanmamış davranış değil ( buraya bakın ).

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

Tür erişim kuralları yalnızca Standardın "son" sürümlerinde değildir. C'nin her versiyonu aslında aynı kuralları içeriyordu. Değişen şey, "Bu listenin amacı, bir nesnenin diğer adının örtüştüğü veya örtüştüğü koşulları" belirtmek için kullanılan dipnotu dikkate almak için kullanılan derleyicilerdir. kuralın yazılı olarak takma ad içermeyen durumlarda uygulanmasının amaçlanmadığını belirten , ancak şimdi, daha önce bulunmayan bir takma ad oluşturmak için kodu yeniden yazma daveti olarak değerlendiriyorlar.
supercat

1

Sendika kullanmak için iyi bir pratik örnek eklemek istiyorum - formül hesaplayıcı / yorumlayıcı uygulamak veya hesaplamada bir çeşit kullanmak (örneğin, hesaplama formüllerinizin çalışma zamanı bölümleri sırasında değiştirilebilir kullanmak istiyorsanız - denklemi sayısal olarak çözmek - sadece Örneğin). Dolayısıyla, farklı türlerdeki sayıları / sabitleri (tamsayı, kayan nokta, hatta karmaşık sayılar) şu şekilde tanımlamak isteyebilirsiniz:

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

Böylece hafızayı ve daha önemli olanı tasarruf edersiniz - küçük nesnelerin (çok fazla çalışma zamanı tanımlı sayı kullanırsanız) (sınıf mirası / polimorfizm yoluyla yapılan uygulamalara kıyasla) muhtemelen aşırı miktarda herhangi bir dinamik ayırmadan kaçınırsınız. Ama daha ilginç olan, bu tür bir yapı ile hala C ++ polimorfizminin gücünü (örneğin çift sevkıyatın hayranıysanız) kullanabilirsiniz. Ham yapıya ek olarak / yerine bu örneğe işaret ederek tüm sayı türlerinin üst sınıfına "kukla" arayüz işaretçisini eklemeniz veya eski C işlev işaretçileri kullanmanız yeterlidir.

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

bu yüzden anahtarla (tip) tip kontrolleri yerine polimorfizmi kullanabilirsiniz - bellek tasarruflu uygulama ile (küçük nesnelerin dinamik olarak tahsis edilmemesi) - elbette ihtiyacınız varsa.


Bu, dinamik bir dil oluştururken yararlı olabilir. Çözeceğini düşündüğüm sorun, bu modifikasyonu N kez uygulamadan kitle bilinmeyen bir değişkeni modifiye etmektir. Makrolar bunun için dehşet verici ve şablonlama neredeyse imkansız.
Andrew

0

Gönderen http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :

Birliğin kullanımı az ve çoktur. Çoğu bilgisayarda, bir işaretçi ve int boyutu genellikle aynıdır - bunun nedeni, her ikisinin de genellikle CPU'daki bir kayıt defterine sığmasıdır. Bu nedenle, int veya başka bir şekilde bir işaretçiyi hızlı ve kirli bir şekilde yapmak istiyorsanız, bir birlik bildirin.

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

Birliğin bir başka kullanımı, farklı boyuttaki mesajların gönderildiği ve alındığı bir komut veya mesaj protokolündedir. Her ileti türü farklı bilgiler içerir, ancak her birinin sabit bir parçası (muhtemelen bir yapı) ve değişken bir parça biti olacaktır. Bu şekilde uygulayabilirsiniz ..

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

msgstring80 {sabit yapı; karakter mesajı [80]; }
yapı msgint10 {yapı kafası düzeltildi; int mesajı [10]; msgack {sabit yapı; int ok; sendika
mesaj metni { struct msgstring50 m50; yapı msgstring80 m80; yapı msgint10 i10; msgack ack yapı; }

Uygulamada, sendikalar aynı büyüklükte olmasına rağmen, boşa harcanan alanı değil, sadece anlamlı verileri göndermek mantıklıdır. Bir msgack boyutu sadece 16 bayt, msgstring80 ise 92 bayttır. Bu nedenle, bir messagetype değişkeni başlatıldığında, boyut alanı, hangi tür olduğuna göre ayarlanır. Bu daha sonra doğru sayıda bayt aktarmak için diğer işlevler tarafından kullanılabilir.


0

Sendikalar, makineden bağımsız herhangi bir bilgi programa gömmeden, farklı türdeki verileri tek bir depolama alanında işlemek için bir yol sağlarlar. Pascal'daki varyant kayıtlara benzerler

Bir derleyici sembol tablosu yöneticisinde bulunabilecek gibi bir örnek olarak, bir sabitin int, kayan nokta veya karakter işaretçisi olabileceğini varsayalım. Belirli bir sabitin değeri uygun tipte bir değişkente saklanmalıdır, ancak değer aynı miktarda depolama alanı kaplıyorsa ve türünden bağımsız olarak aynı yerde depolanıyorsa, tablo yönetimi için en uygun yöntemdir. Birliğin amacı budur - çeşitli türlerden herhangi birini meşru bir şekilde tutabilen tek bir değişken. Sözdizimi yapılara dayanır:

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

U değişkeni, üç türün en büyüğünü tutacak kadar büyük olacaktır; belirli boyut uygulamaya bağlıdır. Bu türlerden herhangi biri u'ya atanabilir ve daha sonra kullanım tutarlı olduğu sürece ifadelerde kullanılabilir

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.