Qual é o propósito de usar um sindicato com apenas um membro?

89

Quando eu estava lendo o código-fonte seastar , notei que havia uma estrutura de união chamada tx_sideque tinha apenas um membro. Isso é algum truque para lidar com um determinado problema?

Para sua informação, colo a tx_sideestrutura abaixo:

union tx_side {
    tx_side() {}
    ~tx_side() {}
    void init() { new (&a) aa; }
    struct aa {
        std::deque<work_item*> pending_fifo;
    } a;
} _tx;
daoliker
fonte
11
Duplicata potencial de stackoverflow.com/questions/26572432/… .
precisa
5
@MaxLanghof Esta pergunta e as respostas correspondentes não mencionaram o propósito de usar essa estrutura de união.
Daoliker 27/11/19
Você tem um exemplo para o uso deste membro?
N314159 27/11/19
4
Por isso, na verdade, não usei meu voto obrigatório. Mas não tenho certeza do que exatamente você espera das respostas à sua pergunta que não segue diretamente das respostas de lá. Presumivelmente, o objetivo de usar em unionvez de structé uma ou mais das diferenças entre os dois. É uma técnica bastante obscura, portanto, a menos que o autor original desse código apareça, não tenho certeza de que alguém possa lhe dar uma resposta autorizada sobre qual problema eles esperam resolver com isso (se houver).
precisa
2
Meu melhor palpite é que a união é usada para atrasar a construção (que é um pouco inútil nesse caso) ou impedir a destruição (que leva ao vazamento de memória) do pending_fifo. Mas difícil dizer sem exemplo de uso.
Konstantin Stupnik 27/11/19

Respostas:

82

Porque tx_sideé uma união, tx_side()não inicializa / constrói automaticamente ae ~tx_side()não a destrói automaticamente. Isso permite um controle refinado sobre a vida útil ae pending_fifo, por meio de chamadas de destruidoras novas e manuais (um homem pobre std::optional).

Aqui está um exemplo:

#include <iostream>

struct A
{
    A() {std::cout << "A()\n";}
    ~A() {std::cout << "~A()\n";}
};

union B
{
    A a;
    B() {}
    ~B() {}
};

int main()
{
    B b;
}

Aqui, B b;imprime nada, porque anão é construído nem destruído.

Se Bfosse um struct, B()ligaria A()e ~B()ligaria ~A(), e você não seria capaz de impedir isso.

HolyBlackCat
fonte
23
@daoliker não necessariamente aleatório, mas imprevisível por você. O mesmo que qualquer outra variável não inicializada. Você não pode assumir que é aleatório; para tudo o que você sabe que poderia segurar a senha do usuário que você pediu-los previamente para digitar.
user253751
5
@daoliker: O comentário anterior é muito otimista. Bytes aleatórios teriam valores no intervalo de 0 a 255, mas se você ler um byte não inicializado em um, intpoderá obter 0xCCCCCCCC. Ler dados não inicializados é Comportamento indefinido, e o que pode acontecer é que o compilador simplesmente descarta a tentativa. Isto não é apenas teoria. O Debian cometeu esse erro exato e interrompeu a implementação do OpenSSL. Eles tinham alguns bytes aleatórios reais, adicionaram uma variável não inicializada e o compilador disse "bem, o resultado é indefinido, portanto pode ser zero". Zero obviamente não é mais aleatório.
MSalters
11
@MSalters: Você tem uma fonte para esta reivindicação? Porque o que posso encontrar sugere que não foi o que aconteceu: não foi o compilador que o removeu, mas os desenvolvedores. Honestamente, eu ficaria surpreso se algum escritor de compilador tomou uma decisão tão incrivelmente ruim. (veja stackoverflow.com/questions/45395435/... )
Jack Aidley
5
@JackAidley: Qual afirmação precisa? Você tem um bom link, parece que eu tenho a história invertida. O OpenSSL errou a lógica e usou uma variável não inicializada de forma que um compilador pudesse assumir legalmente qualquer resultado. O Debian percebeu isso corretamente, mas quebrou a correção. Quanto aos "compiladores que tomam decisões tão ruins"; eles não tomam essa decisão. O comportamento indefinido é a má decisão. Otimizadores são projetados para serem executados no código correto. O GCC, por exemplo, não assume ativamente nenhum estouro assinado. Assumir "nenhum dado não inicializado" é igualmente razoável; pode ser usado para eliminar caminhos de código impossíveis.
MSalters
11
@JackAidley Encontrei problemas semelhantes aos mencionados pelo @MSalters no meu próprio código; Eu assumi erroneamente que uma variável não inicializada estaria vazia e fiquei desconcertada quando uma != 0comparação subsequente foi verdadeira. Desde então, adicionei sinalizadores de compilador para tratar variáveis ​​não inicializadas como erros para garantir que não cairei nessa armadilha novamente.
Tom Lint
0

Em palavras simples, a menos que seja atribuído / inicializado explicitamente um valor, a união de membro único não inicializa a memória alocada. Essa funcionalidade pode ser alcançada std:: optionalno c ++ 17.

Sitesh
fonte
2
Essa é uma resposta enganosa. A união com apenas um membro terá o mesmo tamanho que o membro. Essa memória simplesmente não será inicializada até que o membro seja inicializado.
Kirill Dmitrenko