2012-04-21 32 views
35

¿Cuáles son buenos casos de uso para usar tuplas en C++ 11? Por ejemplo, tengo una función que define una estructura local de la siguiente manera:¿Cuáles son los buenos casos de uso para las tuplas en C++ 11?

template<typename T, typename CmpF, typename LessF> 
void mwquicksort(T *pT, int nitem, const int M, CmpF cmp, LessF less) 
{ 
    struct SI 
    { 
    int l, r, w; 
    SI() {} 
    SI(int _l, int _r, int _w) : l(_l), r(_r), w(_w) {} 
    } stack[40]; 

    // etc 

estaba considerando para reemplazar la estructura SI con un std::tuple<int,int,int>, que es una declaración mucho más corto con los constructores convenientes y operadores ya predefinidos, pero con las siguientes desventajas:

  • Los elementos Tuple están ocultos en estructuras oscuras, definidas por la implementación. Aunque Visual Studio interpreta y muestra sus contenidos muy bien, todavía no puedo poner puntos de interrupción condicionales que dependen del valor de los elementos de tupla.
  • Acceder a campos de tuplas individuales (get<0>(some_tuple)) es mucho más detallado que acceder a elementos de estructura (s.l).
  • Acceder a los campos por nombre es mucho más informativo (¡y más corto!) Que por índice numérico.

La función tie se ocupa de los dos últimos puntos. Dadas estas desventajas, ¿cuál sería un buen caso de uso para las tuplas?

ACTUALIZACIÓN Resulta que VS2010 SP1 depurador no puede mostrar el contenido de la siguiente matriz std::tuple<int, int, int> stack[40], pero funciona bien cuando se codifica con una estructura. Entonces, la decisión es básicamente obvia: si alguna vez tiene que inspeccionar sus valores, use una estructura [esp. importante con depuradores como GDB].

+3

el problema de indexación puede resolverse con conste/enums definidos apropiados. – KillianDS

+2

¿Escribió una función de clasificación que toma 'T *, size'? lolwot, ¿por qué alguna vez harías tal cosa? – Puppy

+0

@DeadMG Solo para que pueda darle algo de material para trolling. – zvrba

Respuesta

24

Bueno, en mi humilde opinión, la parte más importante es el código genérico. Escribir código genérico que funcione en todo tipo de estructuras es mucho más difícil que escribir genéricos que funcionen en tuplas. Por ejemplo, la función std::tie que mencionó usted mismo sería casi imposible de hacer para las estructuras.

Esto le permite hacer cosas como esta:

  • parámetros de la función tienda para la ejecución retardada (por ejemplo this question)
  • devuelven varios parámetros sin engorrosos (des) embalaje con std::tie
  • Combine (no igual -typed) conjuntos de datos (por ejemplo, desde la ejecución en paralelo), se puede hacer tan simple como std::tuple_cat.

La cuestión es que no se detiene con estos usos, las personas pueden ampliar esta lista y escribir funcionalidades genéricas basadas en tuplas que son mucho más difíciles de hacer con las estructuras. Quién sabe, tal vez mañana alguien encuentre un uso brillante para fines de serialización.

+1

Marcado como respuesta, ya que me ha informado acerca de 'tuple_cat', que no figuraba en MSDN para VS2010. Pero, ¿cómo declararías el valor de retorno de una función que devuelve 'tuple_cat (x, y)' para algunas tuplas 'x' y' y' cuyos tipos * no * son fácilmente deducibles de los argumentos de función? – zvrba

+0

Me interesa su primer caso de uso para la tupla, ¿no sería más fácil almacenar los parámetros de la función usando 'std :: bind' que usar una tupla? Esto se hizo sobre la pregunta a la que se vincula, pero la respuesta fue que alguien más les estaba dando la tupla. –

1

Interoperación con otros lenguajes de programación que usan tuplas y devuelven valores múltiples sin que la persona que llama tenga que entender ningún tipo adicional. Esos son los primeros dos que vienen a mi mente.

+3

@zvrba: eso es interpolación de tiempo de "compilación", la interoperabilidad de tiempo de ejecución también es posible.Además, otro idioma puede usar interfaces C++. Apuesto a que cosas como 'boost.python' podrían beneficiarse de esto. – KillianDS

+2

@zvrba: C++ interopera con .NET, Python, Lua, Ruby, Perl, Fortran, etc. Interoperar aquí significa poder enlazar y llamar funciones ... y si vas a escribir bibliotecas de pegamento como Boost Python, Luabind , Rcpp, etc., es agradable tener características como tuplas disponibles como parte del vocabulario del lenguaje para que la interoperabilidad aparezca lo más fluida posible. –

+1

@JohnZwinck "Interoperar" significa para mí llamar funciones en otros idiomas sin escribir capas de pegamento. Eso abarca C, C++ y Fortran, que he olvidado. Pero acepto que es bueno tener tuplas que permitan reflejar más de cerca la API de "otros idiomas". – zvrba

54

Es una forma fácil de devolver múltiples valores de una función;

std::tuple<int,int> fun(); 

Los valores resultantes se pueden utilizar con elegancia de la siguiente manera:

int a; 
int b; 
std::tie(a,b)=fun(); 
+0

No puede usar 'auto'. No puede usar constructores de copia (y por lo tanto no puede usar _RVO_). Si fuera posible escribir 'std :: tie (auto a, auto b) = fun();' tendría sentido. Puede escribir 'auto tuple = func();' y luego usar 'std :: get <0> (tuple);' y 'std :: get <1> (tuple);' pero eso es horrible. –

12

¿Alguna vez ha usado std::pair? Muchos de los lugares que usaría std::tuple son similares, pero no están limitados a exactamente dos valores.

Las desventajas que enumera para las tuplas también se aplican a std :: pair, a veces desea un tipo más expresivo con mejores nombres para sus miembros que first y second, pero a veces no es necesario. Lo mismo se aplica a las tuplas.

9

Creo que NO hay un buen uso para las tuplas fuera de los detalles de implementación de alguna función de biblioteca genérica.

El (posible) ahorro al escribir no compensa las pérdidas en las propiedades de auto-documentación del código resultante.

Sustituyendo tuplas para estructuras que simplemente quitan un nombre significativo para un campo, reemplazando el nombre del campo con un "número" (como el concepto mal concebido de un std :: pair).

Devolver múltiples valores utilizando tuplas es mucho menos autodocumentado que las alternativas: devolver tipos con nombre o usar referencias con nombre. Sin esta auto-documentación, es fácil confundir el orden de los valores devueltos, si son mutuamente convertibles.

+0

Pasar argumentos por orden no es mucho mejor que devolver múltiples valores usando tupla. Aún puedes confundir el orden de los parámetros. Pero al menos cada parámetro tiene su propio nombre. Los valores de Tuple no. –

18

Creo que la mayoría de uso tuple s proviene de std::tie:

bool MyStruct::operator<(MyStruct const &o) const 
{ 
    return std::tie(a, b, c) < std::tie(o.a, o.b, o.c); 
} 

Junto con muchos otros ejemplos en las respuestas aquí. Sin embargo, considero que este ejemplo es el más comúnmente útil, ya que ahorra mucho esfuerzo de cómo solía ser en C++ 03.

+0

Sí, acabo de enterarme e hice uso de esto hoy. Usé un método un poco más elaborado/genérico, que podría desarrollar en otra respuesta. –

6

Los casos de uso real son situaciones en las que tiene elementos innombrables: plantillas variadas y funciones lambda. En ambas situaciones, puede tener elementos sin nombre con tipos desconocidos y, por lo tanto, la única forma de almacenarlos es una estructura con elementos sin nombre: std :: tuple. En cualquier otra situación, usted tiene un número conocido de elementos con nombres conocidos y puede usar una estructura común, que es la respuesta superior el 99% del tiempo.

Por ejemplo, NO debe usar std :: tuple para tener "retornos múltiples" de funciones o plantillas comunes con un número fijo de entradas genéricas. Usa una estructura real para eso. Un objeto real es MUCHO más "genérico" que el std :: tuple cookie-cutter, porque puedes darle a un objeto real literalmente cualquier interfaz. También le dará mucha más seguridad y flexibilidad en las bibliotecas públicas.

Basta con comparar estas funciones miembro de la clase 2:

std::tuple<double, double, double> GetLocation() const; // x, y, z 

GeoCoordinate GetLocation() const; 

Con un verdadero 'geo coordinar' objeto que pueda proporcionar un bool operador() que devuelve false si el objeto padre no tuvo lugar. A través de sus API, los usuarios podrían obtener las ubicaciones x, y, z. Pero esto es lo más importante: si decido hacer GeoCoordinate 4D agregando un campo de tiempo en 6 meses, el código de los usuarios actuales no se romperá. No puedo hacer eso con la versión std :: tuple.

1

que no puedo comentar sobre la respuesta de mirk, así que voy a tener que dar una respuesta por separado:

creo tuplas se añadieron a la norma también para permitir la programación de estilo funcional.A modo de ejemplo, mientras que un código como

void my_func(const MyClass& input, MyClass& output1, MyClass& output2, MyClass& output3) 
{ 
    // whatever 
} 

es omnipresente en ++ C tradicional, porque es la única manera de tener múltiples objetos devueltos por una función, esto es una abominación para la programación funcional. Ahora usted puede escribir

tuple<MyClass, MyClass, MyClass> my_func(const MyClass& input) 
{ 
    // whatever 
    return tuple<MyClass, MyClass, MyClass>(output1, output2, output3); 
} 

lo tanto tener la oportunidad de evitar los efectos secundarios y la mutabilidad, para permitir la canalización, y, al mismo tiempo, para preservar la fuerza semántica de su función.

+1

objeción estándar: No 'struct MyFuncReturn {MyClass output1, output2; }; MyFuncReturn my_func (MyClass const & input); '¿ser superior? Si estamos hablando de "fuerza semántica", entonces una 'estructura' construida especialmente tiene mucho más de eso que cualquier 'tupla'. Claro, es paralelo al significado existente de los argumentos de la función, donde solo importa el orden, no los nombres, lo que podría ser suficiente para usted. Sin embargo, el mismo argumento, el significado semántico, es la razón por la cual las personas intentan constantemente encontrar la manera de hacer que los argumentos con nombre funcionen en idiomas como este, sin introducir una rotura/fragilidad total. Se reirían de 'tuple'. –

Cuestiones relacionadas