Wann sollte ich typedef in C ++ verwenden?


68

In meinen Jahren der C ++ (MFC) -Programmierung in hatte ich nie das Bedürfnis, sie zu verwenden typedef, daher weiß ich nicht wirklich, wofür sie verwendet wird. Wo soll ich es verwenden? Gibt es reale Situationen, in denen die Verwendung typedefbevorzugt wird? Oder ist das wirklich eher ein C-spezifisches Schlüsselwort?

Antworten:


88

Vorlagen-Metaprogrammierung

typedefist für viele Metaprogrammieraufgaben für Vorlagen erforderlich. Wenn eine Klasse als "Typfunktion zur Kompilierungszeit" behandelt wird, wird a als "Typwert zur Kompilierungszeit" verwendet, um den resultierenden Typ zu erhalten. Betrachten Sie beispielsweise eine einfache Metafunktion zum Konvertieren eines Zeigertyps in seinen Basistyp:typedef

template<typename T>
struct strip_pointer_from;

template<typename T>
struct strip_pointer_from<T*> {   // Partial specialisation for pointer types
    typedef T type;
};

Beispiel: Der Typausdruck wird als strip_pointer_from<double*>::typeausgewertet double. Beachten Sie, dass die Metaprogrammierung von Vorlagen außerhalb der Bibliotheksentwicklung nicht häufig verwendet wird.

Vereinfachung der Funktionszeigertypen

typedefist hilfreich , um komplizierten Funktionszeigertypen einen kurzen, scharfen Alias ​​zu geben:

typedef int (*my_callback_function_type)(int, double, std::string);

void RegisterCallback(my_callback_function_type fn) {
    ...
}

1
Notwendig? Möchtest du ein Beispiel geben? Ich kann mir keine Fälle vorstellen, in denen es notwendig wäre.
Jalf

10
Für C ++ 11 überlässt das Hinzufügen der Syntax "using a = b" das Schlüsselwort "typedef" hauptsächlich den Erinnerungen, da typedef immer verwirrend rückwärts und inkonsistent mit #define war (jetzt habe ich die beiden nie versehentlich umgekehrt, weil es das ist wie bei der Reihenfolge der variablen Zuordnung).
Dwayne Robinson

37

In Bjarnes Buch heißt es, dass Sie typedef verwenden können, um Portabilitätsprobleme zwischen Systemen mit unterschiedlichen Ganzzahlgrößen zu lösen. (Dies ist eine Paraphrase)

Auf einer Maschine mit sizeof(int)4 können Sie

typedef int int32;

Dann int32überall in Ihrem Code verwenden. Wenn Sie zu einer Implementierung von C ++ mit sizeof(int)2 wechseln, können Sie einfach die änderntypdef

typedef long int32;

und Ihr Programm wird weiterhin an der neuen Implementierung arbeiten.


12
Natürlich würden Sie das uint32_t von <stdint.h> verwenden, obwohl richtig? :)
Greg Rogers

Und nur für die normalerweise seltenen Fälle, in denen Sie genau 32 Bit benötigen.
KeithB

7
@KeithB: Ich denke, die Seltenheit hängt davon ab, welche Art von Entwicklung Sie machen. Entwickler eingebetteter Systeme und solche, die sich häufig mit Dateiformaten befassen, sind zwei Fälle, an die ich denken kann, wenn Sie häufig genaue Größen kennen müssen.
j_random_hacker

22

Verwendung mit Funktionszeiger

Funktionszeigerdeklarationen ausblenden Mit einem typedef

void (*p[10]) (void (*)() );

Nur wenige Programmierer können erkennen, dass p ein "Array von 10 Zeigern auf eine Funktion ist, die void zurückgibt, und einen Zeiger auf eine andere Funktion, die void zurückgibt und keine Argumente akzeptiert." Die umständliche Syntax ist nahezu nicht zu entziffern. Sie können es jedoch erheblich vereinfachen, indem Sie typedef-Deklarationen verwenden. Deklarieren Sie zunächst ein typedef für "Zeiger auf eine Funktion, die void zurückgibt und keine Argumente akzeptiert" wie folgt:

  typedef void (*pfv)();

