Khi nào có ai sử dụng liên minh? Có phải là tàn dư từ những ngày chỉ có C?


133

Tôi đã học nhưng không thực sự có được công đoàn. Mỗi văn bản C hoặc C ++ tôi trải qua đều giới thiệu chúng (đôi khi là thông qua), nhưng chúng có xu hướng đưa ra rất ít ví dụ thực tế về lý do hoặc nơi sử dụng chúng. Khi nào các công đoàn sẽ hữu ích trong một trường hợp hiện đại (hoặc thậm chí di sản)? Hai dự đoán duy nhất của tôi sẽ là lập trình vi xử lý khi bạn có không gian rất hạn chế để làm việc hoặc khi bạn đang phát triển API (hoặc một cái gì đó tương tự) và bạn muốn buộc người dùng cuối chỉ có một phiên bản của một số đối tượng / loại một lần. Có phải hai dự đoán này thậm chí gần đúng không?


31
C / C ++ không phải là ngôn ngữ. Các hiệp hội rất hữu ích trong C và phần lớn là vô dụng trong C ++. Sẽ đúng khi nói rằng trong C ++, họ là "tàn dư từ C ++ dựa trên C", nhưng không nói họ là "tàn dư từ C chỉ vài ngày" như thể C ++ thay thế C.
R .. GitHub DỪNG GIÚP ICE

12
Bạn có thể giải thích về sự thay thế của c ++ cho các công đoàn là gì, hoặc tại sao chúng vô dụng trong c ++ không?
Russel

3
Sự thay thế của C ++ cho các công đoàn là các lớp và sự kế thừa - các hiệp hội trong C hầu như chỉ được sử dụng cho đa hình loại an toàn. Một cái gì đó lớp học tốt hơn nhiều. (Xem câu trả lời của vz0 cho đa hình kiểu C)
tobyodavies

6
@R ..: union vẫn có ích vừa phải trong C ++. Xem câu trả lời dưới đây.
Michael

2
Các hiệp hội có thể có giá trị đặc biệt trong ruột của một hệ điều hành, hoặc, ví dụ, một gói lắp ráp / tháo rời các tệp âm thanh. Trong các bối cảnh như vậy, chúng được sử dụng theo nhiều cách khác nhau - chuyển đổi dữ liệu / endian, đa hình mức độ thấp, et al. Vâng, có những giải pháp khác cho cùng một vấn đề (chủ yếu là đúc giữa các loại con trỏ), nhưng các công đoàn thường sạch hơn và tự viết tài liệu tốt hơn.
Hot Licks

Câu trả lời:


105

Liên hiệp thường được sử dụng với công ty của người phân biệt đối xử: một biến chỉ ra lĩnh vực nào của liên minh là hợp lệ. Ví dụ: giả sử bạn muốn tạo loại Biến thể của riêng mình :

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

Sau đó, bạn sẽ sử dụng nó như:

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

Đây thực sự là một thành ngữ khá phổ biến, đặc biệt trên các phần bên trong Visual Basic.

Để biết ví dụ thực tế, hãy xem SDL_Event union của SDL . ( mã nguồn thực tế ở đây ). Có một typetrường ở đầu liên kết và cùng một trường được lặp lại trên mỗi cấu trúc sự kiện SDL_ *. Sau đó, để xử lý sự kiện chính xác, bạn cần kiểm tra giá trị của typetrường.

Các lợi ích rất đơn giản: có một loại dữ liệu duy nhất để xử lý tất cả các loại sự kiện mà không sử dụng bộ nhớ không cần thiết.


2
Tuyệt quá! Trong trường hợp đó, bây giờ tôi tự hỏi tại sao hàm Sdl không được triển khai như một hệ thống phân cấp lớp. Có phải đó là để làm cho nó tương thích với C và không chỉ C ++?
Russel

12
Các lớp @Russel C ++ không thể được sử dụng từ chương trình C, nhưng các cấu trúc / hiệp hội C có thể dễ dàng được tích lũy từ C ++ bằng cách sử dụng khối 'C "bên ngoài.
vz0

