visual studio microsoft espaƱol descargar community c++ tuples boost-tuples

c++ - studio - Boost:: Tuples vs Structs para valores devueltos



visual studio windows 7 (9)

Estoy tratando de entender las tuplas (gracias @litb), y la sugerencia común para su uso es para las funciones que devuelven> 1 valor.

Esto es algo que normalmente usaría una estructura para, y no puedo entender las ventajas de las tuplas en este caso, parece un enfoque propenso a errores para los perezosos terminales.

Tomando prestado un ejemplo , usaría esto

struct divide_result { int quotient; int remainder; };

Usando una tupla, tendrías

typedef boost::tuple<int, int> divide_result;

Pero sin leer el código de la función que está llamando (o los comentarios, si es lo suficientemente tonto como para confiar en ellos) no tiene idea qué int es cociente y viceversa. Parece más bien como ...

struct divide_result { int results[2]; // 0 is quotient, 1 is remainder, I think };

... lo cual no me llenaría de confianza.

Entonces, ¿cuáles son las ventajas de las tuplas sobre las estructuras que compensan la ambigüedad?


tuplas

Creo que estoy de acuerdo con usted en que el problema con qué posición corresponde a qué variable puede generar confusión. Pero creo que hay dos lados. Uno es el lado de llamada y el otro es el lado de llamada :

int remainder; int quotient; tie(quotient, remainder) = div(10, 3);

Creo que está claro lo que tenemos, pero puede ser confuso si tienes que devolver más valores a la vez. Una vez que el programador de la persona que llama ha buscado la documentación de div , sabrá qué posición es qué, y puede escribir un código efectivo. Como regla general, diría que no devuelva más de 4 valores a la vez. Para cualquier cosa más allá, prefiere una estructura.

parámetros de salida

Los parámetros de salida también se pueden usar, por supuesto:

int remainder; int quotient; div(10, 3, &quotient, &remainder);

Ahora creo que eso ilustra cómo las tuplas son mejores que los parámetros de salida. Hemos mezclado la entrada de div con su salida, sin obtener ninguna ventaja. Peor aún, dejamos al lector de ese código en duda sobre cuál podría ser el verdadero valor de retorno de div ser. Hay ejemplos maravillosos cuando los parámetros de salida son útiles. En mi opinión, debes usarlos solo cuando no tienes otra opción, porque el valor de retorno ya está tomado y no se puede cambiar a una tupla o estructura. operator>> es un buen ejemplo de dónde usa los parámetros de salida, porque el valor de retorno ya está reservado para la secuencia, por lo que puede realizar operator>> llamadas de operator>> cadena. Si no tiene que ver con los operadores, y el contexto no es claro como el cristal, le recomiendo que use punteros para indicar al lado de la llamada que el objeto se usa realmente como parámetro de salida, además de los comentarios, cuando corresponda.

devolviendo una estructura

La tercera opción es usar una estructura:

div_result d = div(10, 3);

Creo que definitivamente gana el premio por claridad . Pero tenga en cuenta que todavía tiene que acceder al resultado dentro de esa estructura, y el resultado no está "desnudo" en la tabla, como fue el caso de los parámetros de salida y la tupla utilizada con el tie .

Creo que un punto importante en estos días es hacer que todo sea lo más genérico posible. Entonces, digamos que tiene una función que puede imprimir tuplas. Puedes simplemente hacer

cout << div(10, 3);

Y tiene su resultado mostrado. Creo que las tuplas, en el otro lado, claramente ganan por su naturaleza versátil . Al hacer eso con div_result, necesita sobrecargar el operador <<, o necesita generar cada miembro por separado.


Con tuplas, puede usar tie , que a veces es bastante útil: std::tr1::tie (quotient, remainder) = do_division (); . Esto no es tan fácil con las estructuras. En segundo lugar, cuando se usa el código de la plantilla, a veces es más fácil confiar en los pares que agregar otro typedef para el tipo de estructura.

Y si los tipos son diferentes, entonces un par / tupla no es peor que una estructura. Piénsese, por ejemplo, en el pair<int, bool> readFromFile() , donde int es el número de bytes leídos y bool es si el eof ha sido impactado. Agregar una estructura en este caso parece exagerado para mí, especialmente porque aquí no hay ambigüedad.


Estoy de acuerdo contigo 100% Roddy.

Para devolver varios valores de un método, tiene varias opciones además de tuplas, cuál es la mejor depende de su caso:

