Quando usare typedef?


14

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 alcun typedef:

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 sbarazzo della maggior parte del materiale da incubo. Ma ci sono ancora gli iteratori vettoriali int, questa variante elimina 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? Devo usarli nelle firme delle funzioni o conservarli nell'implementazione?


1
Non duplicato, ma in qualche modo correlato alla mia domanda programmers.stackexchange.com/questions/130679/…
c0da

typedef Input_iterator std::vector<int>::const_iterator;è al contrario
Per Johansson

1
C'è una differenza tra leggibilità e chiarezza?
Neil,

Quando #definenon è abbastanza buono.
Thomas Eding,

Risposte:


6

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

Mi piace ancora di più il tuo terzo esempio, ma potresti voler pensare alla denominazione e dare agli iteratori nomi che raccontano l'intenzione del contenitore.

Un'altra opzione sarebbe quella di creare un modello dalla tua funzione, in modo che funzioni anche con contenitori diversi. Sulla falsariga di

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

che è anche molto nello spirito della STL.


2

Pensa a typedefcome l'equivalente di una dichiarazione variabile 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 cruenti del tipo in modo che non siano sempre in mostra.
  • ... assicurati che le modifiche al tipo si riflettano ovunque vengano utilizzate.

Personalmente, smalto se devo leggere nomi di caratteri lunghi come std::vector<int>::const_iteratorripetutamente.

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


1

typedefle dichiarazioni 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 classi, perché:

  • Se è necessario typedef, è probabile che lo facciano anche i chiamanti, specialmente come nell'esempio in cui viene utilizzato negli argomenti.
  • Se è necessario modificare il tipo per qualsiasi motivo, inclusa la sostituzione con la propria classe, è necessario farlo in un solo posto.
  • Ti rende meno soggetto a errori a causa della ripetuta scrittura di tipi complessi.
  • Nasconde dettagli di implementazione non necessari.

A parte questo, il tuo codice di memoization sarebbe molto più pulito se lo astrassi ulteriormente, come:

if (lookup_table.exists(first, last))
    return lookup_table.get(first, last);

Il tuo suggerimento può sembrare più pulito, ma fa perdere tempo eseguendo la ricerca due volte.
Derek Ledbetter

Sì, quello era un compromesso intenzionale. Ci sono modi per farlo con una sola ricerca che sono quasi altrettanto puliti, soprattutto se non sei preoccupato per la sicurezza del thread.
Karl Bielefeldt,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.