Saya telah belajar tetapi tidak benar-benar mendapatkan serikat pekerja. Setiap teks C atau C ++ yang saya lalui memperkenalkan mereka (kadang-kadang secara sepintas), tetapi mereka cenderung memberikan sedikit contoh praktis mengapa atau di mana menggunakannya. Kapan serikat akan berguna dalam kasus modern (atau bahkan warisan)? Hanya dua tebakan saya yang akan memprogram mikroprosesor ketika Anda memiliki ruang yang sangat terbatas untuk bekerja, atau ketika Anda sedang mengembangkan API (atau yang serupa) dan Anda ingin memaksa pengguna akhir untuk hanya memiliki satu instance dari beberapa objek / tipe di satu kali. Apakah kedua tebakan ini mendekati benar?
133
Jawaban:
Serikat pekerja biasanya digunakan dengan perusahaan diskriminator: variabel yang menunjukkan bidang mana dari serikat pekerja yang valid. Misalnya, katakanlah Anda ingin membuat jenis Varian Anda sendiri :
Maka Anda akan menggunakannya seperti:
Ini sebenarnya idiom yang cukup umum, khususnya pada Visual Basic internal.
Untuk contoh nyata, lihat penyatuan SDL_Event SDL . ( kode sumber aktual di sini ). Ada
type
bidang di bagian atas serikat, dan bidang yang sama diulang pada setiap SDL_ * Acara struktur. Kemudian, untuk menangani acara yang benar, Anda perlu memeriksa nilaitype
bidang.Manfaatnya sederhana: ada satu tipe data tunggal untuk menangani semua jenis acara tanpa menggunakan memori yang tidak perlu.
sumber
struct object
dalam github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.cSaya menemukan serikat C ++ cukup keren. Tampaknya orang biasanya hanya memikirkan use case di mana seseorang ingin mengubah nilai instance serikat "di tempat" (yang, tampaknya, berfungsi hanya untuk menghemat memori atau melakukan konversi yang meragukan).
Faktanya, serikat pekerja dapat memiliki kekuatan besar sebagai alat rekayasa perangkat lunak, bahkan ketika Anda tidak pernah mengubah nilai instance serikat apa pun .
Gunakan case 1: bunglon
Dengan serikat pekerja, Anda dapat mengelompokkan kembali sejumlah kelas sewenang-wenang di bawah satu denominasi, yang bukan tanpa kesamaan dengan kasus kelas dasar dan kelas turunannya. Namun, perubahan apa yang bisa dan tidak bisa Anda lakukan dengan contoh serikat pekerja tertentu:
Tampaknya programmer harus yakin dengan jenis konten instance serikat tertentu ketika dia ingin menggunakannya. Ini adalah kasus dalam fungsi di
f
atas. Namun, jika suatu fungsi menerima instance serikat sebagai argumen yang disahkan, seperti halnya dengan dig
atas, maka tidak akan tahu apa yang harus dilakukan dengan itu. Hal yang sama berlaku untuk fungsi mengembalikan instance serikat, lihath
: bagaimana penelepon tahu apa yang ada di dalamnya?Jika instance serikat tidak pernah dianggap sebagai argumen atau sebagai nilai balik, maka itu pasti memiliki kehidupan yang sangat monoton, dengan lonjakan kegembiraan ketika programmer memilih untuk mengubah kontennya:
Dan itulah kasus penggunaan serikat yang paling populer. Kasus penggunaan lain adalah ketika contoh serikat datang dengan sesuatu yang memberitahu Anda jenisnya.
Gunakan case 2: "Senang bertemu Anda, saya
object
, dariClass
"Misalkan seorang programmer terpilih untuk selalu memasangkan instance union dengan deskriptor tipe (saya akan menyerahkannya pada kebijaksanaan pembaca untuk membayangkan implementasi untuk satu objek seperti itu). Ini mengalahkan tujuan serikat itu sendiri jika apa yang diinginkan programmer adalah untuk menghemat memori dan bahwa ukuran deskriptor jenis tidak dapat diabaikan sehubungan dengan serikat. Tetapi mari kita anggap bahwa sangat penting bahwa contoh serikat pekerja dapat disahkan sebagai argumen atau sebagai nilai pengembalian dengan callee atau penelepon yang tidak mengetahui apa yang ada di dalamnya.
Kemudian programmer harus menulis
switch
pernyataan aliran kontrol untuk memberi tahu Bruce Wayne terpisah dari tongkat kayu, atau sesuatu yang setara. Ini tidak terlalu buruk ketika hanya ada dua jenis konten di serikat tetapi jelas, serikat tidak skala lagi.Gunakan case 3:
Sebagai penulis rekomendasi untuk Standar ISO C ++ mengembalikannya pada tahun 2008,
Dan sekarang, sebuah contoh, dengan diagram kelas UML:
Situasi dalam bahasa Inggris biasa: objek kelas A dapat memiliki objek kelas apa pun di antara B1, ..., Bn, dan paling banyak satu dari setiap tipe, dengan n menjadi angka yang cukup besar, katakan setidaknya 10.
Kami tidak ingin menambahkan bidang (anggota data) ke A seperti:
karena n mungkin bervariasi (kami mungkin ingin menambahkan kelas Bx ke dalam campuran), dan karena ini akan menyebabkan kekacauan dengan konstruktor dan karena objek A akan memakan banyak ruang.
Kita bisa menggunakan wadah aneh dari
void*
pointer keBx
objek dengan gips untuk mengambilnya, tapi itu gaya C dan jelek ... tapi yang lebih penting itu akan meninggalkan kita dengan masa hidup dari banyak objek yang dialokasikan secara dinamis untuk dikelola.Sebaliknya, yang bisa dilakukan adalah ini:
Kemudian, untuk mendapatkan konten instance serikat
data
, Anda menggunakana.get(TYPE_B2).b2
dan suka, di mana instancea
kelasA
.Ini semakin kuat karena serikat pekerja tidak dibatasi dalam C ++ 11. Lihat dokumen yang ditautkan ke atas atau artikel ini untuk detailnya.
sumber
Salah satu contohnya adalah di bidang tertanam, di mana setiap bit register dapat berarti sesuatu yang berbeda. Sebagai contoh, penyatuan integer 8-bit dan struktur dengan 8 bitfield 1-bit yang terpisah memungkinkan Anda untuk mengubah satu bit atau seluruh byte.
sumber
void*
s eksplisit atau topeng dan pergeseran.REG |= MASK
danREG &= ~MASK
. Jika itu rawan kesalahan maka letakkan di a#define SETBITS(reg, mask)
dan#define CLRBITS(reg, mask)
. Jangan mengandalkan kompiler untuk mendapatkan bit dalam urutan tertentu ( stackoverflow.com/questions/1490092/… )Herb Sutter menulis di GOTW sekitar enam tahun lalu, dengan penekanan ditambahkan:
Dan untuk contoh yang kurang bermanfaat, lihat pertanyaan panjang tapi tidak meyakinkan gcc, aliasing, dan casting melalui serikat pekerja .
sumber
Nah, satu contoh use case yang bisa saya pikirkan adalah ini:
Anda kemudian dapat mengakses bagian terpisah 8-bit dari blok data 32-bit itu; namun, bersiaplah untuk berpotensi digigit oleh endianness.
Ini hanyalah satu contoh hipotetis, tetapi setiap kali Anda ingin membagi data dalam bidang menjadi bagian-bagian komponen seperti ini, Anda bisa menggunakan gabungan.
Yang mengatakan, ada juga metode yang aman-endian:
Sebagai contoh, karena operasi biner itu akan dikonversi oleh kompiler ke endianness yang benar.
sumber
Beberapa kegunaan untuk serikat pekerja:
Menghemat ruang penyimpanan saat bidang bergantung pada nilai-nilai tertentu:
Grep file include untuk digunakan dengan kompiler Anda. Anda akan menemukan puluhan hingga ratusan penggunaan
union
:sumber
Serikat pekerja berguna ketika berurusan dengan data level-byte (level rendah).
Salah satu penggunaan terakhir saya adalah pada pemodelan alamat IP yang terlihat seperti di bawah ini:
sumber
Contoh ketika saya menggunakan serikat pekerja:
ini memungkinkan saya untuk mengakses data saya sebagai array atau elemen.
Saya telah menggunakan serikat pekerja untuk menunjuk istilah yang berbeda ke nilai yang sama. Dalam pemrosesan gambar, apakah saya sedang mengerjakan kolom atau lebar atau ukuran dalam arah X, itu bisa membingungkan. Untuk mengatasi masalah ini, saya menggunakan gabungan sehingga saya tahu deskripsi mana yang cocok.
sumber
Serikat pekerja memberikan polimorfisme dalam C.
sumber
void*
melakukan itu ^^Penggunaan union yang brilian adalah penyelarasan memori, yang saya temukan dalam kode sumber PCL (Point Cloud Library). Struktur data tunggal dalam API dapat menargetkan dua arsitektur: CPU dengan dukungan SSE serta CPU tanpa dukungan SSE. Sebagai contoh: struktur data untuk PointXYZ adalah
3 pelampung diisi dengan pelampung tambahan untuk perataan SSE. Sehingga untuk
Pengguna dapat mengakses point.data [0] atau point.x (tergantung pada dukungan SSE) untuk mengakses katakanlah, koordinat x. Detail penggunaan yang lebih baik dan lebih mirip ada di tautan berikut: Dokumentasi PCL Jenis-jenis PointT
sumber
Kata
union
kunci, sementara masih digunakan dalam C ++ 03 1 , sebagian besar adalah sisa dari hari C. Masalah yang paling mencolok adalah bahwa ia hanya bekerja dengan POD 1 .Namun, gagasan serikat pekerja masih ada, dan memang perpustakaan Boost menampilkan kelas seperti serikat pekerja:
Yang memiliki sebagian besar manfaat
union
(jika tidak semua) dan menambahkan:Dalam praktiknya, telah ditunjukkan bahwa itu setara dengan kombinasi
union
+enum
, dan membandingkannya dengan cepat (sementaraboost::any
lebih merupakan ranahdynamic_cast
, karena menggunakan RTTI).1 Serikat ditingkatkan di C ++ 11 ( serikat tidak terbatas ), dan sekarang dapat berisi objek dengan destruktor, meskipun pengguna harus memanggil destruktor secara manual (pada anggota serikat aktif saat ini). Masih jauh lebih mudah menggunakan varian.
sumber
boost::variant
daripada mencoba menggunakan serikat sendiri. Ada terlalu banyak perilaku tak terdefinisi di sekitar serikat pekerja sehingga peluang Anda untuk memperbaikinya benar-benar buruk.Dari artikel Wikipedia tentang serikat pekerja :
sumber
Pada hari-hari awal C (misalnya seperti yang didokumentasikan pada tahun 1974), semua struktur berbagi ruang nama yang sama untuk anggota mereka. Setiap nama anggota dikaitkan dengan tipe dan offset; jika "wd_woozle" adalah "int" pada offset 12, maka diberi pointer
p
dari semua jenis struktur,p->wd_woozle
akan setara dengan*(int*)(((char*)p)+12)
. Bahasa mensyaratkan bahwa semua anggota dari semua jenis struktur memiliki nama unik kecuali bahwa itu secara eksplisit memungkinkan penggunaan kembali nama anggota dalam kasus di mana setiap struct di mana mereka digunakan memperlakukan mereka sebagai urutan awal umum.Fakta bahwa tipe-tipe struktur dapat digunakan secara sembarangan memungkinkan struktur berperilaku seolah-olah mengandung bidang-bidang yang tumpang tindih. Misalnya, diberikan definisi:
kode dapat mendeklarasikan struktur tipe "float1" dan kemudian menggunakan "anggota" b0 ... b3 untuk mengakses masing-masing byte di dalamnya. Ketika bahasa diubah sehingga setiap struktur akan menerima namespace terpisah untuk anggotanya, kode yang bergantung pada kemampuan untuk mengakses berbagai hal akan rusak. Nilai memisahkan ruang nama untuk jenis struktur yang berbeda sudah cukup untuk mengharuskan kode tersebut diubah untuk mengakomodasi itu, tetapi nilai teknik tersebut cukup untuk membenarkan perluasan bahasa untuk terus mendukungnya.
Kode yang telah ditulis untuk mengeksploitasi kemampuan untuk mengakses penyimpanan dalam
struct float1
seolah-olah itu adalah sebuahstruct byte4
bisa dibuat untuk bekerja dalam bahasa baru dengan menambahkan deklarasi:union f1b4 { struct float1 ff; struct byte4 bb; };
, menyatakan objek sebagai jenisunion f1b4;
bukanstruct float1
, dan mengganti akses kef0
,b0
,b1
, dll . denganff.f0
,bb.b0
,bb.b1
, dll Meskipun ada cara yang lebih baik kode tersebut bisa saja yang didukung,union
pendekatan setidaknya agak bisa diterapkan, setidaknya dengan interpretasi C89-era aturan aliasing.sumber
Katakanlah Anda memiliki berbagai jenis konfigurasi (hanya menjadi seperangkat variabel yang menentukan parameter). Dengan menggunakan enumerasi tipe konfigurasi, Anda dapat menentukan struktur yang memiliki ID tipe konfigurasi, bersama dengan gabungan semua tipe konfigurasi yang berbeda.
Dengan cara ini, di mana pun Anda lulus konfigurasi dapat menggunakan ID untuk menentukan cara menginterpretasikan data konfigurasi, tetapi jika konfigurasi itu besar Anda tidak akan dipaksa untuk memiliki struktur paralel untuk setiap tipe potensial ruang buang.
sumber
Satu dorongan baru-baru ini pada pentingnya serikat pekerja telah diberikan oleh Peraturan Aliasing Ketat diperkenalkan dalam versi terbaru dari standar C.
Anda dapat menggunakan serikat pekerja untuk mengetik-punning tanpa melanggar standar C.
Program ini memiliki perilaku yang tidak ditentukan (karena saya berasumsi
float
danunsigned int
memiliki panjang yang sama) tetapi tidak memiliki perilaku yang tidak jelas (lihat di sini ).sumber
Saya ingin menambahkan satu contoh praktis yang baik untuk menggunakan serikat pekerja - menerapkan rumus kalkulator / juru bahasa atau menggunakan semacam itu dalam perhitungan (misalnya, Anda ingin menggunakan bagian yang dapat dimodifikasi saat run-time dari rumus komputasi Anda - menyelesaikan persamaan secara numerik - hanya dengan menghitung sebagai contoh). Jadi, Anda mungkin ingin mendefinisikan bilangan / konstanta dari tipe yang berbeda (integer, floating-point, bahkan bilangan kompleks) seperti ini:
Jadi Anda menghemat memori dan apa yang lebih penting - Anda menghindari alokasi dinamis untuk jumlah yang mungkin ekstrem (jika Anda menggunakan banyak angka run-time) dari objek kecil (dibandingkan dengan implementasi melalui warisan kelas / polimorfisme). Tetapi yang lebih menarik, Anda masih dapat menggunakan kekuatan polimorfisme C ++ (jika Anda penggemar pengiriman ganda, misalnya;) dengan tipe struct ini. Cukup tambahkan pointer antarmuka "dummy" ke kelas induk dari semua jenis nomor sebagai bidang struct ini, menunjuk ke instance ini alih-alih / di samping tipe mentah, atau gunakan pointer fungsi C tua yang baik.
jadi Anda bisa menggunakan polimorfisme alih-alih mengetikkan centang dengan sakelar (tipe) - dengan implementasi yang efisien-memori (tidak ada alokasi dinamis objek kecil) - jika Anda memerlukannya, tentu saja.
sumber
Dari http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :
sumber
Serikat pekerja menyediakan cara untuk memanipulasi berbagai jenis data dalam satu area penyimpanan tanpa menanamkan informasi independen mesin apa pun dalam program. Mereka analog dengan catatan varian dalam pascal
Sebagai contoh seperti yang dapat ditemukan di manajer tabel simbol kompiler, misalkan konstanta dapat berupa int, float, atau penunjuk karakter. Nilai konstanta tertentu harus disimpan dalam variabel tipe yang tepat, namun akan lebih mudah bagi manajemen tabel jika nilainya menempati jumlah penyimpanan yang sama dan disimpan di tempat yang sama terlepas dari jenisnya. Ini adalah tujuan dari persatuan - variabel tunggal yang dapat secara sah menampung salah satu dari beberapa jenis. Sintaks didasarkan pada struktur:
Variabel u akan cukup besar untuk menampung yang terbesar dari ketiga jenis; ukuran spesifik tergantung pada implementasi. Setiap tipe ini dapat ditugaskan untuk Anda dan kemudian digunakan dalam ekspresi, selama penggunaannya konsisten
sumber