Als nächstes deklarieren Sie ein anderes typedef für "Zeiger auf eine Funktion, die void zurückgibt und ein pfv nimmt" basierend auf dem zuvor deklarierten typedef:

 typedef void (*pf_taking_pfv) (pfv);

Nachdem wir nun den pf_taking_pfv-Typedef als Synonym für den unhandlichen "Zeiger auf eine Funktion erstellt haben, die void zurückgibt und einen pfv nimmt", ist es ein Kinderspiel, ein Array von 10 solcher Zeiger zu deklarieren:

  pf_taking_pfv p[10];

von


18

Nur um einige Beispiele für die genannten Dinge zu nennen: STL-Container.

 typedef std::map<int,Froboz> tFrobozMap;
 tFrobozMap frobozzes; 
 ...
 for(tFrobozMap::iterator it=frobozzes.begin(); it!=map.end(); ++it)
 {
     ...
 }

Es ist nicht ungewöhnlich, auch nur typedefs wie zu verwenden

typedef tFrobozMap::iterator tFrobozMapIter;
typedef tFrobozMap::const_iterator tFrobozMapCIter;

Ein weiteres Beispiel: Verwenden gemeinsamer Zeiger:

class Froboz;
typedef boost::shared_ptr<Froboz> FrobozPtr;

[Update] Laut Kommentar - wo sollen sie abgelegt werden?

Das letzte Beispiel - Verwenden shared_ptr- ist einfach: Sind echtes Header-Material - oder zumindest ein Forward-Header. Sie benötigen die Vorwärtsdeklaration für shared_ptr ohnehin, und einer der deklarierten Vorteile besteht darin, dass die Verwendung mit einer Vorwärtsdeklaration sicher ist.

Anders ausgedrückt: Wenn es einen shared_ptr gibt, sollten Sie den Typ wahrscheinlich nur über einen shared_ptr verwenden, sodass das Trennen der Deklarationen wenig Sinn macht.

(Ja, xyzfwd.h ist ein Schmerz. Ich würde sie nur in Hotspots verwenden - in dem Wissen, dass Hotspots schwer zu identifizieren sind. Beschuldigen Sie das C ++ - Kompilierungs- + Linkmodell ...)

Containertypedefs verwende ich normalerweise dort, wo die Containervariable deklariert ist - z. B. lokal für eine lokale Variable, als Klassenmitglieder, wenn die tatsächliche Containerinstanz ein Klassenmitglied ist. Dies funktioniert gut, wenn der tatsächliche Containertyp ein Implementierungsdetail ist und keine zusätzliche Abhängigkeit verursacht.

Wenn sie Teil einer bestimmten Schnittstelle werden, werden sie zusammen mit der Schnittstelle deklariert, mit der sie verwendet werden, z

// FrobozMangler.h
#include "Froboz.h"
typedef std::map<int, Froboz> tFrobozMap;
void Mangle(tFrobozMap const & frobozzes); 

Dies wird problematisch, wenn der Typ ein Bindungselement zwischen verschiedenen Schnittstellen ist - dh der gleiche Typ wird von mehreren Headern benötigt. Einige Lösungen:

  • deklarieren Sie es zusammen mit dem enthaltenen Typ (geeignet für Container, die häufig für diesen Typ verwendet werden)
  • Verschieben Sie sie in einen separaten Header
  • Wechseln Sie zu einem separaten Header und machen Sie ihn zu einer Datenklasse, in der der eigentliche Container wieder ein Implementierungsdetail ist

Ich bin damit einverstanden, dass die beiden letzteren nicht so toll sind, ich würde sie nur verwenden, wenn ich in Schwierigkeiten gerate (nicht proaktiv).


Können Sie hierzu bewährte Methoden für Header-Dateien diskutieren? Die Optionen scheinen darin zu bestehen, typedef in Froboz.h zu platzieren, was zu Headerabhängigkeit und langen Erstellungszeiten führt. Einfügen der typedefs in Frobozfwd.h (per Effective C ++), was die Wartbarkeit beeinträchtigt (zwei Header für alles); oder die typedefs in FroCommon.h einfügen, wodurch die Wiederverwendbarkeit beeinträchtigt wird. Gibt es einen besseren Weg?
Rob Napier

