c++ - ¿Cómo debería uno usar std:: opcional?
boost-optional c++-tr2 (4)
pero no entiendo cuándo debería usarlo o cómo debería usarlo.
Considere cuando está escribiendo una API y desea expresar que el valor de "no tener devolución" 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:
class YourBlock { /* block header, format, whatever else */ };
std::optional<YourBlock> cache_and_get_block(
some_socket_object& socket);
Si los datos adjuntos completaron un bloque analizable, puede procesarlo; de lo contrario, sigue leyendo y anexando datos:
void your_client_code(some_socket_object& socket)
{
char raw_data[1024]; // max 1024 bytes of raw data (for example)
while(socket.read(raw_data, 1024))
{
if(auto block = cache_and_get_block(raw_data))
{
// process *block here
// then return or break
}
// else [ no error; just keep reading and appending ]
}
}
Editar: con respecto al resto de sus preguntas:
Cuándo es std :: opcional, una buena opción para usar
Cuando calcula un valor y necesita devolverlo, mejora la semántica para devolver por valor que para 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 (quien escribe el código del cliente puede no verificar el error; si intenta utilizar un puntero no inicializado, obtendrá un volcado del núcleo; si intenta utilizar un Initialized std :: opcional, obtiene una excepción catch-able).
[...] y cómo compensa lo que no se encontró en el Estándar anterior (C ++ 11).
Antes de C ++ 11, tenía que usar una interfaz diferente para "funciones que pueden no devolver un valor": retornar por puntero y comprobar NULO, o aceptar un parámetro de salida y devolver un código de error / resultado para "no disponible" ".
Ambos imponen esfuerzo extra y atención del implementador del cliente para hacerlo bien y ambos son una fuente de confusión (el primero empujó al implementador del cliente a pensar en una operación como asignación y requiriendo que el código del cliente implemente la lógica de manejo del puntero y el segundo código de cliente para salirse con la suya usando valores inválidos / no inicializados).
std::optional
ocupa muy bien de los problemas que surgen con las soluciones anteriores.
Estoy leyendo la documentación de std::experimental::optional
y tengo una buena idea sobre lo que hace, pero no entiendo cuándo debería usarla o cómo debería usarla. El sitio aún no contiene ningún ejemplo que me haga más difícil comprender el verdadero concepto de este objeto. Cuándo es std::optional
una buena opción de usar, y cómo compensa lo que no se encontró en el estándar anterior (C ++ 11).
A menudo uso opcionales para representar datos opcionales extraídos de archivos de configuración, es decir, donde se proporcionan opcionalmente esos datos (como con un elemento esperado, aunque no necesario, dentro de un documento XML), de modo que puedo 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 "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 un valor nulo (p == 0) no está establecido, un valor de 0 (* p == 0) está configurado y 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 renderizado, donde la geometría puede anular los indicadores de renderizado (establecer), desactivar los indicadores de renderizado (establecer en 0) o simplemente no afectan las banderas de renderizado (no establecidas), una opción 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 de compartir, ya que puede ofrecer una implementación más limpia, sin embargo, yo 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 null significa literalmente no asignado o creado, aunque podría , pero no necesariamente significa "no establecido". Vale la pena señalar que un puntero debe ser lanzado, y en buenas prácticas establecerse en 0, sin embargo, al igual que con un puntero compartido, un opcional no requiere una limpieza explícita, por lo que no hay una preocupación de mezclar la limpieza con el opcional no se ha establecido.
Creo que se trata de la claridad del código. La claridad reduce el costo del mantenimiento y el 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. 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 los comentarios explícitos, ya que los comentarios no se pueden hacer cumplir (por ejemplo, mediante compilación). Ejemplos de estos ítems implícitos para el desarrollo (aquellos artículos en desarrollo que se proporcionan puramente para hacer cumplir la intención) incluyen los diferentes moldes de estilo de C ++, "const" (especialmente en las funciones de miembros) y el tipo "bool", por nombrar algunos. Podría decirse que realmente no necesita estas características de código, siempre y cuando obedezcan las intenciones o los comentarios.
El ejemplo más simple que puedo pensar:
std::optional<int> try_parse_int(std::string s)
{
//try to parse an int from the given string,
//and return "nothing" if you fail
}
Lo mismo podría lograrse con un argumento de referencia (como en la siguiente firma), pero usar std::optional
hace que la firma y el uso sean más agradables.
bool try_parse_int(std::string s, int& i);
Otra forma en que esto podría hacerse es especialmente malo :
int* try_parse_int(std::string s); //return nullptr if fail
Esto requiere una asignación de memoria dinámica, preocuparse por la propiedad, etc., siempre prefiere una de las otras dos firmas anteriores.
Otro ejemplo:
class Contact
{
std::optional<std::string> home_phone;
std::optional<std::string> work_phone;
std::optional<std::string> mobile_phone;
};
¡Esto es extremadamente preferible a tener algo así como un std::unique_ptr<std::string>
para cada número de teléfono! std::optional
le da ubicación de datos, que es excelente para el rendimiento.
Otro ejemplo:
template<typename Key, typename Value>
class Lookup
{
std::optional<Value> get(Key key);
};
Si la búsqueda no tiene una determinada clave, simplemente podemos devolver "sin valor".
Puedo usarlo así:
Lookup<std::string, std::string> location_lookup;
std::string location = location_lookup.get("waldo").value_or("unknown");
Otro ejemplo:
std::vector<std::pair<std::string, double>> search(
std::string query,
std::optional<int> max_count,
std::optional<double> min_match_score);
¡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) y min_match_score
(o no)!
También elimina el maldito "Pase -1
para max_count
si no quieres un límite" o "Pass std::numeric_limits<double>::min()
para min_match_score
si no quieres un puntaje mínimo".
Otro ejemplo:
std::optional<int> find_in_string(std::string s, std::string query);
Si la cadena de consulta no está en s
, quiero "no int
", no importa el valor especial que alguien haya decidido utilizar para este fin (-1?).
Para ver ejemplos adicionales, puede consultar la documentation boost::optional
. boost::optional
y std::optional
serán básicamente idénticos en términos de comportamiento y uso.
Se cita un ejemplo del Nuevo documento adoptado: N3672, std :: opcional :
optional<int> str2int(string); // converts int to string if possible
int get_int_from_user()
{
string s;
for (;;) {
cin >> s;
optional<int> o = str2int(s); // ''o'' may or may not contain an int
if (o) { // does optional contain a value?
return *o; // use the value
}
}
}