1
Mẫu biến thể này cũng thường được sử dụng cho các thông dịch viên ngôn ngữ lập trình, ví dụ: định nghĩa struct objecttrong github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.c
Adam Rosenfield

1
Giải thích tuyệt vời. Tôi luôn biết các công đoàn là gì, nhưng chưa bao giờ thấy một lý do trong thế giới thực về lý do tại sao bất cứ ai cũng đủ điên rồ để sử dụng chúng :) Cảm ơn ví dụ.
riwalk

@ Stargazer712, tìm kiếm mã của Google: google.com/ từ
kagali-san

87

Tôi thấy công đoàn C ++ khá tuyệt. Dường như mọi người thường chỉ nghĩ đến trường hợp sử dụng khi người ta muốn thay đổi giá trị của một thể hiện liên minh "tại chỗ" (có vẻ như chỉ phục vụ để lưu bộ nhớ hoặc thực hiện chuyển đổi đáng ngờ).

Trong thực tế, các công đoàn có thể có sức mạnh lớn như một công cụ kỹ thuật phần mềm, ngay cả khi bạn không bao giờ thay đổi giá trị của bất kỳ trường hợp liên minh nào .

Trường hợp sử dụng 1: tắc kè hoa

Với các hiệp hội, bạn có thể tập hợp lại một số lớp tùy ý theo một mệnh giá, không có sự tương đồng với trường hợp của lớp cơ sở và các lớp dẫn xuất của nó. Tuy nhiên, những thay đổi là những gì bạn có thể và không thể làm với một thể hiện liên minh cụ thể:

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

Có vẻ như lập trình viên phải chắc chắn về loại nội dung của một thể hiện liên minh nhất định khi anh ta muốn sử dụng nó. Đây là trường hợp trong chức năng ftrên. Tuy nhiên, nếu một hàm được nhận một thể hiện hợp nhất như là một đối số được thông qua, như trường hợp gở trên, thì nó sẽ không biết phải làm gì với nó. Điều tương tự cũng áp dụng cho các hàm trả về một thể hiện hợp nhất, xemh : làm thế nào để người gọi biết những gì bên trong?

Nếu một thể hiện liên minh không bao giờ được thông qua như một đối số hoặc như một giá trị trả về, thì nó chắc chắn sẽ có một cuộc sống rất đơn điệu, với sự phấn khích khi lập trình viên chọn thay đổi nội dung của nó:

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

Và đó là trường hợp sử dụng phổ biến nhất (chưa) của các công đoàn. Một trường hợp sử dụng khác là khi một thể hiện hợp nhất đi kèm với một cái gì đó cho bạn biết loại của nó.

Trường hợp sử dụng 2: "Rất vui được gặp bạn, tôi object, từClass "

Giả sử một lập trình viên được bầu để luôn ghép một thể hiện hợp nhất với một mô tả kiểu (tôi sẽ để nó theo ý của người đọc để tưởng tượng việc thực hiện cho một đối tượng như vậy). Điều này đánh bại mục đích của chính liên minh nếu điều mà lập trình viên muốn là tiết kiệm bộ nhớ và kích thước của bộ mô tả kiểu không đáng kể so với liên kết. Nhưng hãy giả sử rằng điều quan trọng là thể hiện liên minh có thể được thông qua dưới dạng đối số hoặc là giá trị trả về với callee hoặc người gọi không biết những gì bên trong.

Sau đó, lập trình viên phải viết một switchtuyên bố dòng điều khiển để nói với Bruce Wayne ngoài một thanh gỗ, hoặc một cái gì đó tương đương. Nó không quá tệ khi chỉ có hai loại nội dung trong liên minh nhưng rõ ràng, công đoàn không còn quy mô nữa.

Trường hợp sử dụng 3:

Như các tác giả của một khuyến nghị cho Tiêu chuẩn ISO C ++ đã đưa nó trở lại vào năm 2008,

