¿Cuál es el propósito de usar una unión con un solo miembro?

89

Cuando estaba leyendo el código fuente de Seastar , noté que hay una estructura de unión llamada tx_sideque tiene un solo miembro. ¿Es esto un truco para lidiar con cierto problema?

FYI, pego la tx_sideestructura a continuación:

union tx_side {
    tx_side() {}
    ~tx_side() {}
    void init() { new (&a) aa; }
    struct aa {
        std::deque<work_item*> pending_fifo;
    } a;
} _tx;
daoliker
fuente
1
Posible duplicado de stackoverflow.com/questions/26572432/… .
Max Langhof
55
@MaxLanghof Esta pregunta y las respuestas correspondientes no mencionaron el propósito de usar dicha estructura de unión.
Daoliker
¿Tienes un ejemplo para el uso de este miembro?
n314159
44
Es por eso que en realidad no utilicé mi voto cerrado vinculante. Pero no estoy seguro de qué es exactamente lo que espera de las respuestas a su pregunta que no se deduce directamente de las respuestas de allí. Presumiblemente, el propósito de usar en unionlugar de structes una o más de las diferencias entre los dos. Es una técnica bastante oscura, así que a menos que aparezca el autor original de ese código, no estoy seguro de que alguien pueda darle una respuesta autorizada sobre el problema que esperan resolver con esto (si lo hay).
Max Langhof
2
Mi mejor conjetura es que la unión se usa para retrasar la construcción (que en este caso no tiene sentido) o para evitar la destrucción (que conduce a una pérdida de memoria) de Pendiente_Fifo. Pero difícil de decir sin ejemplo de uso.
Konstantin Stupnik

Respuestas:

82

Porque tx_sidees una unión, tx_side()no se inicializa / construye automáticamente a, y ~tx_side()no la destruye automáticamente. Esto permite un control detallado sobre la vida útil de , ay pending_fifomediante la colocación de nuevas llamadas destructoras manuales y (un hombre pobre std::optional).

Aquí hay un ejemplo:

#include <iostream>

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

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

int main()
{
    B b;
}

Aquí, B b;no imprime nada, porque ano se construye ni se destruye.

Si Bfuera un struct, B()llamaría A()y ~B()llamaría ~A(), y usted no podría evitarlo.

HolyBlackCat
fuente
23
@daoliker no necesariamente al azar, pero impredecible por ti. Igual que cualquier otra variable no inicializada. No puedes asumir que es aleatorio; Por lo que sabes que podría contener la contraseña del usuario que previamente les pedirá que escriba.
user253751
55
@daoliker: El comentario anterior es demasiado optimista. Los bytes aleatorios tendrían valores en el rango de 0-255, pero si lee un byte no inicializado en uno int, puede obtener 0xCCCCCCCC. La lectura de datos no inicializados es Comportamiento indefinido, y lo que podría suceder es que el compilador simplemente descarta el intento. Esto no es solo teoría. Debian cometió este error exacto y rompió su implementación de OpenSSL. Tenían algunos bytes aleatorios reales, agregaron una variable no inicializada y el compilador dijo "bueno, el resultado no está definido, por lo que podría ser cero". Obviamente, Zero ya no es aleatorio.
MSalters
1
@MSalters: ¿Tiene una fuente para este reclamo? Porque lo que puedo encontrar sugiere que no es lo que sucedió: no fue el compilador lo que lo eliminó, sino los desarrolladores. Honestamente, me sorprendería si algún escritor compilador tomara una decisión tan increíblemente mala. (ver stackoverflow.com/questions/45395435/… )
Jack Aidley
55
@JackAidley: ¿Qué afirmación precisa? Tienes un buen enlace, parece que tengo la historia invertida. OpenSSL se equivocó de lógica y usó una variable no inicializada de tal manera que un compilador podría asumir legalmente cualquier resultado. Debian lo detectó correctamente, pero rompió la solución. En cuanto a "compiladores que toman decisiones tan malas"; ellos no toman esa decisión. El comportamiento indefinido es la mala decisión. Los optimizadores están diseñados para ejecutarse en el código correcto. GCC, por ejemplo, supone activamente que no hay desbordamiento firmado. Asumir que "no hay datos sin inicializar" es igualmente razonable; Se puede utilizar para eliminar rutas de código imposibles.
MSalters
1
@JackAidley He encontrado problemas similares a lo que @MSalters menciona en mi propio código; Asumí erróneamente que una variable no inicializada estaría vacía, y me desconcerté cuando una != 0comparación posterior arrojó verdadero. Desde entonces, agregué indicadores de compilación para tratar las variables no inicializadas como errores para asegurarme de que no vuelva a caer en esa trampa.
Tom Lint el
0

En palabras simples, a menos que se asigne / inicialice explícitamente un valor, la unión de un solo miembro no inicializa la memoria asignada. Esta funcionalidad se puede lograr con std:: optionalc ++ 17.

Sitesh
fuente
2
Esa es una respuesta engañosa. La unión con un solo miembro tendrá el mismo tamaño que el miembro. Esta memoria simplemente no se inicializará hasta que se inicialice el miembro.
Kirill Dmitrenko