Quando usare typedef?

13

Sono un po 'confuso su se e quando dovrei usare typedef in C ++. Sento che è un atto di equilibrio tra leggibilità e chiarezza.

Ecco un esempio di codice senza errori 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;            

    ...
}

IMO piuttosto brutto. Quindi aggiungerò alcuni typedef all'interno della funzione per renderlo più bello:

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;            

    ...
}

Il codice è ancora un po 'goffo, ma mi libero del materiale da incubo. Ma ci sono ancora gli iteratori vettoriali int, questa variante si sbarazza di quelli:

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;            

    ...
}

Sembra pulito, ma è ancora leggibile?

Quando dovrei usare un typedef? Non appena ho un tipo da incubo? Non appena si verifica più di una volta? Dove dovrei metterli? Dovrei usarli nelle firme delle funzioni o tenerle all'implementazione?

    
posta futlib 07.05.2012 - 07:05
fonte

3 risposte

6

Il tuo ultimo esempio è molto leggibile, ma dipende da dove si definisce typedef. I typedef dell'ambito locale (come nel tuo secondo esempio) sono IMVHO quasi sempre una vittoria.

Mi piace ancora il tuo terzo esempio, ma potresti voler pensare al nome e dare i nomi degli iteratori che indicano l'intenzione del contenitore.

Un'altra opzione sarebbe quella di creare un modello fuori dalla tua funzione, in modo che funzioni anche con diversi contenitori. Lungo le linee di

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

che è anche molto nello spirito dell'STL.

    
risposta data 07.05.2012 - 11:23
fonte
2

Pensa a typedef come equivalente alla dichiarazione di una funzione: è lì così tu ...

  • ... non devi ripetere te stesso quando riutilizzi lo stesso tipo (come fanno i tuoi primi due esempi).
  • ... può nascondere i dettagli sanguinosi del tipo in modo che non siano sempre in mostra.
  • ... assicurati che le modifiche al tipo siano riflesse ovunque vengano utilizzate.

Personalmente, smascherò se devo leggere più volte nomi lunghi come std::vector<int>::const_iterator .

Il tuo terzo esempio non si ripete inutilmente ed è più facile da leggere.

    
risposta data 07.05.2012 - 16:15
fonte
1
Le dichiarazioni di

typedef hanno essenzialmente lo stesso scopo dell'incapsulamento. Per questo motivo, si adattano quasi sempre al meglio in un file di intestazione, seguendo le stesse convenzioni di denominazione delle tue classi, perché:

  • Se hai bisogno di typedef , è probabile che lo faranno anche i chiamanti, specialmente come nell'esempio in cui è usato negli argomenti.
  • Se devi cambiare il tipo per qualsiasi ragione, inclusa la sostituzione con la tua classe, dovrai farlo in un unico posto.
  • Ti rende meno incline agli errori a causa della ripetuta scrittura di tipi complessi.
  • Nasconde i dettagli di implementazione non necessari.

Per inciso, il codice di memoizzazione sarebbe molto più pulito se lo avessi ulteriormente astratto, come:

if (lookup_table.exists(first, last))
    return lookup_table.get(first, last);
    
risposta data 07.05.2012 - 18:22
fonte

Leggi altre domande sui tag