Quel est le but d'utiliser un syndicat avec un seul membre?

89

Lorsque je lisais le code source de Seastar , j'ai remarqué qu'il existe une structure d'union appelée tx_sidequi n'a qu'un seul membre. Est-ce un hack pour résoudre un certain problème?

Pour info, je colle la tx_sidestructure ci-dessous:

union tx_side {
    tx_side() {}
    ~tx_side() {}
    void init() { new (&a) aa; }
    struct aa {
        std::deque<work_item*> pending_fifo;
    } a;
} _tx;
daoliker
la source
1
Duplicata potentiel de stackoverflow.com/questions/26572432/… .
Max Langhof
5
@MaxLanghof Cette question et les réponses correspondantes n'ont pas mentionné le but de l'utilisation d'une telle structure syndicale.
daoliker
Avez-vous un exemple d'utilisation de ce membre?
n314159
4
C'est pourquoi je n'ai pas vraiment utilisé mon vote serré contraignant. Mais je ne sais pas exactement ce que vous attendez des réponses à votre question qui ne découlent pas directement des réponses là-bas. Vraisemblablement, le but d'utiliser unionau lieu de structest une ou plusieurs des différences entre les deux. C'est une technique assez obscure, donc à moins que l'auteur original de ce code ne vienne, je ne suis pas sûr que quelqu'un puisse vous donner une réponse faisant autorité quel problème il espère résoudre avec cela (le cas échéant).
Max Langhof
2
Ma meilleure supposition est que l'union est utilisée pour retarder la construction (ce qui est quelque peu inutile dans ce cas) ou empêcher la destruction (qui conduit à une fuite de mémoire) de pending_fifo. Mais difficile à dire sans exemple d'utilisation.
Konstantin Stupnik

Réponses:

82

Parce que tx_sideest une union, tx_side()n'initialise / construit pas automatiquement aet ~tx_side()ne la détruit pas automatiquement. Cela permet un contrôle fin sur la durée de vie aet pending_fifo, via le placement, de nouveaux appels de destructeur manuels (ceux d'un pauvre std::optional).

Voici un exemple:

#include <iostream>

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

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

int main()
{
    B b;
}

Ici, B b;n'imprime rien, car an'est ni construit ni détruit.

Si Bétait un struct, B()appellerait A()et ~B()appellerait ~A(), et vous ne pourriez pas empêcher cela.

HolyBlackCat
la source
23
@daoliker n'est pas nécessairement aléatoire, mais imprévisible par vous. Identique à toute autre variable non initialisée. Vous ne pouvez pas supposer que c'est aléatoire; pour tout ce que vous savez, il pourrait contenir le mot de passe de l'utilisateur que vous lui avez déjà demandé de taper.
user253751
5
@daoliker: Le commentaire précédent est trop optimiste. Les octets aléatoires auraient des valeurs comprises entre 0 et 255, mais si vous lisez un octet non initialisé dans un, intvous pouvez obtenir 0xCCCCCCCC. La lecture de données non initialisées est un comportement indéfini, et ce qui peut arriver, c'est que le compilateur rejette simplement la tentative. Ce n'est pas seulement de la théorie. Debian a fait cette erreur exacte et a cassé leur implémentation d'OpenSSL. Ils avaient de vrais octets aléatoires, ont ajouté une variable non initialisée, et le compilateur a dit "eh bien le résultat n'est pas défini, donc il pourrait aussi bien être zéro". Zéro n'est évidemment plus aléatoire.
MSalters
1
@MSalters: Avez-vous une source pour cette réclamation? Parce que ce que je peux trouver suggère que ce n'est pas ce qui s'est passé: ce n'est pas le compilateur qui l'a supprimé, mais les développeurs. Honnêtement, je serais étonné si un auteur de compilateur prenait une décision aussi mauvaise. (voir stackoverflow.com/questions/45395435/… )
Jack Aidley
5
@JackAidley: Quelle revendication précise? Vous avez un bon lien, semble-t-il que l'histoire soit inversée. OpenSSL s'est trompé de logique et a utilisé une variable non initialisée de telle manière qu'un compilateur pouvait légalement supposer n'importe quel résultat. Debian a bien repéré cela, mais a cassé le correctif. Quant aux «compilateurs prenant de si mauvaises décisions»; ils ne prennent pas cette décision. Le comportement indéfini est la mauvaise décision. Les optimiseurs sont conçus pour fonctionner sur du code correct. GCC, par exemple, suppose activement aucun débordement signé. En supposant que «aucune donnée non initialisée» est également raisonnable; il peut être utilisé pour éliminer les chemins de code impossibles.
MSalters
1
@JackAidley J'ai rencontré des problèmes similaires à ce que @MSalters mentionne dans mon propre code; J'ai supposé à tort qu'une variable non initialisée serait vide et j'ai été déconcerté lorsqu'une != 0comparaison ultérieure a donné la valeur true. J'ai depuis ajouté des indicateurs de compilateur pour traiter les variables non initialisées comme des erreurs afin de m'assurer de ne plus tomber dans ce piège.
Tom Lint
0

En termes simples, sauf si une valeur explicitement assignée / initialisée, l' union à membre unique n'initialise pas la mémoire allouée. Cette fonctionnalité peut être obtenue avec std:: optionalen c ++ 17.

Sitesh
la source
2
C'est une réponse trompeuse. L'union avec un seul membre aura la même taille que le membre. Cette mémoire ne sera simplement pas initialisée tant que le membre ne sera pas initialisé.
Kirill Dmitrenko