974 Stimmen

Unterschied zwischen "struct" und "typedef struct" in C++?

Unter C++ gibt es einen Unterschied zwischen:

struct Foo { ... };

und:

typedef struct { ... } Foo;

0 Stimmen

2 Stimmen

1396voto

Adam Rosenfield Punkte 373807

In C++ gibt es nur einen feinen Unterschied. Es ist ein Überbleibsel aus C, in dem es einen Unterschied macht.

Der C-Sprachstandard ( C89 §3.1.2.3 , C99 §6.2.3 und C11 §6.2.3 ) schreibt separate Namensräume für verschiedene Kategorien von Bezeichnern vor, darunter Tag-Bezeichner (für struct / union / enum ) und gewöhnliche Identifikatoren (für typedef und andere Identifikatoren).

Wenn Sie gerade gesagt haben:

struct Foo { ... };
Foo x;

würden Sie einen Compilerfehler erhalten, weil Foo ist nur im Tag-Namensraum definiert.

Sie müssten es als deklarieren:

struct Foo x;

Jedes Mal, wenn Sie sich auf eine Foo müsste man es immer als struct Foo . Das wird schnell lästig, also können Sie eine typedef :

struct Foo { ... };
typedef struct Foo Foo;

Jetzt struct Foo (im Tag-Namensraum) und einfach nur Foo (im gewöhnlichen Bezeichner-Namensraum) beziehen sich beide auf dieselbe Sache, und Sie können Objekte des Typs Foo ohne die struct Stichwort.


Das Konstrukt:

typedef struct Foo { ... } Foo;

ist lediglich eine Abkürzung für die Deklaration und typedef .


Endlich,

typedef struct { ... } Foo;

deklariert eine anonyme Struktur und erstellt eine typedef für sie. Mit diesem Konstrukt hat es also keinen Namen im Tag-Namensraum, sondern nur einen Namen im Typedef-Namensraum. Das bedeutet, dass es auch nicht weiterdeklariert werden kann. Wenn Sie eine Forward-Deklaration machen wollen, müssen Sie ihr einen Namen im Tag-Namensraum geben .


In C++ werden alle struct / union / enum / class Deklarationen verhalten sich so, als wären sie implizit typedef solange der Name nicht durch eine andere Erklärung mit demselben Namen verdeckt wird. Siehe Michael Burrs Antwort für alle Einzelheiten.

33 Stimmen

Gut erkannt, es gibt einen feinen Unterschied zwischen "t

10 Stimmen

In C teilen sich die struct-Tags, union-Tags und enumeration-Tags einen Namensraum und nicht wie oben behauptet zwei (struct und union); der Namensraum, auf den für typedef-Namen verwiesen wird, ist tatsächlich getrennt. Das bedeutet, dass man nicht beides haben kann, 'union x { ... };' und 'struct x { ... };' in einem einzigen Bereich haben.

11 Stimmen

Abgesehen von der Sache mit dem nicht ganz typisierten Code besteht ein weiterer Unterschied zwischen den beiden Teilen des Codes in der Frage darin, dass Foo im ersten Beispiel einen Konstruktor definieren kann, im zweiten Beispiel jedoch nicht (da anonyme Klassen keine Konstruktoren oder Destruktoren definieren können).

256voto

Michael Burr Punkte 320591

Unter dieser DDJ-Artikel erklärt Dan Saks einen kleinen Bereich, in dem sich Bugs einschleichen können, wenn Sie Ihre Strukturen (und Klassen!) nicht typisieren:

Wenn Sie wollen, können Sie sich vorstellen, dass C++ ein Typedef für jeden Tag Namen eine Typendefinition erzeugt, wie zum Beispiel

typedef class string string;

Leider ist dies nicht ganz korrekt. Ich wünschte, es wäre so einfach, aber das ist es nicht. C++ kann keine solchen typedefs für structs, unions oder enums ohne Inkompatibilitäten mit C einzuführen.

Nehmen wir zum Beispiel an, ein C-Programm deklariert sowohl eine Funktion als auch eine Struktur namens status:

int status(); struct status;

Auch dies mag eine schlechte Praxis sein, aber es ist C. In diesem Programm bezieht sich status (durch selbst) auf die Funktion; struct status bezieht sich auf den Typ.

Wenn C++ automatisch eine typedefs für Tags generiert, dann haben Sie beim dieses Programm als C++ kompiliert, würde der Compiler generieren würde:

typedef struct status status;

Leider würde dieser Typname mit dem Funktionsnamen in Konflikt geraten, und das Programm würde nicht kompiliert werden. Das ist der Grund kann C++ daher nicht einfach einen typedef für jedes Tag erzeugen.