Nhiều miền vấn đề quan trọng yêu cầu số lượng lớn đối tượng hoặc tài nguyên bộ nhớ hạn chế. Trong những tình huống này, việc bảo tồn không gian là rất quan trọng và liên minh thường là một cách hoàn hảo để làm điều đó. Trong thực tế, một trường hợp sử dụng phổ biến là tình huống mà một công đoàn không bao giờ thay đổi thành viên tích cực của mình trong suốt cuộc đời. Nó có thể được xây dựng, sao chép và phá hủy như thể nó là một cấu trúc chỉ chứa một thành viên. Một ứng dụng điển hình của việc này sẽ là tạo ra một bộ sưu tập các loại không liên quan không được phân bổ động (có lẽ chúng được đặt tại chỗ trong bản đồ hoặc các thành viên của một mảng).

Và bây giờ, một ví dụ, với sơ đồ lớp UML:

nhiều sáng tác cho lớp A

Tình huống bằng tiếng Anh đơn giản: một đối tượng của lớp A có thể có các đối tượng thuộc bất kỳ lớp nào trong số B1, ..., Bn và nhiều nhất là một trong mỗi loại, với n là một số khá lớn, ít nhất là 10.

Chúng tôi không muốn thêm các trường (thành viên dữ liệu) vào A như vậy:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

bởi vì n có thể khác nhau (chúng ta có thể muốn thêm các lớp Bx vào hỗn hợp) và bởi vì điều này sẽ gây ra sự lộn xộn với các hàm tạo và vì các đối tượng A sẽ chiếm rất nhiều không gian.

Chúng ta có thể sử dụng một thùng chứa void*con trỏ lập dị đểBx các đối tượng có phôi để lấy chúng, nhưng đó là kiểu chạy trốn và theo kiểu C ... nhưng quan trọng hơn là điều đó sẽ khiến chúng ta có nhiều thời gian để quản lý các đối tượng được phân bổ động.

Thay vào đó, những gì có thể được thực hiện là đây:

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

Sau đó, để lấy nội dung của một thể hiện liên minh từ đó data, bạn sử dụng a.get(TYPE_B2).b2và lượt thích, amột lớp học ở đâuA thể hiện của .

Đây là tất cả những gì mạnh mẽ hơn vì các công đoàn không bị hạn chế trong C ++ 11. Xem tài liệu được liên kết đến ở trên hoặc bài viết này để biết chi tiết.


Điều này rất hữu ích, và loạt bài viết thứ hai đó rất nhiều thông tin. Cảm ơn.
Andrew

38

Một ví dụ là trong lĩnh vực nhúng, trong đó mỗi bit của một thanh ghi có thể có nghĩa gì đó khác nhau. Ví dụ, liên kết của một số nguyên 8 bit và cấu trúc với 8 bitfield 1 bit riêng biệt cho phép bạn thay đổi một bit hoặc toàn bộ byte.


7
Điều này là rất phổ biến trong trình điều khiển thiết bị là tốt. Vài năm trước tôi đã viết rất nhiều mã bằng cách sử dụng các công đoàn như thế này cho một dự án. Nó thường không được khuyến nghị và nó có thể là trình biên dịch cụ thể trong một số trường hợp, nhưng nó hoạt động.
thkala

11
Tôi sẽ không gọi đó là "không khuyến khích". Trong không gian nhúng, nó thường sạch hơn và ít bị lỗi hơn so với các giải pháp thay thế, thường liên quan đến rất nhiều diễn viên rõ ràng và void*s hoặc mặt nạ và ca.
bta

hả Rất nhiều diễn viên rõ ràng? Dường như với tôi những tuyên bố đơn giản như REG |= MASKREG &= ~MASK. Nếu đó là lỗi dễ dàng, sau đó đặt chúng trong một #define SETBITS(reg, mask)#define CLRBITS(reg, mask). Đừng dựa vào trình biên dịch để lấy các bit theo một thứ tự nhất định ( stackoverflow.com/questions/1490092/ mẹo )
Michael

26

Herb Sutter đã viết trong GOTW khoảng sáu năm trước, với sự nhấn mạnh được thêm vào:

