Gibt es in C ++ einen Unterschied zwischen:
struct Foo { ... };
und:
typedef struct { ... } Foo;
Gibt es in C ++ einen Unterschied zwischen:
struct Foo { ... };
und:
typedef struct { ... } Foo;
Antworten:
In C ++ gibt es nur einen subtilen Unterschied. Es ist ein Überbleibsel von C, bei dem es einen Unterschied macht.
Der C-Sprachstandard ( C89 §3.1.2.3 , C99 §6.2.3 und C11 §6.2.3 ) schreibt separate Namespaces für verschiedene Kategorien von Bezeichnern vor, einschließlich Tag-Bezeichnern (für struct
/ union
/ enum
) und gewöhnlichen Bezeichnern (für typedef
und andere Bezeichner). .
Wenn Sie gerade gesagt haben:
struct Foo { ... };
Foo x;
Sie würden einen Compilerfehler erhalten, da dieser Foo
nur im Tag-Namespace definiert ist.
Sie müssten es deklarieren als:
struct Foo x;
Jedes Mal, wenn Sie sich auf a beziehen möchten Foo
, müssen Sie es immer a nennen struct Foo
. Dies wird schnell ärgerlich, so dass Sie Folgendes hinzufügen können typedef
:
struct Foo { ... };
typedef struct Foo Foo;
Jetzt struct Foo
(im Tag-Namespace) und einfach Foo
(im normalen Bezeichner-Namespace) beziehen sich beide auf dasselbe, und Sie können Objekte vom Typ Foo
ohne das struct
Schlüsselwort frei deklarieren .
Das Konstrukt:
typedef struct Foo { ... } Foo;
ist nur eine Abkürzung für die Deklaration und typedef
.
Schließlich,
typedef struct { ... } Foo;
deklariert eine anonyme Struktur und erstellt eine typedef
dafür. Daher hat dieses Konstrukt keinen Namen im Tag-Namespace, sondern nur einen Namen im typedef-Namespace. Dies bedeutet, dass es auch nicht vorwärts deklariert werden kann. Wenn Sie eine Vorwärtsdeklaration abgeben möchten, müssen Sie ihr im Tag-Namespace einen Namen geben .
In C ++, alle struct
/ union
/ enum
/ class
Erklärungen handeln , wie sie implizit sind typedef
‚ed, solange der Name nicht durch eine andere Erklärung mit dem gleichen Namen verbirgt. Siehe die Antwort von Michael Burr für die vollständigen Details.
In diesem DDJ-Artikel erklärt Dan Saks einen kleinen Bereich, in dem sich Fehler einschleichen können, wenn Sie Ihre Strukturen (und Klassen!) Nicht eingeben:
Wenn Sie möchten, können Sie sich vorstellen, dass C ++ für jeden Tag-Namen ein typedef generiert, z
typedef class string string;
Leider ist dies nicht ganz richtig. Ich wünschte, es wäre so einfach, aber es ist nicht so. C ++ kann solche Typedefs für Strukturen, Gewerkschaften oder Aufzählungen nicht generieren, ohne Inkompatibilitäten mit C einzuführen.
Angenommen, ein C-Programm deklariert sowohl eine Funktion als auch eine Struktur mit dem Namen status:
int status(); struct status;
Auch dies mag eine schlechte Praxis sein, aber es ist C. In diesem Programm bezieht sich der Status (für sich) auf die Funktion; Der Strukturstatus bezieht sich auf den Typ.
Wenn C ++ automatisch Typedefs für Tags generieren würde, würde der Compiler beim Kompilieren dieses Programms als C ++ Folgendes generieren:
typedef struct status status;
Leider würde dieser Typname mit dem Funktionsnamen in Konflikt stehen und das Programm würde nicht kompiliert. Aus diesem Grund kann C ++ nicht einfach für jedes Tag ein typedef generieren.
In C ++ verhalten sich Tags wie typedef-Namen, außer dass ein Programm ein Objekt, eine Funktion oder einen Enumerator mit demselben Namen und demselben Bereich wie ein Tag deklarieren kann. In diesem Fall verbirgt der Objekt-, Funktions- oder Enumeratorname den Tag-Namen. Das Programm kann nur unter Verwendung der Schlüsselwortklasse, Struktur, Vereinigung oder Aufzählung (je nach Bedarf) vor dem Tag-Namen auf den Tag-Namen verweisen. Ein Typname, der aus einem dieser Schlüsselwörter gefolgt von einem Tag besteht, ist ein ausgearbeiteter Typspezifizierer. Zum Beispiel sind Strukturstatus und Aufzählungsmonat ausgearbeitete Typspezifizierer.
Ein C-Programm, das beides enthält:
int status(); struct status;
verhält sich beim Kompilieren als C ++ gleich. Der Namensstatus allein bezieht sich auf die Funktion. Das Programm kann nur unter Verwendung des Strukturstatus des ausgearbeiteten Typspezifizierers auf den Typ verweisen.
Wie können sich dadurch Fehler in Programme einschleichen? Betrachten Sie das Programm in Listing 1 . Dieses Programm definiert eine Klasse foo mit einem Standardkonstruktor und einen Konvertierungsoperator, der ein foo-Objekt in char const * konvertiert. Der Ausdruck
p = foo();
In der Hauptsache sollte ein foo-Objekt erstellt und der Konvertierungsoperator angewendet werden. Die nachfolgende Ausgabeanweisung
cout << p << '\n';
sollte die Klasse foo anzeigen, tut es aber nicht. Es zeigt die Funktion foo an.
Dieses überraschende Ergebnis tritt auf, weil das Programm den in Listing 2 gezeigten Header lib.h enthält . Dieser Header definiert eine Funktion mit dem Namen foo. Der Funktionsname foo verbirgt den Klassennamen foo, sodass sich der Verweis auf foo im Wesentlichen auf die Funktion bezieht, nicht auf die Klasse. main kann nur mit einem ausgearbeiteten Typspezifizierer auf die Klasse verweisen, wie in
p = class foo();
Um solche Verwirrung im gesamten Programm zu vermeiden, fügen Sie den folgenden Typedef für den Klassennamen foo hinzu:
typedef class foo foo;
unmittelbar vor oder nach der Klassendefinition. Dieses typedef verursacht einen Konflikt zwischen dem Typnamen foo und dem Funktionsnamen foo (aus der Bibliothek), der einen Fehler bei der Kompilierung auslöst.
Ich kenne niemanden, der diese Typedefs selbstverständlich schreibt. Es erfordert viel Disziplin. Da die Häufigkeit von Fehlern wie in Listing 1 wahrscheinlich recht gering ist, haben Sie viele Probleme mit diesem Problem. Wenn jedoch ein Fehler in Ihrer Software zu Körperverletzungen führen kann, sollten Sie die typedefs schreiben, egal wie unwahrscheinlich der Fehler ist.
Ich kann mir nicht vorstellen, warum irgendjemand jemals einen Klassennamen mit einem Funktions- oder Objektnamen im selben Bereich wie die Klasse verstecken möchte. Die Ausblendregeln in C waren ein Fehler und sollten nicht auf Klassen in C ++ erweitert werden. Sie können den Fehler zwar korrigieren, dies erfordert jedoch zusätzliche Programmierdisziplin und zusätzlichen Aufwand, der nicht erforderlich sein sollte.
Listing 1
und Listing 2
Links sind kaputt. Guck mal.
Ein weiterer wichtiger Unterschied: typedef
s können nicht vorwärts deklariert werden. Für die typedef
Option müssen Sie also #include
die Datei enthalten, die das enthält typedef
, dh alles, was #include
Ihnen .h
gehört, enthält auch diese Datei, unabhängig davon, ob sie direkt benötigt wird oder nicht, und so weiter. Dies kann sich definitiv auf Ihre Erstellungszeiten bei größeren Projekten auswirken.
Ohne das typedef
können Sie in einigen Fällen einfach eine Vorwärtsdeklaration von struct Foo;
oben in Ihrer .h
Datei und nur #include
die Strukturdefinition in Ihrer .cpp
Datei hinzufügen .
Es gibt einen Unterschied, aber subtil. Betrachten Sie es so: struct Foo
führt einen neuen Typ ein. Der zweite erstellt einen Alias namens Foo (und keinen neuen Typ) für einen unbenannten struct
Typ.
7.1.3 Der typedef-Bezeichner
1 [...]
Ein mit dem Typedef-Bezeichner deklarierter Name wird zu einem Typedef-Namen. Ein typedef-Name entspricht im Rahmen seiner Deklaration syntaktisch einem Schlüsselwort und benennt den mit dem Bezeichner verknüpften Typ wie in Abschnitt 8 beschrieben. Ein typedef-Name ist somit ein Synonym für einen anderen Typ. Ein typedef-Name führt keinen neuen Typ ein, wie es eine Klassendeklaration (9.1) oder eine Enum-Deklaration tut.
8 Wenn die typedef-Deklaration eine unbenannte Klasse (oder Aufzählung) definiert, wird der erste typedef-Name, der von der Deklaration als dieser Klassentyp (oder Aufzählungstyp) deklariert wird, verwendet, um den Klassentyp (oder Aufzählungstyp) nur für Verknüpfungszwecke zu bezeichnen ( 3.5). [Beispiel:
typedef struct { } *ps, S; // S is the class name for linkage purposes
Ein typedef wird also immer als Platzhalter / Synonym für einen anderen Typ verwendet.
Sie können die Forward-Deklaration nicht mit der typedef-Struktur verwenden.
Die Struktur selbst ist ein anonymer Typ, sodass Sie keinen tatsächlichen Namen zum Weiterleiten deklarieren müssen.
typedef struct{
int one;
int two;
}myStruct;
Eine solche Vorwärtserklärung funktioniert nicht:
struct myStruct; //forward declaration fails
void blah(myStruct* pStruct);
//error C2371: 'myStruct' : redefinition; different basic types
myStruct
lebt im Tag-Namespace und typedef_ed myStruct
lebt im normalen Namespace, in dem andere Bezeichner wie Funktionsname, lokale Variablennamen leben. Es sollte also keinen Konflikt geben. Ich kann Ihnen meinen Code zeigen, wenn Sie Zweifel daran haben, dass ein Fehler darin vorliegt.
typedef
Forward-Deklaration mit dem typedef
ed-Namen haben, diese nicht auf die unbenannte Struktur verweist. Stattdessen deklariert die Forward-Deklaration eine unvollständige Struktur mit Tag myStruct
. Ohne die Definition von zu sehen typedef
, ist der Funktionsprototyp, der den typedef
ed-Namen verwendet, nicht legal. Daher müssen wir das gesamte typedef einschließen, wann immer wir myStruct
einen Typ bezeichnen müssen. Korrigiere mich, wenn ich dich missverstanden habe. Vielen Dank.
Ein wichtiger Unterschied zwischen einer 'typedef struct' und einer 'struct' in C ++ besteht darin, dass die Inline-Elementinitialisierung in 'typedef structs' nicht funktioniert.
// the 'x' in this struct will NOT be initialised to zero
typedef struct { int x = 0; } Foo;
// the 'x' in this struct WILL be initialised to zero
struct Foo { int x = 0; };
x
wird initialisiert. Siehe Test in der Coliru-Online-IDE (ich habe ihn auf 42 initialisiert, sodass es offensichtlicher als mit Null ist, dass die Zuweisung tatsächlich stattgefunden hat).
Es gibt keinen Unterschied in C ++, aber ich glaube, in C können Sie Instanzen der Struktur Foo deklarieren, ohne explizit Folgendes zu tun:
struct Foo bar;