In C++ funktionieren Tags genauso wie typedef Namen, mit der Ausnahme, dass ein Programm ein Objekt, eine Funktion oder eine Enumerator mit demselben Namen und demselben gleichen Geltungsbereich wie ein Tag deklarieren kann. In diesem Fall wird das Objekt-, Funktions- oder Aufzählungsname den Tag-Namen überdeckt. Das Programm kann auf den Tag-Namen nur durch die Verwendung das Schlüsselwort class, struct, union oder enum (je nach Fall) vor dem Tag-Namen. Ein Typname bestehend aus einem dieser Schlüsselwörter, gefolgt von einem Tag ist ein elaborierter Typenspezifizierer. Zum Beispiel sind struct status und enum month sind elaborierte Typbezeichner.

Ein C-Programm, das beides enthält, ist also:

int status(); struct status;

verhält sich genauso, wenn es als C++ kompiliert wird. Der Name status allein bezieht sich auf den Funktion. Das Programm kann sich auf den Typ nur durch die Verwendung der elaborierten Typ-Spezifizierer struct Status.

Wie können sich also Bugs einschleichen in Programme? Betrachten Sie das Programm in Auflistung 1 . Dieses Programm definiert eine Klasse foo mit einem Standardkonstruktor, und einem Konvertierungsoperator, der ein foo-Objekt in char const * umwandelt. Der Ausdruck

p = foo();

in main sollte ein foo-Objekt konstruieren konstruieren und den Umwandlungsoperator anwenden. Die anschließende Ausgabeanweisung

cout << p << '\n';

sollte die Klasse foo anzeigen, aber sie tut es aber nicht. Es wird die Funktion foo angezeigt.

Dieses überraschende Ergebnis kommt zustande, weil das Programm den Header lib.h enthält, der in Auflistung 2 . Diese Kopfzeile definiert eine Funktion, die ebenfalls foo heißt. Die Funktionsname foo verbirgt den Klassennamen foo, so dass der Verweis auf foo in main sich auf die Funktion und nicht auf die Klasse bezieht. main kann sich auf die Klasse nur beziehen, wenn einen elaborierten-type-specifier verwenden, wie in

p = class foo();

So vermeiden Sie solche Verwechslungen im Programm zu vermeiden, ist das Hinzufügen des folgenden Typedef für den Klassennamen foo:

typedef class foo foo;

unmittelbar vor oder nach dem Unterricht Definition. Dieses Typedef verursacht einen Konflikt zwischen dem Typnamen foo und dem Funktionsnamen foo (aus der Bibliothek), der zu einem Kompilierzeit-Fehler auslöst.

Ich kenne niemanden, der tatsächlich schreibt diese Typendefinitionen als eine Selbstverständlichkeit. Das erfordert eine Menge Disziplin. Seit das Auftreten von Fehlern wie dem in Auflistung 1 ist wahrscheinlich ziemlich ist, werden Sie wahrscheinlich nie mit diesem Problem konfrontiert dieses Problem. Aber wenn ein Fehler in Ihrer Software Körperverletzungen verursachen könnte, dann sollten Sie die Typendefinitionen schreiben, egal wie unwahrscheinlich der Fehler auch sein mag.

Ich kann mir nicht vorstellen, warum jemand jemals einen Klassennamen mit einer Funktion oder einem Objektnamen im selben Bereich wie die Klasse verstecken will. Die Ausblendungsregeln in C waren ein Fehler, und sie sollten nicht auf Klassen in C++ ausgedehnt werden. C++. In der Tat kann man den Fehler korrigieren, aber das erfordert zusätzliche Programmierdisziplin und Aufwand, der nicht notwendig sein sollte.

19 Stimmen

