¿Utiliza boost::variant
o una de las muchas implementaciones C ++ 11 de código abierto de una "Unión etiquetada" o tipo de variante en sus proyectos C ++?
boost::variant
es una gran biblioteca. Creé strict_variant
para abordar algunas cosas sobre boost::variant
que no me gustó.
La versión TL; DR es que, a diferencia de boost::variant
o std::variant
, strict_variant
nunca lanzará una excepción ni hará una asignación dinámica en el esfuerzo de soportar tipos que tienen movimientos de lanzamiento. La versión predeterminada simplemente fallará una afirmación estática si esto sucedería. El strict_variant::easy_variant
hará asignaciones en esta situación, por lo que puede optar por eso si lo desea, y estas dos versiones de variantes "juegan bien" juntas. Este tipo de cosa es a menudo una preocupación importante en los proyectos con requisitos en tiempo real, o en dispositivos integrados, que pueden no permitir, o simplemente no tener estas características de C ++. Si está haciendo una biblioteca que podría usarse en proyectos "convencionales" que desean la facilidad de uso que proviene de la variant
boost::variant
Como parte de la API, strict_variant
podría ofrecer una forma de mantener felices a todos.
Además de esto, hay algunos problemas en la interfaz de la variante que se abordaron que hacen que sea más agradable usar el día a día en mi humilde opinión. (Estas fueron en realidad la motivación original del proyecto).
No me gustó que ese código como este pueda compilar sin ningún mensaje de advertencia o error:
boost::variant<std::string, int > v;
v = true ;
Por lo general, prefiero que mi variant
sea más restrictiva sobre qué conversiones implícitas pueden suceder.
Quería que cosas como esta se compilaran y hicieran lo que tiene sentido, incluso si la resolución de sobrecarga sería ambigua.
variant< bool , long , double , std::string> v;
v = true ; // selects bool
v = 10 ; // selects long
v = 20 . 5f ; // selects double
v = " foo " ; // selects string
También quería que dicho comportamiento (lo que se selecciona en tales casos) sea portátil.
(Para ejemplos de código como este, donde boost::variant
tiene un comportamiento desafortunado, consulte "Resumen y motivación" en la documentación).
En strict_variant
modificamos la resolución de sobrecarga en estas situaciones eliminando algunos candidatos.
Por ejemplo:
bool
, Integral, Punto flotante, puntero, carácter y algunas otras clases.int -> long
e int -> long long
son candidatos, el long long
se elimina.Ver documentación para más detalles.
No me gustó esa boost::variant
Hará en silencio copias de respaldo de mis objetos. Por ejemplo, considere este programa simple, en el que A
y B
se han definido para registrar todas las llamadas CTOR y DTOR.
int main () {
using var_t = boost::variant<A, B>;
var_t v{ A ()};
std::cout << " 1 " << std::endl;
v = B ();
std::cout << " 2 " << std::endl;
v = A ();
std::cout << " 3 " << std::endl;
}
La boost::variant
produce la siguiente salida:
A ()
A(A&&)
~A()
1
B()
B(B&&)
A( const A &)
~A()
B( const B &)
~A()
~B()
~B()
2
A()
A(A&&)
B( const B &)
~B()
A( const A &)
~B()
~A()
~A()
3
~A()
Esto puede ser bastante sorprendente para algunos programadores.
Por el contrario, si usa la std::variant
, o una de las variantes con semántica "a veces vacía", obtiene algo como esto (este resultado de std::experimental::variant
)
A ()
A(A&&)
~A()
1
B()
~A()
B(B&&)
~B()
2
A()
~B()
A(A&&)
~A()
3
~A()
Esto está mucho más cerca de lo que el programador ingenuo espera quién no sabe sobre los detalles internos de boost::variant
: las únicas copias de sus objetos que existen son lo que puede ver en su código fuente.
Este tipo de cosas generalmente no importa, pero a veces si, por ejemplo, está depurando un problema de corrupción de memoria desagradable (tal vez hay un código incorrecto en uno de los objetos contenidos en la variante), entonces estos objetos, movimientos y copias adicionales pueden Haz las cosas incidentalmente más complicadas.
Esto es lo que obtienes con strict_variant
:
A ()
A(A&&)
~A()
1
B()
B(B&&)
~A()
~B()
2
A()
A(A&&)
~B()
~A()
3
~A()
Sin embargo, strict_variant
no tiene un estado vacío, ¡y es completamente seguro de excepción!
(Estos ejemplos de gcc 5.4
, vea el código en la carpeta example
).
Para resumir las diferencias:
std::variant
rara vez está vacía, siempre basada en la pila. De hecho, está vacío exactamente cuando se lanza una excepción. Más tarde, arroja diferentes excepciones si intenta visitar cuando está vacío.boost::variant
es nunca vacía, generalmente basada en la pila. Tiene que hacer una asignación dinámica y una copia de copia de seguridad cada vez que se pueda lanzar una excepción, pero eso se libera justo después de si una excepción no se lanza realmente.strict_variant
es nunca vacío, y se basa exactamente en la pila cuando el tipo de valor actual es móvil de Nothrow. Nunca hace un movimiento o copia de copia de seguridad, y nunca arroja una excepción. Cada enfoque tiene sus méritos. Elegí el enfoque strict_variant
porque me parece más simple y evita lo que considero inconvenientes de boost::variant
and std::variant
. Y, si logras hacer que todos tus tipos de movimiento sin tiros sean construibles, lo que a menudo encuentro que puedo, entonces strict_variant
te brinda un rendimiento óptimo, lo mismo que std::variant
, sin un estado vacío.
Para una discusión en profundidad del diseño, consulte la documentación.
Para una introducción suave a las variantes, y una descripción general de estricto variante, ver diapositivas de una charla que di sobre esto: [PPTX] [PDF]
En páginas de Github.
strict_variant
se dirige al estándar C ++ 11.
Se sabe que funciona con gcc >= 4.8
y clang >= 3.5
, y se prueba con MSVC 2015
.
strict_variant
se puede usar como en proyectos que requieren -fno-exceptions
y -fno-rtti
.
Strict Variant está disponible bajo la licencia de software BOOST.