"Nhưng đừng nghĩ rằng các công đoàn chỉ là một sự nắm giữ từ thời trước. Các công đoàn có lẽ hữu ích nhất cho việc tiết kiệm không gian bằng cách cho phép dữ liệu chồng chéo, và điều này vẫn được mong muốn trong C ++ và trong thế giới hiện đại ngày nay. Ví dụ, một số C ++ nâng caoHiện tại việc triển khai thư viện chuẩn trên thế giới chỉ sử dụng kỹ thuật này để thực hiện "tối ưu hóa chuỗi nhỏ", một phương án tối ưu hóa tuyệt vời sử dụng lại bộ lưu trữ bên trong một đối tượng chuỗi: đối với các chuỗi lớn, không gian bên trong đối tượng chuỗi lưu trữ con trỏ thông thường vào động phân bổ bộ đệm và thông tin vệ sinh như kích thước của bộ đệm; thay vào đó, đối với các chuỗi nhỏ, cùng một không gian được sử dụng lại để lưu trữ nội dung chuỗi trực tiếp và hoàn toàn tránh mọi phân bổ bộ nhớ động. Để biết thêm về tối ưu hóa chuỗi nhỏ (và tối ưu hóa chuỗi khác và bi quan hóa ở độ sâu đáng kể), xem .... "

Và đối với một ví dụ ít hữu ích hơn, hãy xem gcc câu hỏi dài nhưng không có kết luận , bí danh nghiêm ngặt và truyền qua một liên minh .


23

Vâng, một ví dụ sử dụng trường hợp tôi có thể nghĩ là:

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

Sau đó, bạn có thể truy cập các phần riêng biệt 8 bit của khối dữ liệu 32 bit đó; tuy nhiên, chuẩn bị để có khả năng bị cắn bởi endianness.

Đây chỉ là một ví dụ giả thuyết, nhưng bất cứ khi nào bạn muốn chia dữ liệu trong một trường thành các phần thành phần như thế này, bạn có thể sử dụng kết hợp.

Điều đó nói rằng, cũng có một phương pháp an toàn về cuối:

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

Ví dụ, vì hoạt động nhị phân đó sẽ được trình biên dịch chuyển đổi thành endianness chính xác.


Tôi nghĩ rằng câu hỏi được đưa ra tốt nhất là khi nào nên sử dụng công đoàn. Bạn đã cung cấp một câu trả lời về nơi mà một công đoàn không phải là công cụ chính xác, mà tôi nghĩ nên làm rõ hơn trong câu trả lời này.
Michael

15

Một số sử dụng cho các công đoàn:

  • Cung cấp một giao diện endianness chung cho một máy chủ bên ngoài không xác định.
  • Thao tác dữ liệu điểm nổi kiến ​​trúc CPU nước ngoài, chẳng hạn như chấp nhận VAX G_FLOATS từ một liên kết mạng và chuyển đổi chúng thành các thực tế dài của IEEE 754 để xử lý.
  • Cung cấp quyền truy cập gấp đôi bit đơn giản cho loại cấp cao hơn.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

Với khai báo này, thật đơn giản để hiển thị các giá trị byte hex của a long double, thay đổi dấu của số mũ, xác định xem đó có phải là giá trị bất thường hay thực hiện số học kép dài cho CPU không hỗ trợ nó, v.v.

  • Tiết kiệm không gian lưu trữ khi các trường phụ thuộc vào các giá trị nhất định:

    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 các tập tin bao gồm để sử dụng với trình biên dịch của bạn. Bạn sẽ tìm thấy hàng chục đến hàng trăm công dụng của 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! Hai downvote và không có lời giải thích. Điều đó thật đáng thất vọng.
wallyk

Ví dụ với một người có thể giữ một người đàn ông và một người phụ nữ là một thiết kế rất xấu trong mắt tôi. Tại sao không phải là một lớp cơ sở người và một người đàn ông và phụ nữ có nguồn gốc một? Xin lỗi, nhưng tìm kiếm thủ công một biến để xác định loại được lưu trữ trong trường dữ liệu aa là ý tưởng tồi. Đây là mã c thủ công chưa từng thấy trong nhiều năm. Nhưng không có downvote, đó chỉ là quan điểm của tôi :-)
Klaus

