Estoy leyendo la documentación std::experimental::optional
y tengo una buena idea de lo que hace, pero no entiendo cuándo debería usarla o cómo debería usarla. Hasta el momento, el sitio no contiene ningún ejemplo, lo que me dificulta comprender el verdadero concepto de este objeto. Cuándo es std::optional
una buena opción para usar, y cómo compensa lo que no se encontró en el Estándar anterior (C ++ 11).
c++
boost-optional
c++-tr2
0x499602D2
fuente
fuente
optional
. Imagina que quieres unoptional<int>
o incluso<char>
. ¿Realmente cree que se requiere "Zen" para asignar, desreferenciar y luego eliminar dinámicamente, algo que de otro modo no podría requerir ninguna asignación y encajar de manera compacta en la pila, y posiblemente incluso en un registro?std::optional
(mientras que puede ser parastd::unique_ptr
). Más precisamente, la norma exige que T [...] satisfaga los requisitos de Destructible .Respuestas:
El ejemplo más simple que se me ocurre:
Lo mismo podría lograrse con un argumento de referencia (como en la siguiente firma), pero el uso
std::optional
hace que la firma y el uso sean más agradables.Otra forma de hacer esto es especialmente mala :
Esto requiere una asignación de memoria dinámica, preocuparse por la propiedad, etc. Siempre prefiera una de las otras dos firmas anteriores.
Otro ejemplo:
¡Esto es extremadamente preferible a tener algo como un
std::unique_ptr<std::string>
para cada número de teléfono!std::optional
le brinda la localidad de datos, que es excelente para el rendimiento.Otro ejemplo:
Si la búsqueda no tiene una clave determinada, simplemente podemos devolver "sin valor".
Puedo usarlo así:
Otro ejemplo:
¡Esto tiene mucho más sentido que, por ejemplo, tener cuatro sobrecargas de funciones que toman todas las combinaciones posibles de
max_count
(o no) ymin_match_score
(o no)!¡También elimina el maldito "Pase
-1
pormax_count
si no quiere un límite" o "Pasestd::numeric_limits<double>::min()
pormin_match_score
si no quiere un puntaje mínimo"!Otro ejemplo:
Si la cadena de consulta no está en
s
, quiero "noint
", no cualquier valor especial que alguien haya decidido usar para este propósito (-1?).Para ver ejemplos adicionales, puede consultar la
boost::optional
documentación .boost::optional
ystd::optional
básicamente será idéntico en términos de comportamiento y uso.fuente
std::optional<T>
es solo unaT
y unabool
. Las implementaciones de funciones miembro son extremadamente simples. El rendimiento no debería ser realmente una preocupación al usarlo: hay veces en que algo es opcional, en cuyo caso esta suele ser la herramienta correcta para el trabajo.std::optional<T>
es mucho más complejo que eso. Utiliza la colocaciónnew
y muchas otras cosas con la alineación y el tamaño adecuados para que sea un tipo literal (es decir, usar conconstexpr
) entre otras cosas. La ingenuidadT
y elbool
enfoque fallarían bastante rápido.union storage_t { unsigned char dummy_; T value_; ... }
Línea 289:struct optional_base { bool init_; storage_t<T> storage_; ... }
¿Cómo es que eso no es "aT
y abool
"? Estoy completamente de acuerdo en que la implementación es muy complicada y no trivial, pero conceptual y concretamente el tipo es aT
y abool
. "La ingenuidadT
y elbool
enfoque fallarían bastante rápido". ¿Cómo puedes hacer esta declaración cuando miras el código?struct{bool,maybe_t<T>}
el sindicato está ahí para no hacerstruct{bool,T}
lo que construiría una T en todos los casos.std::unique_ptr<T>
ystd::optional<T>
en cierto sentido cumplen el papel de "un opcionalT
". Describiría la diferencia entre ellos como "detalles de implementación": asignaciones adicionales, administración de memoria, localidad de datos, costo de traslado, etc. Nunca tendría,std::unique_ptr<int> try_parse_int(std::string s);
por ejemplo, porque causaría una asignación para cada llamada aunque no haya razón para . Nunca tendría una clase con unstd::unique_ptr<double> limit;
- ¿por qué hacer una asignación y perder la localidad de datos?Se cita un ejemplo del nuevo documento adoptado: N3672, std :: opcional :
fuente
int
o no una jerarquía de llamadas ascendente o descendente, en lugar de pasar algún valor "fantasma" "supuesto" que tiene un significado de "error".str2int()
implementar la conversión como quiera, (B) independientemente del método para obtenerlastring s
, y (C) transmite el significado completo a travésoptional<int>
de alguna forma estúpida de número mágico,bool
/ referencia o asignación dinámica basada en haciéndolo.Considere cuando está escribiendo una API y desea expresar que el valor "no tener un retorno" no es un error. Por ejemplo, necesita leer datos de un socket, y cuando se completa un bloque de datos, lo analiza y lo devuelve:
Si los datos adjuntos completaron un bloque analizable, puede procesarlo; de lo contrario, siga leyendo y agregando datos:
Editar: con respecto al resto de sus preguntas:
Cuando calcula un valor y necesita devolverlo, es mejor que la semántica regrese por valor que tomar una referencia a un valor de salida (que puede no generarse).
Cuando desee asegurarse de que el código del cliente tenga que verificar el valor de salida (el que escriba el código del cliente puede no verificar el error; si intenta usar un puntero no inicializado, obtendrá un volcado del núcleo; si intenta usar un no- Inicializado std :: opcional, obtienes una excepción capturable).
Antes de C ++ 11, tenía que usar una interfaz diferente para "funciones que pueden no devolver un valor": devolver por puntero y verificar NULL, o aceptar un parámetro de salida y devolver un código de error / resultado para "no disponible ".
Ambos imponen un esfuerzo extra y atención del implementador del cliente para hacerlo bien y ambos son una fuente de confusión (el primero empuja al implementador del cliente a pensar en una operación como una asignación y requiere que el código del cliente implemente la lógica de manejo del puntero y el segundo permite código de cliente para evitar el uso de valores no válidos / no inicializados).
std::optional
Cuida muy bien los problemas que surgen con las soluciones anteriores.fuente
boost::
lugar destd::
?boost::optional
porque después de aproximadamente dos años de usarlo, está codificado en mi corteza prefrontal).A menudo uso opcionales para representar datos opcionales extraídos de los archivos de configuración, es decir, dónde se proporcionan opcionalmente esos datos (como con un elemento esperado, pero no necesario, dentro de un documento XML), para que pueda mostrar explícita y claramente si los datos estaban realmente presentes en el documento XML. Especialmente cuando los datos pueden tener un estado "no establecido", frente a un estado "vacío" y un estado "establecido" (lógica difusa). Con un opcional, establecer y no establecer es claro, también vacío sería claro con el valor de 0 o nulo.
Esto puede mostrar cómo el valor de "no establecido" no es equivalente a "vacío". En concepto, un puntero a un int (int * p) puede mostrar esto, donde no se establece un valor nulo (p == 0), se establece un valor de 0 (* p == 0) y está vacío, y cualquier otro valor (* p <> 0) se establece en un valor.
Para un ejemplo práctico, tengo una pieza de geometría extraída de un documento XML que tenía un valor llamado indicadores de representación, donde la geometría puede anular los indicadores de representación (conjunto), deshabilitar los indicadores de representación (establecer a 0), o simplemente no afectar las banderas de renderizado (no establecido), un opcional sería una forma clara de representar esto.
Claramente, un puntero a un int, en este ejemplo, puede lograr el objetivo, o mejor, un puntero compartido ya que puede ofrecer una implementación más limpia, sin embargo, diría que se trata de la claridad del código en este caso. ¿Es un nulo siempre un "no establecido"? Con un puntero, no está claro, ya que nulo significa literalmente no asignado o creado, aunque podría , pero no necesariamente significa "no establecido". Vale la pena señalar que se debe liberar un puntero y, en la buena práctica, se debe establecer en 0, sin embargo, como con un puntero compartido, un opcional no requiere una limpieza explícita, por lo que no existe la preocupación de mezclar la limpieza con el opcional no se ha configurado.
Creo que se trata de la claridad del código. La claridad reduce el costo de mantenimiento y desarrollo del código. Una comprensión clara de la intención del código es increíblemente valiosa.
El uso de un puntero para representar esto requeriría sobrecargar el concepto del puntero. Para representar "nulo" como "no establecido", normalmente puede ver uno o más comentarios a través del código para explicar esta intención. Esa no es una mala solución en lugar de una opción, sin embargo, siempre opto por la implementación implícita en lugar de comentarios explícitos, ya que los comentarios no son exigibles (como por compilación). Los ejemplos de estos elementos implícitos para el desarrollo (aquellos artículos en desarrollo que se proporcionan únicamente para imponer la intención) incluyen los diversos modelos de estilo C ++, "const" (especialmente en funciones miembro) y el tipo "bool", por nombrar algunos. Podría decirse que realmente no necesita estas características de código, siempre y cuando todos obedezcan intenciones o comentarios.
fuente