1
Vielen Dank. Ich habe hier eine längere Version dieser Frage gestellt: stackoverflow.com/questions/2356548/… . Ich fürchte, ich bin bisher zu den gleichen Schlussfolgerungen gekommen, nämlich, dass es keine wirklich gute Antwort gibt, die Sie konsequent verwenden können, was bedeutet, dass es schwierig ist, eine Regel zu haben, der jeder im Team folgen und sich darauf verlassen kann. "Für diesen Header müssen Sie die fwd-Version verwenden, aber für diesen Header enthalten Sie nur den Basisheader , und dieses verwandte Material wird hier gemeinsam definiert. H ..." Wie schreibt jemand jemals wartbares und wiederverwendbares C ++? (ObjC hat mich verwöhnt ...: D)
Rob Napier

5

typedef ist in vielen Situationen nützlich.

Grundsätzlich können Sie einen Alias ​​für einen Typ erstellen. Wenn Sie den Typ ändern müssen, kann der Rest des Codes unverändert bleiben (dies hängt natürlich vom Code ab). Angenommen, Sie möchten einen C ++ - Vektor iterieren

vector<int> v;

...

for(vector<int>::const_iterator i = v->begin(); i != v.end(); i++) {

// Stuff here

}

In Zukunft könnten Sie daran denken, den Vektor mit einer Liste zu ändern, da die Art der Operationen, die Sie darauf ausführen müssen. Ohne typedef müssen Sie ALLE Vektorvorkommen in Ihrem Code ändern. Aber wenn Sie so etwas schreiben:

typedef vector<int> my_vect;

my_vect v;

...

for(my_vect::const_iterator i = v->begin(); i != v.end(); i++) {

// Stuff here

}

Jetzt müssen Sie nur noch eine Codezeile ändern (dh von " typedef vector<int> my_vect" nach " typedef list<int> my_vect") und alles funktioniert.

typedef spart Ihnen auch Zeit, wenn Sie komplexe Datenstrukturen haben, die sehr lang zu schreiben (und schwer zu lesen) sind.


1
Das ist keine wirklich gute Begründung für die Verwendung von typedefs: Sie sollten dafür einen Schnittstellentyp verwenden (abstrakter Datentyp, wenn Sie dies bevorzugen). Aus diesem Grund mussten Sie "Abhängig vom Code" hinzufügen. Es sollte der Code sein, der vom Typ abhängt :)
xtofl

Und C ++ 0x kommt! AWW-TO! AWW-TO! AWW-TO!
David Thornley

3
@xtofl: Typedefs und Schnittstellentypen sind beide gültige Methoden, um dieses spezielle Problem zu lösen. Schnittstellentypen sind allgemeiner, aber auch schwerer. Die korrekte Verwendung von Schnittstellentypen impliziert auch, dass alle Aufrufe virtuell sind - ein hoher Preis für die Weiterentwicklung / Dereferenzierung von Iteratoren.
j_random_hacker

5

Ein guter Grund, typedef zu verwenden, ist, wenn sich der Typ von etwas ändern kann. Angenommen, 16-Bit-Ints eignen sich derzeit für die Indizierung eines Datasets, da Sie auf absehbare Zeit weniger als 65535 Elemente haben und die Speicherplatzbeschränkungen erheblich sind oder eine gute Cache-Leistung erforderlich sind. Wenn Sie Ihr Programm jedoch nicht für ein Dataset mit mehr als 65535 Elementen verwenden müssen, möchten Sie problemlos zu einer breiteren Ganzzahl wechseln können. Verwenden Sie ein typedef, und Sie müssen dies nur an einer Stelle ändern.


1
Was ist, wenn ich von int zu long ohne Vorzeichen wechseln möchte? Ich müsste meinen gesamten Quellcode auf Überläufe usw. überprüfen ... -> kein guter Grund, ein typedef zu verwenden! Verwenden Sie stattdessen eine Wrapper-Schnittstelle.
xtofl

Oder geben Sie dem typedef einen sinnvollen Namen, der angibt, auf welche Eigenschaften (wie Größe und Signatur) vertraut werden kann, und ändern Sie ihn dann nicht so, dass diese Eigenschaften beschädigt werden. stdint hat einige gute Modelle dafür, wie int_fast * und int_least *. Dort ist keine große Schnittstelle erforderlich.
Steve Jessop