4
Tôi đoán bạn đã nhận được các downvote cho liên minh "thiến" hoặc "mang thai". Đó là một chút bệnh.
akaltar

2
Vâng, tôi đoán đó là một ngày đen tối.
wallyk

14

Các hiệp hội rất hữu ích khi xử lý dữ liệu mức byte (mức thấp).

Một trong những cách sử dụng gần đây của tôi là về mô hình địa chỉ IP trông như dưới đây:

// 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
Mặc dù vậy, hãy nhớ rằng việc truy cập vào các nội dung thô như thế không phải là tiêu chuẩn và có thể không hoạt động như mong đợi với tất cả các trình biên dịch.
số

3
Ngoài ra, rất phổ biến để thấy điều này được sử dụng theo cách không đảm bảo sự liên kết, đó là hành vi không xác định.
Vịt Mooing

10

Một ví dụ khi tôi đã sử dụng một liên minh:

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

        };
...
}

điều này cho phép tôi truy cập dữ liệu của mình dưới dạng một mảng hoặc các phần tử.

Tôi đã sử dụng một liên minh để có các điều khoản khác nhau cho cùng một giá trị. Trong xử lý hình ảnh, cho dù tôi đang làm việc trên các cột hoặc chiều rộng hoặc kích thước theo hướng X, nó có thể trở nên khó hiểu. Để giải quyết vấn đề này, tôi sử dụng một liên minh để tôi biết những mô tả nào đi cùng nhau.

   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
Lưu ý rằng mặc dù giải pháp này hoạt động trên hầu hết các nền tảng có thể quan sát được, việc đặt giá trị thành _x, _y, _z và truy cập _coord là một hành vi không xác định. Mục đích chính của công đoàn là bảo tồn không gian. Bạn phải truy cập chính xác cùng một yếu tố hợp nhất mà bạn đã đặt trước đó.
lo lắng

1
đây cũng là cách tôi sử dụng nó, vì vậy tôi sử dụng một chuỗi std :: mảng forr và một số static_asserts
Viktor Sehr

1
Mã này vi phạm các quy tắc răng cưa nghiêm ngặt và không được khuyến khích.
Walter

Có lẽ có một cách để cải thiện liên minh để nó đáng tin cậy để làm điều này?
Andrew

8

Công đoàn cung cấp đa hình trong C.


18
Tôi nghĩ rằng void*đã làm điều đó ^^

2
@ user166390 Đa hình đang sử dụng cùng một giao diện để thao tác nhiều loại; void * không có giao diện.
Alice

2
Trong C, tính đa hình thường được thực hiện thông qua các loại mờ và / hoặc con trỏ hàm. Tôi không biết làm thế nào hoặc tại sao bạn sẽ sử dụng một liên minh để đạt được điều đó. Nghe có vẻ như một ý tưởng thực sự xấu.
Lundin

7

Một cách sử dụng hợp nhất tuyệt vời là căn chỉnh bộ nhớ, mà tôi tìm thấy trong mã nguồn PCL (Point Cloud Library). Cấu trúc dữ liệu đơn trong API có thể nhắm mục tiêu hai kiến ​​trúc: CPU có hỗ trợ SSE cũng như CPU ​​không có hỗ trợ SSE. Ví dụ: cấu trúc dữ liệu cho PointXYZ là

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

3 phao được đệm thêm một phao bổ sung để căn chỉnh SSE. Vì vậy đối với

PointXYZ point;

Người dùng có thể truy cập point.data [0] hoặc point.x (tùy thuộc vào hỗ trợ SSE) để truy cập nói, tọa độ x. Các chi tiết sử dụng tương tự tốt hơn tương tự nằm ở liên kết sau: Tài liệu PCL Các loại PointT


7