Für den Fall, dass Sie "class foo()" ausprobieren und es fehlschlägt: In ISO C++ ist "class foo()" ein illegales Konstrukt (der Artikel wurde '97 geschrieben, anscheinend vor der Standardisierung). Sie können "typedef class foo foo;" in main einfügen, dann können Sie "foo();" sagen (denn dann ist der typedef-Name lexikalisch näher als der Name der Funktion). Syntaktisch gesehen muss T in T() ein einfacher Typenspezifizierer sein. elaborierte Typenspezifizierer sind nicht erlaubt. Trotzdem ist dies natürlich eine gute Antwort.

0 Stimmen

Listing 1 y Listing 2 Links sind defekt. Schauen Sie nach.

7 Stimmen

Wenn Sie unterschiedliche Namenskonventionen für Klassen und Funktionen verwenden, vermeiden Sie auch den Namenskonflikt, ohne dass Sie zusätzliche Typendefinitionen hinzufügen müssen.

71voto

Joe Punkte 3727

Ein weiterer wichtiger Unterschied: typedef s können nicht vorwärts deklariert werden. Also für die typedef Option müssen Sie #include die Datei, die die typedef das heißt alles, was #include s Ihr .h schließt diese Datei ebenfalls ein, unabhängig davon, ob sie direkt benötigt wird oder nicht, und so weiter. Dies kann sich bei größeren Projekten definitiv auf die Erstellungszeiten auswirken.

Ohne die typedef können Sie in einigen Fällen einfach eine Forward-Deklaration von struct Foo; an der Spitze Ihres .h Datei, und nur #include die Strukturdefinition in Ihrem .cpp Datei.

0 Stimmen

Warum wirkt sich die Offenlegung der Definition von struct auf die Erstellungszeit aus? Führt der Compiler eine zusätzliche Prüfung durch, auch wenn er das nicht tun muss (angesichts der typedef-Option, so dass der Compiler die Definition kennt), wenn er etwas sieht wie Foo* nextFoo; ?

4 Stimmen

Es handelt sich nicht wirklich um eine zusätzliche Prüfung, sondern nur um mehr Code, mit dem der Compiler umgehen muss. Für jede cpp-Datei, die irgendwo in ihrer Include-Kette auf dieses Typedef stößt, kompiliert er das Typedef. In größeren Projekten kann die .h-Datei, die den Typedef enthält, leicht hunderte Male kompiliert werden, obwohl vorkompilierte Header eine große Hilfe sind. Wenn man mit einer Vorwärtsdeklaration auskommt, ist es einfacher, die Einbindung der .h-Datei, die die vollständige struct-Spezifikation enthält, auf den Code zu beschränken, der wirklich wichtig ist, und daher wird die entsprechende Include-Datei weniger oft kompiliert.

0 Stimmen

Bitte @ den vorherigen Kommentator (anders als der Eigentümer des Beitrags). Ich habe Ihre Antwort fast übersehen. Aber danke für die Info.

35voto

dirkgently Punkte 104289

Dort es ein Unterschied, aber subtil. Sehen Sie es mal so: struct Foo führt einen neuen Typ ein. Die zweite erzeugt einen Alias namens Foo (und nicht einen neuen Typ) für einen unbenannten struct Typ.

7.1.3 Der typedef-Spezifizierer

1 [...]

Ein mit dem Spezifizierer typedef deklarierter Name wird zu einem typedef-name. Innerhalb des Geltungsbereichs seiner Deklaration ist ein typedef-name syntaktisch äquivalent zu einem Schlüsselwort und benennt den mit dem Bezeichner assoziierten Typ in der in Klausel 8 beschriebenen Weise. Ein typedef-name ist also ein Synonym für einen anderen Typ. Ein typedef-name führt keinen neuen Typ ein so wie eine Klassendeklaration (9.1) oder eine Enum-Deklaration.

8 Wenn die typedef-Deklaration eine unbenannte Klasse (oder enum) definiert, wird der erste typedef-Name, der durch die Deklaration deklariert wurde, wird der Klassentyp (oder der Enum-Typ) nur für Verknüpfungszwecke verwendet nur für Verknüpfungszwecke verwendet (3.5). [Beispiel:

typedef struct { } *ps, S; // S is the class name for linkage purposes

Also, ein Typedef siempre wird als Platzhalter/Synonym für einen anderen Typ verwendet.

10voto

Yochai Timmer Punkte 46099

Sie können keine Vorwärtsdeklaration mit dem typedef struct verwenden.

Die Struktur selbst ist ein anonymer Typ, d.h. Sie haben keinen tatsächlichen Namen, den Sie weitergeben können.

typedef struct{
    int one;
    int two;
}myStruct;

Eine solche Vorwärtserklärung wird nicht funktionieren:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types

0 Stimmen

Der zweite Fehler für den Funktionsprototyp tritt nicht auf. Warum heißt es "redefinition; different basic types"? der Compiler muss doch nicht wissen, wie die Definition von myStruct aussieht, oder? Egal aus welchem Codestück (dem typedef oder der forward declaration), myStruct bezeichnet einen struct-Typ, richtig?

0 Stimmen

@Rich Es beschwert sich, dass es einen Konflikt zwischen den Namen gibt. Es gibt eine Vorwärtsdeklaration, die besagt "suche eine Struktur namens myStruct" und dann gibt es das Typedef, das eine namenlose Struktur in "myStruct" umbenennt.

1 Stimmen

Sie meinen, sowohl typedef als auch forward-Deklaration in dieselbe Datei zu schreiben? Das habe ich getan, und gcc hat es gut kompiliert. myStruct wird korrekt als die namenlose Struktur interpretiert. Tag myStruct lebt im Tag-Namensraum und typedef_ed myStruct liegt im normalen Namensraum, in dem auch andere Bezeichner wie Funktionsnamen und lokale Variablennamen liegen. Es sollte also keinen Konflikt geben Ich kann Ihnen meinen Code zeigen, wenn Sie daran zweifeln, dass er einen Fehler enthält.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X