Eu aprendi, mas realmente não tenho sindicatos. Todo texto em C ou C ++ que eu passo apresento os apresenta (às vezes de passagem), mas eles tendem a dar muito poucos exemplos práticos de por que ou onde usá-los. Quando os sindicatos seriam úteis em um caso moderno (ou até legado)? Minhas duas únicas suposições seriam programar microprocessadores quando você tiver um espaço muito limitado para trabalhar ou quando estiver desenvolvendo uma API (ou algo semelhante) e desejar forçar o usuário final a ter apenas uma instância de vários objetos / tipos em um tempo. Essas duas suposições estão quase certas?
133
Respostas:
Os sindicatos geralmente são usados com a companhia de um discriminador: uma variável que indica qual dos campos da união é válido. Por exemplo, digamos que você queira criar seu próprio tipo de variante :
Então você usaria como:
Este é realmente um idioma bastante comum, especialmente em internos do Visual Basic.
Para um exemplo real, consulte a união SDL_Event da SDL . ( código fonte atual aqui ). Há um
type
campo na parte superior da união e o mesmo campo é repetido em cada estrutura de evento SDL_ *. Em seguida, para manipular o evento correto, você precisa verificar o valor dotype
campo.Os benefícios são simples: existe um único tipo de dados para lidar com todos os tipos de eventos sem usar memória desnecessária.
fonte
struct object
em github.com/petermichaux/bootstrap-scheme/blob/v0.21/scheme.cAcho as uniões C ++ bastante legais. Parece que as pessoas geralmente pensam apenas no caso de uso em que se deseja alterar o valor de uma instância de união "no local" (que, ao que parece, serve apenas para economizar memória ou realizar conversões duvidosas).
De fato, os sindicatos podem ter grande poder como ferramenta de engenharia de software, mesmo quando você nunca altera o valor de nenhuma instância de união .
Caso de uso 1: o camaleão
Com os sindicatos, é possível reagrupar várias classes arbitrárias sob uma denominação, o que não deixa de ter semelhanças com o caso de uma classe base e de suas classes derivadas. O que muda, no entanto, é o que você pode e não pode fazer com uma determinada instância de união:
Parece que o programador precisa ter certeza do tipo de conteúdo de uma determinada instância de união quando deseja usá-lo. É o caso da função
f
acima. No entanto, se uma função receber uma instância de união como um argumento passado, como é o casog
acima, ela não saberá o que fazer com ela. O mesmo se aplica às funções que retornam uma instância de união, vejah
: como o chamador sabe o que está dentro?Se uma instância de união nunca é passada como argumento ou como valor de retorno, é provável que tenha uma vida muito monótona, com picos de excitação quando o programador optar por alterar seu conteúdo:
E esse é o caso de uso mais (des) popular dos sindicatos. Outro caso de uso é quando uma instância de união vem com algo que informa seu tipo.
Caso de uso 2: "Prazer em conhecê-lo, eu sou
object
deClass
"Suponha que um programador eleito para sempre emparelhar uma instância de união com um descritor de tipo (deixarei ao critério do leitor imaginar uma implementação para um desses objetos). Isso anula o objetivo da própria união se o que o programador deseja é economizar memória e que o tamanho do descritor de tipo não seja desprezível em relação ao da união. Mas vamos supor que seja crucial que a instância da união possa ser passada como argumento ou como valor de retorno com o receptor ou chamador sem saber o que está dentro.
Em seguida, o programador deve escrever uma
switch
declaração de fluxo de controle para diferenciar Bruce Wayne de uma vara de madeira ou algo equivalente. Não é tão ruim quando existem apenas dois tipos de conteúdo no sindicato, mas obviamente o sindicato não é mais escalável.Caso de uso 3:
Como os autores de uma recomendação para a norma ISO C ++ colocaram em 2008,
E agora, um exemplo, com um diagrama de classes UML:
A situação em inglês simples: um objeto da classe A pode ter objetos de qualquer classe entre B1, ..., Bn e no máximo um de cada tipo, com n sendo um número bastante grande, digamos pelo menos 10.
Não queremos adicionar campos (membros de dados) a A da seguinte maneira:
porque n pode variar (podemos adicionar classes Bx à mistura) e porque isso causaria uma confusão nos construtores e porque os objetos A ocupariam muito espaço.
Poderíamos usar um contêiner maluco de
void*
ponteiros paraBx
objetos com moldes para recuperá-los, mas isso é fugaz e, portanto, no estilo C ... mas mais importante que nos deixaria com a vida útil de muitos objetos alocados dinamicamente para gerenciar.Em vez disso, o que pode ser feito é o seguinte:
Em seguida, para obter o conteúdo de uma instância de união
data
, usea.get(TYPE_B2).b2
e os gostos, ondea
é umaA
instância de classe .Isso é ainda mais poderoso, pois os sindicatos são irrestritos no C ++ 11. Consulte o documento vinculado acima ou este artigo para obter detalhes.
fonte
Um exemplo está no domínio incorporado, onde cada bit de um registro pode significar algo diferente. Por exemplo, uma união de um número inteiro de 8 bits e uma estrutura com 8 campos de bits separados de 1 bit permite alterar um bit ou o byte inteiro.
fonte
void*
es explícitos ou máscaras e mudanças.REG |= MASK
eREG &= ~MASK
. Se isso for propenso a erros, coloque-os em um#define SETBITS(reg, mask)
e#define CLRBITS(reg, mask)
. Não confie em que o compilador para obter os bits em uma determinada ordem ( stackoverflow.com/questions/1490092/... )Herb Sutter escreveu no GOTW cerca de seis anos atrás, com ênfase adicionada:
E para um exemplo menos útil, veja a pergunta longa, mas inconclusiva gcc, alias estrito e transmissão através de um sindicato .
fonte
Bem, um exemplo de caso de uso em que posso pensar é o seguinte:
Você pode acessar as partes separadas de 8 bits desse bloco de dados de 32 bits; no entanto, prepare-se para ser potencialmente picado por endianness.
Este é apenas um exemplo hipotético, mas sempre que você deseja dividir dados em um campo em partes componentes como essa, você pode usar uma união.
Dito isto, há também um método que é endian-safe:
Por exemplo, uma vez que essa operação binária será convertida pelo compilador na endianness correta.
fonte
Alguns usos para sindicatos:
Economizando espaço de armazenamento quando os campos dependem de certos valores:
Grep os arquivos de inclusão para uso com seu compilador. Você encontrará dezenas a centenas de usos de
union
:fonte
As uniões são úteis ao lidar com dados em nível de bytes (nível baixo).
Um dos meus usos recentes foi na modelagem de endereços IP, que se parece com abaixo:
fonte
Um exemplo quando eu usei uma união:
isso me permite acessar meus dados como uma matriz ou os elementos.
Eu usei uma união para que os termos diferentes aponte para o mesmo valor. No processamento de imagens, esteja eu trabalhando em colunas ou largura ou no tamanho na direção X, isso pode se tornar confuso. Para solucionar esse problema, eu uso uma união para saber quais descrições estão juntas.
fonte
As uniões fornecem polimorfismo em C.
fonte
void*
fez isso ^^Um uso brilhante da união é o alinhamento de memória, encontrado no código-fonte PCL (Point Cloud Library). A estrutura de dados única na API pode ter como alvo duas arquiteturas: CPU com suporte a SSE e CPU sem suporte a SSE. Por exemplo: a estrutura de dados do PointXYZ é
Os 3 flutuadores são preenchidos com um flutuador adicional para o alinhamento do SSE. Então para
O usuário pode acessar point.data [0] ou point.x (dependendo do suporte do SSE) para acessar, por exemplo, a coordenada x. Mais detalhes semelhantes sobre o uso estão disponíveis no seguinte link: Documentação PCL Tipos PointT
fonte
A
union
palavra-chave, embora ainda seja usada no C ++ 03 1 , é principalmente um remanescente dos dias C. O problema mais evidente é que ele funciona apenas com o POD 1 .A idéia da união, no entanto, ainda está presente, e de fato as bibliotecas Boost apresentam uma classe semelhante à união:
Qual tem a maioria dos benefícios do
union
(se não todos) e acrescenta:Na prática, demonstrou-se que era equivalente a uma combinação de
union
+enum
e comparou-se que era tão rápido (emboraboost::any
seja mais do domínio dedynamic_cast
, pois usa RTTI).1 As uniões foram atualizadas no C ++ 11 ( uniões irrestritas ) e agora podem conter objetos com destruidores, embora o usuário precise invocar o destruidor manualmente (no membro da união atualmente ativo). Ainda é muito mais fácil usar variantes.
fonte
boost::variant
do que tentando usar uniões por conta própria. Há comportamentos indefinidos demais ao redor dos sindicatos que suas chances de acertar são péssimas.Do artigo da Wikipedia sobre sindicatos :
fonte
Nos primeiros dias de C (por exemplo, como documentado em 1974), todas as estruturas compartilhavam um espaço de nome comum para seus membros. Cada nome de membro foi associado a um tipo e um deslocamento; se "wd_woozle" fosse um "int" no deslocamento 12, então, dado um ponteiro
p
de qualquer tipo de estrutura,p->wd_woozle
seria equivalente a*(int*)(((char*)p)+12)
. A linguagem exigia que todos os membros de todos os tipos de estruturas tivessem nomes exclusivos, exceto que permitia explicitamente a reutilização de nomes de membros nos casos em que todas as estruturas em que foram usadas as tratavam como uma sequência inicial comum.O fato de os tipos de estrutura poderem ser utilizados com promiscuidade tornou possível que as estruturas se comportassem como se contivessem campos sobrepostos. Por exemplo, dadas definições:
O código pode declarar uma estrutura do tipo "float1" e, em seguida, usar "members" b0 ... b3 para acessar os bytes individuais nela. Quando o idioma era alterado para que cada estrutura recebesse um espaço para nome separado para seus membros, o código que dependia da capacidade de acessar as coisas de várias maneiras seria interrompido. Os valores da separação de namespaces para diferentes tipos de estrutura foram suficientes para exigir que esse código fosse alterado para acomodá-lo, mas o valor de tais técnicas foi suficiente para justificar a extensão do idioma para continuar a suportá-lo.
Código que tinha sido escrito para explorar a capacidade de acessar o armazenamento dentro de um
struct float1
como se fosse umstruct byte4
poderia ser feito para trabalhar no novo idioma, adicionando uma declaração:union f1b4 { struct float1 ff; struct byte4 bb; };
, declarando objetos como tipounion f1b4;
, em vez destruct float1
, e substituindo acessosf0
,b0
,b1
, etc . comff.f0
,bb.b0
,bb.b1
, etc. Enquanto existem melhores maneiras tal código poderia ter sido suportados, aunion
abordagem era pelo menos um pouco viável, pelo menos, com interpretações C89 da era das regras aliasing.fonte
Digamos que você tenha n tipos diferentes de configurações (apenas um conjunto de variáveis que define parâmetros). Usando uma enumeração dos tipos de configuração, você pode definir uma estrutura que tenha o ID do tipo de configuração, juntamente com uma união de todos os diferentes tipos de configurações.
Dessa forma, onde quer que você passe a configuração, use o ID para determinar como interpretar os dados de configuração, mas se as configurações fossem enormes, você não seria forçado a ter estruturas paralelas para cada tipo potencial de perda de espaço.
fonte
Um recente impulso à importância já elevada da união foi dado pela Regra Estrita de Alias introduzida na versão recente do padrão C.
Você pode usar os sindicatos para fazer punções sem violar o padrão C.
Este programa tem um comportamento não especificado (porque eu assumi isso
float
eunsigned int
tem o mesmo comprimento), mas não um comportamento indefinido (veja aqui ).fonte
Gostaria de acrescentar um bom exemplo prático para usar a calculadora / interpretadora de fórmula de implementação de união ou usar algum tipo disso na computação (por exemplo, você deseja usar modificável durante partes de tempo de execução de suas fórmulas computacionais - resolvendo a equação numericamente - apenas por exemplo). Portanto, convém definir números / constantes de diferentes tipos (número inteiro, ponto flutuante e até números complexos) como este:
Portanto, você está economizando memória e o que é mais importante - evita alocações dinâmicas para quantidades provavelmente extremas (se você usa muitos números definidos em tempo de execução) de objetos pequenos (em comparação com implementações por herança de classe / polimorfismo). Mas o mais interessante é que você ainda pode usar o poder do polimorfismo C ++ (se você é fã de despacho duplo, por exemplo;) com esse tipo de estrutura. Basta adicionar o ponteiro de interface "fictício" à classe pai de todos os tipos de números como um campo dessa estrutura, apontando para esta instância vez de / além do tipo bruto, ou use bons e antigos ponteiros de função C.
para que você possa usar o polimorfismo em vez das verificações de tipo com o switch (tipo) - com implementação eficiente em memória (sem alocação dinâmica de objetos pequenos) - se necessário, é claro.
fonte
Em http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :
fonte
Os sindicatos fornecem uma maneira de manipular diferentes tipos de dados em uma única área de armazenamento sem incorporar nenhuma informação independente da máquina no programa. Eles são análogos aos registros variantes no pascal.
Como exemplo, como pode ser encontrado em um gerenciador de tabela de símbolos do compilador, suponha que uma constante possa ser um int, um float ou um ponteiro de caractere. O valor de uma constante específica deve ser armazenado em uma variável do tipo apropriado, mas é mais conveniente para o gerenciamento de tabelas se o valor ocupar a mesma quantidade de armazenamento e for armazenado no mesmo local, independentemente do seu tipo. Esse é o objetivo de uma união - uma única variável que pode legitimamente conter qualquer um de vários tipos. A sintaxe é baseada em estruturas:
A variável u será grande o suficiente para conter o maior dos três tipos; o tamanho específico depende da implementação. Qualquer um desses tipos pode ser atribuído a u e depois usado em expressões, desde que o uso seja consistente
fonte