smart pointer make_unique c++ c++11 struct unions unique-ptr

c++ - pointer - Usar una unión con unique_ptr



smart pointer c++ (4)

Intentar usar un unique_ptr dentro de una unión me da una segfault cuando intento std :: move o std :: make_unique.

#include <iostream> #include <memory> union myUnion{ struct{std::unique_ptr<float> upFloat;}structUpFloat; struct{std::unique_ptr<int> upInt;}structUpInt; myUnion(){} ~myUnion(){} }; struct myStruct{ int x; myUnion num; }; int main() { myStruct aStruct, bStruct; aStruct.x = 1; bStruct.x = 2; auto upF = std::make_unique<float>(3.14); auto upI = std::make_unique<int>(3); aStruct.num.structUpFloat.upFloat = std::move(upF); bStruct.num.structUpInt.upInt = std::move(upI); std::cout << "aStruct float = " << *aStruct.num.structUpFloat.upFloat << std::endl; std::cout << "bStruct int = " << *bStruct.num.structUpInt.upInt << std::endl; return 0; }

Sin embargo, usar un puntero normal funciona como se espera:

#include <iostream> #include <memory> union myUnion{ struct{float *pFloat;}structPFloat; struct{int *pInt;}structPInt; myUnion(){} ~myUnion(){} }; struct myStruct{ int x; myUnion num; }; int main() { myStruct aStruct, bStruct; aStruct.x = 1; bStruct.x = 2; auto upF = std::make_unique<float>(3.14); auto upI = std::make_unique<int>(3); aStruct.num.structPFloat.pFloat = upF.get(); bStruct.num.structPInt.pInt = upI.get(); std::cout << "aStruct float = " << *aStruct.num.structPFloat.pFloat << std::endl; std::cout << "bStruct int = " << *bStruct.num.structPInt.pInt << std::endl; return 0; }

Esto es usando clang.3.4.2 o gcc.4.9.0. Así que estoy asumiendo que estoy haciendo algo mal aquí. Cualquier ayuda sería apreciada.

EDITAR:

Ok, entonces probablemente sea una buena cosa compartir el código que establecí. Muchas gracias a todos los que me recomendaron administrar la vida útil de mis punteros en miembros variantes utilizando la colocación nueva.

#include <memory> #include <iostream> #include <vector> struct myStruct { public: union { std::unique_ptr<float> upFloat; std::unique_ptr<int> upInt; }; enum class unionType {f, i,none} type = unionType::none; // Keep it sane myStruct(){} myStruct(std::unique_ptr<float> p) { new (&upFloat) std::unique_ptr<float>{std::move(p)}; type = unionType::f; } myStruct(std::unique_ptr<int> p) { new (&upInt) std::unique_ptr<int>{std::move(p)}; type = unionType::i; } ~myStruct() { switch (type) { case unionType::f: upFloat.~unique_ptr<float>(); break; case unionType::i: upInt.~unique_ptr<int>(); break; } } }; int main() { std::vector<std::unique_ptr<myStruct>> structVec; structVec.push_back(std::make_unique<myStruct>(std::make_unique<float>(3.14f))); structVec.push_back(std::make_unique<myStruct>(std::make_unique<int>(739))); structVec.push_back(std::make_unique<myStruct>()); structVec.push_back(std::make_unique<myStruct>(std::make_unique<float>(8.95f))); structVec.push_back(std::make_unique<myStruct>(std::make_unique<int>(3))); structVec.push_back(std::make_unique<myStruct>()); for(auto &a: structVec) { if(a->type == myStruct::unionType::none) { std::cout << "Struct Has Unallocated Union" << std::endl; } else if(a->type == myStruct::unionType::f) { std::cout << "Struct float = " << *a->upFloat << std::endl; } else { std::cout << "Struct int = " << *a->upInt << std::endl; } std::cout << std::endl; } return 0; }

Productos:

Struct float = 3.14

Struct int = 739

Struct tiene uniones no asignadas

Struct float = 8.95

Struct int = 3

Struct tiene uniones no asignadas


De esta referencia :

Si una unión contiene un miembro de datos no estático con una función de miembro especial no trivial (copiar / mover constructor, copiar / mover asignación o destructor) esa función se elimina de manera predeterminada en la unión y necesita ser definida explícitamente por el programador .