Các uniontừ khóa, trong khi vẫn được sử dụng trong C ++ 03 1 , chủ yếu là tàn dư của những ngày C. Vấn đề rõ ràng nhất là nó chỉ hoạt động với POD 1 .

Tuy nhiên, ý tưởng về liên minh vẫn còn và thực sự các thư viện Boost có một lớp giống như liên minh:

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

Trong đó có hầu hết các lợi ích của union(nếu không phải tất cả) và thêm:

  • khả năng sử dụng chính xác các loại không phải POD
  • loại an toàn tĩnh

Trong thực tế, người ta đã chứng minh rằng nó tương đương với sự kết hợp của union+ enumvà được đánh giá là nó nhanh như vậy (trong khi đó boost::anylà nhiều hơn dynamic_cast, vì nó sử dụng RTTI).

1 Các hiệp hội đã được nâng cấp trong C ++ 11 ( các hiệp hội không bị hạn chế ) và hiện có thể chứa các đối tượng có hàm hủy, mặc dù người dùng phải gọi hàm hủy theo cách thủ công (trên thành viên liên minh hiện đang hoạt động). Nó vẫn dễ sử dụng hơn các biến thể.


Điều này không còn đúng trong các phiên bản gần đây hơn của c ++. Xem câu trả lời của jrsala, ví dụ.
Andrew

@Andrew: Tôi đã cập nhật câu trả lời để đề cập rằng C ++ 11, với các hiệp hội không hạn chế, các loại được phép với các hàm hủy được lưu trữ trong union. Tôi vẫn giữ lập trường của mình rằng bạn thực sự tốt hơn nhiều khi sử dụng các công đoàn được gắn thẻ, chẳng hạn như boost::variantcố gắng tự mình sử dụng các công đoàn. Có quá nhiều hành vi không xác định xung quanh các công đoàn mà cơ hội của bạn để làm cho đúng là rất nhiều.
Matthieu M.

3

Từ bài viết Wikipedia về công đoàn :

Sự hữu ích chính của liên minh là bảo tồn không gian , vì nó cung cấp một cách để cho nhiều loại khác nhau được lưu trữ trong cùng một không gian. Liên hiệp cũng cung cấp đa hình thô . Tuy nhiên, không có kiểm tra các loại, do đó, tùy thuộc vào lập trình viên để chắc chắn rằng các trường thích hợp được truy cập trong các ngữ cảnh khác nhau. Trường có liên quan của một biến liên minh thường được xác định bởi trạng thái của các biến khác, có thể trong một cấu trúc kèm theo.

Một thành ngữ lập trình C phổ biến sử dụng các hiệp hội để thực hiện cái mà C ++ gọi là reinterpret_cast, bằng cách gán cho một trường của liên kết và đọc từ một trường khác, như được thực hiện trong mã phụ thuộc vào biểu diễn thô của các giá trị.


2

Trong những ngày đầu tiên của C (ví dụ như tài liệu năm 1974), tất cả các cấu trúc đã chia sẻ một không gian tên chung cho các thành viên của họ. Mỗi tên thành viên được liên kết với một loại và một phần bù; nếu "wd_woozle" là "int" ở offset 12, thì được đưa ra một con trỏ pthuộc bất kỳ loại cấu trúc nào, p->wd_woozlesẽ tương đương với *(int*)(((char*)p)+12). Ngôn ngữ yêu cầu tất cả các thành viên của tất cả các loại cấu trúc có tên duy nhất ngoại trừ việc nó cho phép sử dụng lại tên thành viên một cách rõ ràng trong trường hợp mọi cấu trúc nơi chúng được sử dụng coi chúng như một chuỗi ban đầu chung.

Thực tế là các loại cấu trúc có thể được sử dụng một cách bừa bãi khiến cho các cấu trúc có thể hoạt động như thể chúng chứa các trường chồng chéo. Ví dụ, định nghĩa đã cho:

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

