Quand utiliser typedef?

14

Je suis un peu confus quant à savoir si et quand je dois utiliser typedef en C ++. Je pense que c'est un équilibre entre lisibilité et clarté.

Voici un exemple de code sans typedefs:

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;            

    ...
}

OMI assez moche. Je vais donc ajouter quelques typedefs dans la fonction pour la rendre plus belle:

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;            

    ...
}

Le code est encore un peu maladroit, mais je me débarrasse de la plupart des documents cauchemardesques. Mais il y a toujours les itérateurs vectoriels int, cette variante se débarrasse de ceux-ci:

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;            

    ...
}

Cela semble propre, mais est-il toujours lisible?

Quand dois-je utiliser un typedef? Dès que j'ai un type de cauchemar? Dès qu'il se produit plus d'une fois? Où dois-je les mettre? Dois-je les utiliser dans les signatures de fonction ou les conserver dans l'implémentation?

futlib
la source
1
Pas en double, mais quelque peu lié à ma question programmers.stackexchange.com/questions/130679/…
c0da
typedef Input_iterator std::vector<int>::const_iterator;est à l'envers
Per Johansson
1
Il y a une différence entre lisibilité et clarté?
Neil
Quand ce #definen'est pas assez bon.
Thomas Eding

Réponses:

6

Votre dernier exemple est très lisible, mais cela dépend de l'endroit où vous définissez le typedef. Les typedefs de portée locale (comme dans votre deuxième exemple) sont IMVHO presque toujours une victoire.

J'aime toujours mieux votre troisième exemple, mais vous voudrez peut-être penser au nom et donner aux itérateurs des noms qui indiquent l'intention du conteneur.

Une autre option serait de créer un modèle à partir de votre fonction, afin qu'il fonctionne également avec différents conteneurs. Le long des lignes de

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

ce qui est également très dans l'esprit de la STL.

Fabio Fracassi
la source
2

Considérez un typedefcomme l'équivalent de déclaration de variable d'une fonction: il est là donc vous ...

  • ... vous n'avez pas à vous répéter lorsque vous réutilisez le même type (ce que font vos deux premiers exemples).
  • ... peut masquer les détails sanglants du type afin qu'ils ne soient pas toujours affichés.
  • ... assurez-vous que les modifications apportées au type sont reflétées partout où elles sont utilisées.

Personnellement, je glaçure si je dois lire des noms de caractères longs comme à std::vector<int>::const_iteratorplusieurs reprises.

Votre troisième exemple ne se répète pas inutilement et est plus facile à lire.

Blrfl
la source
1

typedefles déclarations ont essentiellement le même but que l'encapsulation. Pour cette raison, ils s'intègrent presque toujours mieux dans un fichier d'en-tête, en suivant les mêmes conventions de dénomination que vos classes, car:

  • Si vous en avez besoin typedef, il y a de fortes chances que les appelants le fassent aussi, surtout comme dans votre exemple où il est utilisé dans les arguments.
  • Si vous devez changer le type pour une raison quelconque, y compris le remplacer par votre propre classe, vous n'aurez qu'à le faire au même endroit.
  • Cela vous rend moins sujet aux erreurs dues à l'écriture répétée de types complexes.
  • Il masque les détails d'implémentation inutiles.

En passant, votre code de mémorisation serait beaucoup plus propre si vous l'abrégiez davantage, comme:

if (lookup_table.exists(first, last))
    return lookup_table.get(first, last);
Karl Bielefeldt
la source
Votre suggestion peut sembler plus propre, mais elle fait perdre du temps en effectuant la recherche deux fois.
Derek Ledbetter
Oui, c'était un compromis intentionnel. Il existe cependant des moyens de le faire avec une seule recherche qui sont presque aussi propres, surtout si vous n'êtes pas inquiet pour la sécurité des threads.
Karl Bielefeldt