Supongo que la razón por la que encasillo los punteros en estructuras simples es porque no podría construirlo de otra forma, debido a las restricciones impuestas por el párrafo anterior.

Lo que ha hecho en su lugar se ha pasado por alto los guardias de seguridad del compilador, y probablemente tenga un comportamiento indefinido en su código.


Cambiar el miembro activo de una unión requiere un cuidado especial para la duración del objeto. El estándar C ++ dice (9.5p4):

Nota: En general, uno debe usar llamadas de destructor explícitas y nuevos operadores de ubicación para cambiar el miembro activo de una unión.

Cuando los miembros son datos viejos y simples, generalmente "simplemente funciona", aunque no se está llamando a constructores (que usan la ubicación new ) y destructores. Esto se debe a que la vida útil de los objetos con inicialización trivial comienza "cuando se obtiene el almacenamiento" de tamaño suficiente y alineación correcta, y la unión lo proporciona.

Ahora tienes miembros con constructor y destructor no triviales. Su tiempo de vida no comienza cuando se obtiene el almacenamiento, también debe hacer que la inicialización termine. Y eso significa colocación nueva. Omitir las llamadas al destructor tampoco es seguro, se obtiene un comportamiento indefinido si esos destructores hubieran tenido efectos secundarios en los que su programa se basa (y un destructor unique_ptr tiene el efecto secundario de desasignar su objetivo).

Por lo tanto, está llamando a un operador de asignación de movimiento en un miembro cuya vida útil no ha comenzado. Ese es un comportamiento indefinido.


De §12.6.2 [class.base.init] / p8 del estándar (énfasis añadido):

En un constructor no delegante, si un miembro de datos no estático determinado o clase base no está designado por un identificador-inicializador-memoria (incluido el caso en el que no hay lista-inicializador-mem porque el constructor no tiene inicializador-ctor ) y la entidad no es una clase base virtual de una clase abstracta (10.4), entonces

  • si la entidad es un miembro de datos no estático que tiene un inicializador de llave o igual , la entidad se inicializa como se especifica en 8.5;
  • de lo contrario, si la entidad es un miembro de la variante (9.5), no se realiza ninguna inicialización ;
  • [...]

Los miembros de la Unión son miembros variantes, lo que significa que los unique_ptr s no se inicializan. En particular, no se llama a ningún constructor, ni siquiera el predeterminado. Técnicamente, la vida útil de estos unique_ptr s nunca comenzó.

El operador de asignación de movimiento unique_ptr debe eliminar lo que unique_ptr está reteniendo actualmente, pero está asignando movimiento a un " unique_ptr " no inicializado que contiene valores de elementos no utilizados. Como resultado, su asignación de movimiento probablemente causó un intento de eliminar un puntero de basura, lo que provocó una segfault.


Para la unión no restringida, tienes que manejarte un poco de construcción / destrucción.

Lo siguiente puede ayudar:

union myUnion{ std::unique_ptr<float> upFloat; std::unique_ptr<int> upInt; myUnion(){ new (&upFloat) std::unique_ptr<float>{};} ~myUnion() {} }; class myStruct { public: ~myStruct() { destroy(); } void destroy() { switch (type) { case unionType::f: num.upFloat.~unique_ptr<float>(); break; case unionType::i: num.upInt.~unique_ptr<int>(); break; } } void set(std::unique_ptr<int> p) { destroy(); new (&num.upInt) std::unique_ptr<int>{std::move(p)}; type = unionType::i; } void set(std::unique_ptr<float> p) { destroy(); new (&num.upFloat) std::unique_ptr<float>{std::move(p)}; type = unionType::f; } public: enum class unionType {f, i} type = unionType::f; // match the default constructor of enum myUnion num; }; int main() { myStruct aStruct, bStruct; aStruct.set(std::make_unique<float>(3.14f)); bStruct.set(std::make_unique<int>(3)); std::cout << "aStruct float = " << *aStruct.num.upFloat << std::endl; std::cout << "bStruct int = " << *bStruct.num.upInt << std::endl; return 0; }

En C ++ 17, puede usar std::variant lugar de su propia struct