Kiedy stosować typedef?

14

Jestem trochę zdezorientowany, czy i kiedy powinienem używać typedef w C ++. Uważam, że to balansuje pomiędzy czytelnością a klarownością.

Oto przykładowy kod bez żadnych 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;            

    ...
}

Dość brzydka IMO. Dodam więc kilka funkcji typedef do funkcji, aby wyglądała ładniej:

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;            

    ...
}

Kod jest wciąż nieco niezręczny, ale pozbywam się większości koszmarów. Ale wciąż istnieją iteratory wektorów int, ten wariant pozbywa się tych:

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;            

    ...
}

Wygląda to czysto, ale czy nadal jest czytelne?

Kiedy powinienem użyć typedef? Jak tylko mam typ koszmaru? Jak tylko pojawi się więcej niż jeden raz? Gdzie mam je umieścić? Czy powinienem ich używać w sygnaturach funkcji, czy zachować do implementacji?

futlib
źródło
1
Nie duplikowane, ale nieco związane z moim pytaniem programiści.stackexchange.com
questions
typedef Input_iterator std::vector<int>::const_iterator;jest odwrotnie
Per Johansson,
1
Czy jest różnica między czytelnością a klarownością?
Neil
Kiedy #definenie jest wystarczająco dobry.
Thomas Eding,

Odpowiedzi:

6

Twój ostatni przykład jest bardzo czytelny, ale zależy to od tego, gdzie zdefiniujesz typedef. Typy typowe dla zakresu lokalnego (jak w twoim drugim przykładzie) są prawie zawsze wygrane przez IMVHO.

Nadal podoba mi się twój trzeci przykład, ale możesz pomyśleć o nazwie i nadać iteratorom nazwy, które określają przeznaczenie kontenera.

Inną opcją byłoby utworzenie szablonu z funkcji, aby działał on również z różnymi kontenerami. Wzdłuż linii

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

co jest również bardzo zgodne z duchem STL.

Fabio Fracassi
źródło
2

Pomyśl o typedefekwiwalencie deklaracji zmiennej funkcji: jest tam, więc ...

  • ... nie musisz się powtarzać, gdy ponownie używasz tego samego typu (co robią pierwsze dwa przykłady).
  • ... może ukryć krwawe szczegóły tego typu, aby nie zawsze były widoczne.
  • ... upewnij się, że zmiany typu są odzwierciedlane w każdym miejscu, w którym są używane.

Osobiście glazuruję, jeśli muszę czytać długie nazwy, takie jak std::vector<int>::const_iteratorwielokrotnie.

Twój trzeci przykład nie powtarza się niepotrzebnie i jest najłatwiejszy do odczytania.

Blrfl
źródło
1

typedefdeklaracje służą zasadniczo temu samemu celowi, co enkapsulacja. Z tego powodu prawie zawsze najlepiej pasują do pliku nagłówkowego, zachowując te same konwencje nazewnictwa jak Twoje klasy, ponieważ:

  • Jeśli potrzebujesz typedef , istnieje prawdopodobieństwo, że dzwoniący również będą, szczególnie jak w twoim przykładzie, gdzie jest on używany w argumentach.
  • Jeśli musisz zmienić typ z dowolnego powodu, w tym zastąpić go własną klasą, musisz to zrobić tylko w jednym miejscu.
  • To sprawia, że ​​jesteś mniej podatny na błędy z powodu wielokrotnego pisania złożonych typów.
  • Ukrywa niepotrzebne szczegóły implementacji.

Nawiasem mówiąc, kod memoizacji byłby o wiele bardziej przejrzysty, gdybyś go dalej wyodrębnił, na przykład:

if (lookup_table.exists(first, last))
    return lookup_table.get(first, last);
Karl Bielefeldt
źródło
Twoja sugestia może wyglądać na czystszą, ale marnuje czas, wykonując wyszukiwanie dwukrotnie.
Derek Ledbetter
Tak, to był celowy kompromis. Istnieją jednak sposoby, aby to zrobić za pomocą pojedynczego wyszukiwania, które są prawie tak samo czyste, zwłaszcza jeśli nie martwisz się bezpieczeństwem wątków.
Karl Bielefeldt,