Wann benutzt man typedef?


14

Ich bin ein bisschen verwirrt, ob und wann ich typedef in C ++ verwenden soll. Ich denke, es ist ein Spagat zwischen Lesbarkeit und Klarheit.

Hier ist ein Codebeispiel ohne 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;            

    ...
}

Ziemlich hässlich IMO. Deshalb füge ich ein paar typedefs in die Funktion ein, damit sie besser aussieht:

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;            

    ...
}

Der Code ist immer noch etwas ungeschickt, aber ich werde das meiste Albtraummaterial los. Aber es gibt immer noch die int-Vektor-Iteratoren, diese Variante beseitigt diese:

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;            

    ...
}

Das sieht sauber aus, ist es aber noch lesbar?

Wann sollte ich ein typedef verwenden? Sobald ich einen Albtraumtyp habe? Sobald es mehr als einmal auftritt? Wo soll ich sie hinstellen? Soll ich sie in Funktionssignaturen verwenden oder bei der Implementierung belassen?


1
Nicht duplizieren, aber irgendwie mit meiner Frage verwandt programmers.stackexchange.com/questions/130679/…
c0da

typedef Input_iterator std::vector<int>::const_iterator;ist rückwärts
Per Johansson

1
Gibt es einen Unterschied zwischen Lesbarkeit und Klarheit?
Neil

Wenn es nicht #definegut genug ist.
Thomas Eding

Antworten:


6

Ihr letztes Beispiel ist sehr gut lesbar, hängt jedoch davon ab, wo Sie die typedef definieren. Typedefs für den lokalen Bereich (wie in Ihrem zweiten Beispiel) sind IMVHO fast immer ein Gewinn.

Ihr drittes Beispiel gefällt mir immer noch am besten, aber vielleicht möchten Sie über die Benennung nachdenken und den Iteratoren Namen geben, die die Absicht des Containers angeben.

Eine andere Möglichkeit wäre, eine Vorlage aus Ihrer Funktion zu machen, damit sie auch mit anderen Containern funktioniert. Nach dem Vorbild von

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

Das ist auch sehr im Geiste der STL.


2

Stellen Sie sich eine typedefals Variablendeklaration äquivalent zu einer Funktion vor: Sie ist da, damit Sie ...

  • ... müssen Sie sich nicht wiederholen, wenn Sie denselben Typ wiederverwenden (wie in Ihren ersten beiden Beispielen).
  • ... kann die blutigen Details des Typs verbergen, sodass sie nicht immer sichtbar sind.
  • ... stellen Sie sicher, dass Änderungen am Typ überall dort wiedergegeben werden, wo sie verwendet werden.

Persönlich glasiere ich über, wenn ich lange Typnamen gerne std::vector<int>::const_iteratormehrmals lesen muss.

Ihr drittes Beispiel wiederholt sich nicht unnötig und ist am einfachsten zu lesen.


1

typedefDeklarationen dienen im Wesentlichen dem gleichen Zweck wie die Verkapselung. Aus diesem Grund passen sie nach den gleichen Namenskonventionen wie Ihre Klassen fast immer am besten in eine Header-Datei, weil:

  • Wenn Sie das brauchen typedef , werden wahrscheinlich auch die Anrufer es tun, insbesondere wie in Ihrem Beispiel, in dem es in den Argumenten verwendet wird.
  • Wenn Sie den Typ aus irgendeinem Grund ändern müssen, einschließlich des Ersetzens durch Ihre eigene Klasse, müssen Sie dies nur an einer Stelle tun.
  • Es macht Sie weniger anfällig für Fehler, da Sie immer wieder komplexe Typen schreiben.
  • Es werden unnötige Implementierungsdetails ausgeblendet.

Abgesehen davon wäre Ihr Memo-Code viel sauberer, wenn Sie ihn weiter abstrahieren würden, wie zum Beispiel:

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

Ihr Vorschlag sieht zwar sauberer aus, verschwendet jedoch Zeit, wenn Sie die Suche zweimal durchführen.
Derek Ledbetter

Ja, das war ein absichtlicher Kompromiss. Es gibt jedoch Möglichkeiten, dies mit einer einzelnen Suche zu tun, die fast genauso sauber ist, insbesondere, wenn Sie sich keine Sorgen um die Thread-Sicherheit machen.
Karl Bielefeldt
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.