  1. Creando una nueva estructura Esto es bueno cuando los valores múltiples que está devolviendo están relacionados , y es apropiado crear una nueva abstracción. Por ejemplo, creo que "divide_result" es una buena abstracción general, y pasar esta entidad hace que tu código sea mucho más claro que simplemente pasar una tupla anónima alrededor. Luego puede crear métodos que operen en este nuevo tipo, convertirlo a otros tipos numéricos, etc.

  2. Usando los parámetros "Out". Pase varios parámetros por referencia y devuelva múltiples valores asignando a cada parámetro de salida. Esto es apropiado cuando su método devuelve varias piezas de información no relacionadas . Crear una nueva estructura en este caso sería exagerado, y con los parámetros Out enfatizarás este punto, además cada elemento recibe el nombre que merece.

Las tuplas son malvadas


Evita que su código esté plagado de muchas definiciones de estructuras. Es más fácil para la persona que escribe el código y para otros que lo usa cuando solo documenta lo que es cada elemento en la tupla, en lugar de escribir su propia estructura / hacer que las personas busquen la definición de estructura.


Las tuplas son muy útiles en idiomas como ML o Haskell.

En C ++, su sintaxis los hace menos elegantes, pero puede ser útil en las siguientes situaciones:

  • tiene una función que debe devolver más de un argumento, pero el resultado es "local" para el llamante y el destinatario; no quieres definir una estructura solo para esto

  • puede usar la función de vinculación para hacer una forma muy limitada de coincidencia de patrones "a la ML", que es más elegante que usar una estructura para el mismo propósito.

  • vienen con <operadores predefinidos, lo que puede ahorrar tiempo.


Otra opción es usar un mapa de Boost Fusion (código no probado):

struct quotient; struct remainder; using boost::fusion::map; using boost::fusion::pair; typedef map< pair< quotient, int >, pair< remainder, int > > div_result;

Puede acceder a los resultados de forma relativamente intuitiva:

using boost::fusion::at_key; res = div(x, y); int q = at_key<quotient>(res); int r = at_key<remainder>(res);

También existen otras ventajas, como la posibilidad de iterar sobre los campos del mapa, etc. Consulte el doco para obtener más información.


Tiendo a usar tuplas junto con typedefs para aliviar al menos parcialmente el problema de la "tupla anónima". Por ejemplo, si tuviera una estructura de grilla, entonces:

//row is element 0 column is element 1 typedef boost::tuple<int,int> grid_index;

Luego uso el tipo con nombre como:

grid_index find(const grid& g, int value);

Este es un ejemplo algo artificial, pero creo que la mayoría de las veces llega a un medio feliz entre legibilidad, claridad y facilidad de uso.

O en tu ejemplo:

//quotient is element 0 remainder is element 1 typedef boost:tuple<int,int> div_result; div_result div(int dividend,int divisor);


Tuples será más fácil de escribir, no es necesario crear una nueva estructura para cada función que devuelva algo. La documentación sobre qué va a ir irá a la documentación de la función, que se necesitará de todos modos. Para usar la función, será necesario leer la documentación de la función en cualquier caso y la tupla se explicará allí.


Una característica de las tuplas que no tiene con las estructuras está en su inicialización. Considera algo como lo siguiente:

struct A { int a; int b; };

A menos que escriba un equivalente de make_tuple o un constructor, para usar esta estructura como un parámetro de entrada, primero debe crear un objeto temporal:

void foo (A const & a) { // ... } void bar () { A dummy = { 1, 2 }; foo (dummy); }

No está mal, sin embargo, tomemos el caso donde el mantenimiento agrega un nuevo miembro a nuestra estructura por cualquier razón:

struct A { int a; int b; int c; };

Las reglas de inicialización agregada realmente significan que nuestro código continuará compilando sin cambios. Por lo tanto, tenemos que buscar todos los usos de esta estructura y actualizarlos, sin ninguna ayuda del compilador.

Compare esto con una tupla:

typedef boost::tuple<int, int, int> Tuple; enum { A , B , C }; void foo (Tuple const & p) { } void bar () { foo (boost::make_tuple (1, 2)); // Compile error }

El compilador no puede initailizar "Tuple" con el resultado de make_tuple , y así genera el error que le permite especificar los valores correctos para el tercer parámetro.

Finalmente, la otra ventaja de las tuplas es que te permiten escribir código que itera sobre cada valor. Esto simplemente no es posible usando una estructura.

void incrementValues (boost::tuples::null_type) {} template <typename Tuple_> void incrementValues (Tuple_ & tuple) { // ... ++tuple.get_head (); incrementValues (tuple.get_tail ()); }