mã có thể khai báo cấu trúc kiểu "float1" và sau đó sử dụng "thành viên" b0 ... b3 để truy cập các byte riêng lẻ trong đó. Khi ngôn ngữ được thay đổi để mỗi cấu trúc sẽ nhận được một không gian tên riêng cho các thành viên của nó, mã dựa trên khả năng truy cập vào nhiều thứ sẽ bị phá vỡ. Các giá trị tách biệt các không gian tên cho các loại cấu trúc khác nhau là đủ để yêu cầu thay đổi mã đó để phù hợp với nó, nhưng giá trị của các kỹ thuật đó là đủ để biện minh cho việc mở rộng ngôn ngữ để tiếp tục hỗ trợ nó.

Mã vốn đã được viết để khai thác khả năng truy cập lưu trữ trong vòng một struct float1như thể nó đã được một struct byte4có thể được thực hiện để làm việc trong các ngôn ngữ mới bằng cách thêm một bản tuyên bố: union f1b4 { struct float1 ff; struct byte4 bb; };, tuyên bố đối tượng as type union f1b4;chứ không phải là struct float1, và thay thế các truy cập đến f0, b0, b1, vv . với ff.f0, bb.b0, bb.b1, vv trong khi có những cách tốt hơn mã như vậy có thể đã được hỗ trợ, các unioncách tiếp cận ít nhất phần nào khả thi, ít nhất là với những giải thích C89-kỷ nguyên của các quy tắc răng cưa.


1

Hãy nói rằng bạn có n loại cấu hình khác nhau (chỉ là một tập hợp các biến xác định tham số). Bằng cách sử dụng kiểu liệt kê các loại cấu hình, bạn có thể xác định cấu trúc có ID của loại cấu hình, cùng với sự kết hợp của tất cả các loại cấu hình khác nhau.

Bằng cách này, bất cứ nơi nào bạn vượt qua cấu hình đều có thể sử dụng ID để xác định cách diễn giải dữ liệu cấu hình, nhưng nếu cấu hình quá lớn, bạn sẽ không bị buộc phải có cấu trúc song song cho mỗi loại không gian lãng phí.


1

Một sự gia tăng gần đây về tầm quan trọng, đã được nâng cao, của liên minh đã được đưa ra bởi Quy tắc bí danh nghiêm ngặt giới thiệu trong phiên bản gần đây của tiêu chuẩn C.

Bạn có thể sử dụng các công đoàn để đánh máy mà không vi phạm tiêu chuẩn C.
Chương trình này có hành vi không xác định (vì tôi đã giả định điều đó floatunsigned intcó cùng độ dài) nhưng không phải là hành vi không xác định (xem tại đây ).

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

Các quy tắc truy cập loại không chỉ có trong các phiên bản "gần đây" của Tiêu chuẩn. Mọi phiên bản của C đều bao gồm các quy tắc cơ bản giống nhau. Điều đã thay đổi là các trình biên dịch được sử dụng để xem chú thích "Mục đích của danh sách này là chỉ định các trường hợp trong đó một đối tượng có thể hoặc không thể được đặt bí danh." như chỉ ra rằng quy tắc này không có nghĩa là áp dụng trong các trường hợp không liên quan đến răng cưa như được viết , nhưng giờ đây họ coi nó như một lời mời viết lại mã để tạo bí danh ở những nơi không có.
supercat

1

Tôi muốn thêm một ví dụ thực tế tốt để sử dụng công cụ tính toán / trình thông dịch công thức thực hiện hoặc sử dụng một số loại tính toán (ví dụ: bạn muốn sử dụng có thể sửa đổi trong các phần thời gian chạy của công thức tính toán - giải phương trình số - chỉ ví dụ). Vì vậy, bạn có thể muốn xác định số / hằng số thuộc các loại khác nhau (số nguyên, dấu phẩy động, thậm chí số phức) như thế này:

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

