Pregunta

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 para lo que normalmente usaría una estructura, y no puedo entender las ventajas de las tuplas en este caso: parece un enfoque propenso a errores para los perezosos terminales.

Tomando 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ás llamando (o los comentarios, si eres lo suficientemente tonto como para confiar en ellos) no tienes idea de 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
};

... que no me llenaría de confianza.

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

¿Fue útil?

Solución

tuplas

Creo que estoy de acuerdo con usted en que el problema con qué posición corresponde a qué variable puede introducir confusión. Pero creo que hay dos lados. Uno es call-side y el otro es callee-side :

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

Creo que es muy claro lo que tenemos, pero puede ser confuso si tiene 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á, prefiera 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 operator>> 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 valor de retorno real de tie be. hay ejemplos maravillosos cuando los parámetros de salida son útiles. En mi opinión, debe usarlos solo cuando no tenga otra manera, porque el valor de retorno ya está tomado y no se puede cambiar a una tupla o estructura. <=> 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 encadenar <=> llamadas. Si no tiene que ver con operadores, y el contexto no es claro como el cristal, le recomiendo que use punteros, para indicar en el 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 es & Quot; puesto al descubierto & Quot; en la tabla, como fue el caso de los parámetros de salida y la tupla utilizada con <=>.

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

cout << div(10, 3);

Y muestra tu resultado. Creo que las tuplas, por otro lado, claramente ganan por su naturaleza versátil . Al hacer eso con div_result, debe sobrecargar el operador & Lt; & Lt ;, o debe generar cada miembro por separado.

Otros consejos

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 hay otras ventajas, como la capacidad de iterar sobre los campos del mapa, etc. Ver el doco para obtener más información.

Con las 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 utiliza el código de plantilla, a veces es más fácil confiar en pares que agregar otro typedef para el tipo de estructura.

Y si los tipos son diferentes, entonces un par / tupla no es realmente peor que una estructura. Piense, por ejemplo, pair<int, bool> readFromFile(), donde int es el número de bytes leídos y bool es si se ha alcanzado el eof. Agregar una estructura en este caso me parece excesivo, especialmente porque no hay ambigüedad aquí.

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

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

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

  • puede usar la función de enlace 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 predefinidos < operadores, que pueden ahorrar tiempo.

Tiendo a usar tuplas junto con typedefs para aliviar al menos parcialmente el problema de la "tupla sin nombre". Por ejemplo, si tuviera una estructura de cuadrícula, 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 un tanto artificial, pero creo que la mayoría de las veces se encuentra con un medio feliz entre legibilidad, explicidad 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);

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

struct A
{
  int a;
  int b;
};

A menos que escriba un make_tuple equivalente o constructor, para usar esta estructura como 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, tome 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 en realidad 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.

Contraste 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 inicializar " Tuple " con el resultado de <=>, y genera el error que le permite especificar los valores correctos para el tercer parámetro.

Finalmente, la otra ventaja de las tuplas es que le 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 ());
}

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

Las tuplas serán más fáciles de escribir: no es necesario crear una nueva estructura para cada función que devuelve algo. La documentación sobre lo que va a dónde irá a la documentación de la función, que será necesaria de todos modos. Para usar la función, deberá leer la documentación de la función en cualquier caso y la tupla se explicará allí.

Estoy de acuerdo contigo 100% Roddy.

Para devolver múltiples valores de un método, tiene varias opciones además de las tuplas, cuál es la mejor según su caso:

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

  2. Usando " Fuera " parámetros Pase varios parámetros por referencia y devuelva varios valores asignándolos 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 de Out enfatiza este punto, además de que cada elemento recibe el nombre que merece.

Las tuplas son malvadas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top