@xtofl: Wenn Sie sich Sorgen über Überläufe machen, würden Sie bereits Überprüfungen mit numeric_limits <my_int> durchführen, und diese Überprüfungen werden weiterhin das Richtige tun, wenn Sie ändern, wie my_int typisiert wird.
j_random_hacker

Wenn Sie nur int für die Indizierung verwenden, entspricht die Größe von (int) normalerweise der Bit'edness des Prozessors und ist die Grenze für die Indizierbarkeit des Speichers. Wenn Sie also ein int verwenden können, befinden Sie sich nie in dieser Situation.
Joseph Garvin

4

typedefErmöglicht nicht nur einen Alias ​​für komplexe Typen, sondern bietet Ihnen auch einen natürlichen Ort zum Dokumentieren eines Typs. Ich benutze es manchmal zu Dokumentationszwecken.

Es gibt auch Zeiten, in denen ich ein Array von Bytes verwende. Jetzt könnte ein Array von Bytes eine Menge Dinge bedeuten. typedefmacht es praktisch, mein Byte-Array als "hash32" oder "fileContent" zu definieren, um meinen Code besser lesbar zu machen.


2

Reale Verwendung von typedef:

  • Bereitstellung freundlicher Aliase für langatmige Vorlagen
  • Bereitstellung benutzerfreundlicher Aliase für Funktionszeigertypen
  • Bereitstellung lokaler Bezeichnungen für Typen, z.

    template<class _T> class A
    {
        typedef _T T;
    };
    
    template<class _T> class B
    {
        void doStuff( _T::T _value );
    };
    

Ich denke nicht, dass sich das kompilieren wird. Meinen Sie vielleicht "void doStuff (Typname A <_T> :: T _value);"? (Sie benötigen dort das Schlüsselwort typename, da der Compiler A <_T> :: T andernfalls als Namen einer Mitgliedsvariablen interpretiert.)
j_random_hacker

2

Es gibt einen anderen Anwendungsfall für die Verwendung von typedef, wenn wir eine Art containerunabhängigen Code aktivieren möchten (aber nicht genau!).

Nehmen wir an, Sie haben Unterricht:

Class CustomerList{

public:
    //some function
private:
    typedef list<Customer> CustomerContainer;
    typedef CustomerContainer::iterator Cciterator;
};

Der obige Code kapselt die interne Containerimplementierung mit typedef, und selbst wenn der Listencontainer in Zukunft in Vektor oder Deque geändert werden muss, muss sich der Benutzer der CustomerList-Klasse nicht um die genaue Containerimplementierung kümmern.

Daher kapselt das typedef und hilft uns etwas beim Schreiben von containerunabhängigem Code


0

Wann immer es die Quelle klarer oder besser lesbar macht.

Ich verwende eine Art typedef in C # für Generika / Vorlagen. Ein "NodeMapping" ist einfach besser zu lesen / zu verwenden und zu verstehen als viel "Dictionary <string, XmlNode>". MEINER BESCHEIDENEN MEINUNG NACH. Daher würde ich es für Vorlagen empfehlen.


0

Typedef ermöglicht Flexibilität in Ihrer Klasse. Wenn Sie den Datentyp im Programm ändern möchten, müssen Sie nicht mehrere Speicherorte ändern, sondern nur ein Vorkommen.

typedef <datatype example  int or double> value_type

Sie können stattdessen einen Namen angeben value_type, dies value_typeist jedoch normalerweise der Standardname.

Sie können also typedef like verwenden

value_type i=0;     //same as a int or double i=0; 

-2

... und Sie brauchen kein Typedef für eine Aufzählung oder eine Struktur.

Oder tust du?

typedef enum { c1, c2 } tMyEnum;
typedef struct { int i; double d; } tMyStruct;

kann besser geschrieben werden als

enum tMyEnum { c1, c2 }
struct  tMyStruct { int i; double d; };

Ist das korrekt? Was ist mit C?


In C müssten Sie "struct tMyStruct foo" sagen. Um in der letzten Situation zu deklarieren, werden in C-Strukturdefinitionen häufig Typedefs verwendet.
David Thornley

9
Warum haben Sie Fragen in Ihrer Antwort?
ʇolɐǝz ǝɥʇ qoq
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.