Vì vậy, bạn đang tiết kiệm bộ nhớ và điều quan trọng hơn - bạn tránh mọi phân bổ động cho số lượng cực lớn (nếu bạn sử dụng nhiều số xác định thời gian chạy) của các đối tượng nhỏ (so với triển khai thông qua kế thừa / đa hình lớp). Nhưng điều thú vị hơn, bạn vẫn có thể sử dụng sức mạnh của đa hình C ++ (ví dụ nếu bạn là người hâm mộ của việc gửi đi hai lần;) với kiểu cấu trúc này. Chỉ cần thêm con trỏ giao diện "giả" vào lớp cha của tất cả các loại số làm trường của cấu trúc này, trỏ đến thể hiện này thay vì / ngoài kiểu thô hoặc sử dụng các con trỏ hàm C cũ tốt.

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

vì vậy bạn có thể sử dụng đa hình thay vì kiểm tra kiểu bằng switch (loại) - với việc thực hiện hiệu quả bộ nhớ (không phân bổ động các đối tượng nhỏ) - tất nhiên nếu bạn cần nó.


Điều này có thể hữu ích khi tạo một ngôn ngữ năng động. Vấn đề tôi nghĩ nó sẽ giải quyết là sửa đổi một biến có loại không xác định trong khối lượng mà không thực hiện sửa đổi N lần đó. Macro là khủng khiếp cho điều này và templating là hầu như không thể.
Andrew

0

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

Việc sử dụng của công đoàn là rất ít và xa. Trên hầu hết các máy tính, kích thước của một con trỏ và int thường giống nhau - điều này là do cả hai thường phù hợp với một thanh ghi trong CPU. Vì vậy, nếu bạn muốn thực hiện một cách nhanh chóng và bẩn thỉu một con trỏ đến một int hoặc theo cách khác, hãy khai báo một liên minh.

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

Một cách sử dụng khác của liên minh là trong một giao thức lệnh hoặc tin nhắn trong đó các tin nhắn có kích thước khác nhau được gửi và nhận. Mỗi loại thông báo sẽ chứa thông tin khác nhau nhưng mỗi loại sẽ có một phần cố định (có thể là một cấu trúc) và một phần bit biến. Đây là cách bạn có thể thực hiện nó ..

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

struct diropes80 {struct head fixed; tin nhắn char [80]; }
struct dirint10 {cố định đầu struct; tin nhắn int [10]; } struct dirack {đầu cố định; int ok; } union messagetype {
struct diropes50 m50; cấu trúc tin nhắn chuỗi80 m80; struct dirint10 i10; cấu trúc thông điệp ack; }

Trong thực tế, mặc dù các công đoàn có cùng kích thước, nhưng thật hợp lý khi chỉ gửi dữ liệu có ý nghĩa và không lãng phí không gian. Một thông điệp có kích thước chỉ 16 byte trong khi một chuỗi String80 là 92 byte. Vì vậy, khi một biến messagetype được khởi tạo, nó có trường kích thước được đặt theo loại đó. Điều này sau đó có thể được sử dụng bởi các chức năng khác để chuyển số byte chính xác.


0

Các hiệp hội cung cấp một cách để thao tác các loại dữ liệu khác nhau trong một vùng lưu trữ mà không nhúng bất kỳ thông tin độc lập nào của máy vào chương trình Chúng tương tự như các bản ghi biến thể trong pascal

Như một ví dụ như có thể được tìm thấy trong trình quản lý bảng biểu tượng trình biên dịch, giả sử rằng hằng số có thể là int, float hoặc con trỏ ký tự. Giá trị của một hằng số cụ thể phải được lưu trữ trong một biến của loại thích hợp, nhưng sẽ thuận tiện nhất cho việc quản lý bảng nếu giá trị chiếm cùng một lượng lưu trữ và được lưu trữ ở cùng một vị trí bất kể loại nào. Đây là mục đích của một liên minh - một biến duy nhất có thể nắm giữ một cách hợp pháp bất kỳ một trong một số loại. Cú pháp dựa trên cấu trúc:

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

Biến u sẽ đủ lớn để chứa lớn nhất trong ba loại; kích thước cụ thể là phụ thuộc vào việc thực hiện. Bất kỳ loại nào trong số này có thể được gán cho u và sau đó được sử dụng trong các biểu thức, miễn là việc sử dụng phù hợp

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.