Acho 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:
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?
}
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 caso g
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, veja h
: 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:
Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;
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
de Class
"
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,
Muitos domínios de problemas importantes requerem um grande número de objetos ou recursos limitados de memória. Nessas situações, conservar o espaço é muito importante, e uma união costuma ser a maneira perfeita de fazer isso. De fato, um caso de uso comum é a situação em que uma união nunca muda seu membro ativo durante sua vida útil. Ele pode ser construído, copiado e destruído como se fosse uma estrutura contendo apenas um membro. Uma aplicação típica disso seria criar uma coleção heterogênea de tipos não relacionados que não são alocados dinamicamente (talvez eles sejam construídos no local em um mapa ou membros de uma matriz).
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:
private:
B1 b1;
.
.
.
Bn bn;
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 para Bx
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:
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
};
Em seguida, para obter o conteúdo de uma instância de união data
, use a.get(TYPE_B2).b2
e os gostos, onde a
é uma A
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.