¿Cuándo usar typedef?

14

Estoy un poco confundido acerca de si debería usar typedef en C ++ y cuándo. Siento que es un acto de equilibrio entre legibilidad y claridad.

Aquí hay una muestra de código sin ningún tipo de definición:

int sum(std::vector<int>::const_iterator first, 
        std::vector<int>::const_iterator last)
{
    static std::map<std::tuple<std::vector<int>::const_iterator,
                               std::vector<int>::const_iterator>,
                    int> lookup_table;

    std::map<std::tuple<std::vector<int>::const_iterator,
                        std::vector<int>::const_iterator>, int>::iterator lookup_it =
        lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

Bastante feo de la OMI. Así que agregaré algunos typedefs dentro de la función para que se vea mejor:

int sum(std::vector<int>::const_iterator first, 
        std::vector<int>::const_iterator last)
{
    typedef std::tuple<std::vector<int>::const_iterator,
                       std::vector<int>::const_iterator> Lookup_key;
    typedef std::map<Lookup_key, int> Lookup_table;

    static Lookup_table lookup_table;

    Lookup_table::iterator lookup_it = lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

El código sigue siendo un poco torpe, pero me deshago de la mayoría del material de pesadilla. Pero aún existen los iteradores de vector int, esta variante se deshace de ellos:

typedef std::vector<int>::const_iterator Input_iterator;

int sum(Input_iterator first, Input_iterator last)
{
    typedef std::tuple<Input_iterator, Input_iterator> Lookup_key;
    typedef std::map<Lookup_key, int> Lookup_table;

    static Lookup_table lookup_table;

    Lookup_table::iterator lookup_it = lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

Esto se ve limpio, pero ¿sigue siendo legible?

¿Cuándo debo usar un typedef? ¿Tan pronto como tenga un tipo de pesadilla? ¿Tan pronto como ocurre más de una vez? ¿Dónde debería ponerlos? ¿Debo usarlos en firmas de funciones o mantenerlos en la implementación?

futlib
fuente
1
No duplicado, pero algo relacionado con mi pregunta programmers.stackexchange.com/questions/130679/…
c0da
typedef Input_iterator std::vector<int>::const_iterator;está al revés
Per Johansson
1
¿Hay alguna diferencia entre legibilidad y claridad?
Neil
Cuando #defineno es lo suficientemente bueno.
Thomas Eding

Respuestas:

6

Su último ejemplo es muy legible, pero depende de dónde defina el typedef. Los typedefs de ámbito local (como en su segundo ejemplo) son IMVHO casi siempre una victoria.

Todavía me gusta su tercer ejemplo, pero es posible que desee pensar en el nombre y dar los nombres de los iteradores que indican la intención del contenedor.

Otra opción sería hacer una plantilla a partir de su función, para que también funcione con diferentes contenedores. En la línea de

template <typename Input_iterator> ... sum(Input_iterator first, Input_iterator last) 

que también está muy en el espíritu de la STL.

Fabio Fracassi
fuente
2

Piense en a typedefcomo el equivalente de declaración variable de una función: está ahí, así que ...

  • ... no tiene que repetirlo cuando vuelva a usar el mismo tipo (que hacen sus dos primeros ejemplos).
  • ... puede ocultar los detalles sangrientos del tipo para que no siempre estén expuestos.
  • ... asegúrese de que los cambios en el tipo se reflejen en todos los lugares donde se usan.

Personalmente, me da vueltas si tengo que leer nombres de tipo largos como std::vector<int>::const_iteratorrepetidamente.

Su tercer ejemplo no se repite innecesariamente y es más fácil de leer.

Blrfl
fuente
1

typedefLas declaraciones tienen esencialmente el mismo propósito que la encapsulación. Por esa razón, casi siempre encajan mejor en un archivo de encabezado, siguiendo las mismas convenciones de nomenclatura que sus clases, porque:

  • Si lo necesita typedef, es probable que las personas que llaman también lo hagan, especialmente como en su ejemplo donde se usa en los argumentos.
  • Si necesita cambiar el tipo por cualquier motivo, incluido el reemplazo con su propia clase, solo tendrá que hacerlo en un solo lugar.
  • Te hace menos propenso a errores debido a escribir repetidamente tipos complejos.
  • Oculta detalles de implementación innecesarios.

Por otro lado, su código de memorización sería mucho más limpio si lo resumiera más, como:

if (lookup_table.exists(first, last))
    return lookup_table.get(first, last);
Karl Bielefeldt
fuente
Su sugerencia puede parecer más limpia, pero pierde el tiempo haciendo la búsqueda dos veces.
Derek Ledbetter
Sí, eso fue un intercambio intencional. Sin embargo, hay formas de hacerlo con una sola búsqueda que son casi tan limpias, especialmente si no le preocupa la seguridad del hilo.
Karl Bielefeldt