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

Antworten:

1202

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 typedefund andere Bezeichner). .

Wenn Sie gerade gesagt haben:

struct Foo { ... };
Foo x;

Sie würden einen Compilerfehler erhalten, da dieser Foonur 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 Fooohne das structSchlü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 typedefdafü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/ classErklä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.

Adam Rosenfield
quelle
53
Während das, was Sie sagen, wahr ist, AFAIK, die Aussage 'typedef struct {...} Foo;' Erstellt einen Alias ​​für eine unbenannte Struktur.
Dirkgently
25
Guter Fang, es gibt einen subtilen Unterschied zwischen "typedef struct Foo {...} Foo;" und "typedef struct {...} Foo;".
Adam Rosenfield
8
In C teilen sich die struct-Tags, union-Tags und enumeration-Tags einen Namespace, anstatt (struct und union) zwei wie oben angegeben zu verwenden. Der Namespace, auf den für typedef-Namen verwiesen wird, ist in der Tat separat. Das heißt, Sie können nicht beide 'union x {...};' und 'struct x {...};' in einem einzigen Bereich.
Jonathan Leffler
10
Abgesehen von der nicht ganz typedef Sache besteht ein weiterer Unterschied zwischen den beiden Codeteilen in der Frage darin, dass Foo im ersten Beispiel einen Konstruktor definieren kann, im zweiten jedoch nicht (da anonyme Klassen keine Konstruktoren oder Destruktoren definieren können). .
Steve Jessop
1
@Lazer: Es gibt subtile Unterschiede, aber Adam bedeutet (wie er weiter sagt), dass Sie 'Type var' verwenden können, um Variablen ohne typedef zu deklarieren.
Fred Nurk
226

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.

Michael Burr
quelle
11
Wenn Sie "class foo ()" versuchen und es fehlschlägt: In ISO C ++ ist "class foo ()" ein illegales Konstrukt (der Artikel wurde anscheinend '97 geschrieben, vor der Standardisierung). Sie können "typedef class foo foo" setzen. in main, dann können Sie "foo ();" (weil dann der typedef-name lexikalisch näher ist als der Name der Funktion). Syntaktisch muss T in T () ein einfacher Typspezifizierer sein. Ausgearbeitete Typspezifizierer sind nicht zulässig. Trotzdem ist dies natürlich eine gute Antwort.
Johannes Schaub - litb
Listing 1und Listing 2Links sind kaputt. Guck mal.
Prasoon Saurav
3
Wenn Sie unterschiedliche Namenskonventionen für Klassen und Funktionen verwenden, vermeiden Sie auch den Namenskonflikt, ohne zusätzliche Typedefs hinzufügen zu müssen.
Zstewart
64

Ein weiterer wichtiger Unterschied: typedefs können nicht vorwärts deklariert werden. Für die typedefOption müssen Sie also #includedie Datei enthalten, die das enthält typedef, dh alles, was #includeIhnen .hgehö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 typedefkönnen Sie in einigen Fällen einfach eine Vorwärtsdeklaration von struct Foo;oben in Ihrer .hDatei und nur #includedie Strukturdefinition in Ihrer .cppDatei hinzufügen .

Joe
quelle
Warum wirkt sich das Offenlegen der Definition von struct auf die Erstellungszeit aus? Führt der Compiler zusätzliche Überprüfungen durch, auch wenn dies nicht erforderlich ist (mit der Option typedef, damit der Compiler die Definition kennt), wenn er etwas wie Foo * nextFoo sieht ; ?
Rich
3
Es ist keine zusätzliche Überprüfung, sondern nur mehr Code, mit dem sich der Compiler befassen muss. Für jede CPP-Datei, die irgendwo in ihrer Include-Kette auf dieses Typedef stößt, wird das Typedef kompiliert. In größeren Projekten kann die .h-Datei, die das typedef enthält, leicht hunderte Male kompiliert werden, obwohl vorkompilierte Header sehr hilfreich sind. Wenn Sie mit der Verwendung einer Forward-Deklaration davonkommen können, ist es einfacher, die Aufnahme der .h-Datei mit der vollständigen Strukturspezifikation auf den Code zu beschränken, der wirklich wichtig ist, und daher wird die entsprechende Include-Datei seltener kompiliert.
Joe
Bitte @ den vorherigen Kommentator (außer dem Besitzer des Beitrags). Ich habe deine Antwort fast verpasst. Aber danke für die Info.
Rich
Dies gilt nicht mehr für C11, wo Sie dieselbe Struktur mit demselben Namen mehrmals eingeben können.
Michaelmeyer
32

Es gibt einen Unterschied, aber subtil. Betrachten Sie es so: struct Fooführt einen neuen Typ ein. Der zweite erstellt einen Alias ​​namens Foo (und keinen neuen Typ) für einen unbenannten structTyp.

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.

dirkgently
quelle
10

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
Yochai Timmer
quelle
Ich erhalte nicht den zweiten Fehler für den Funktionsprototyp. Warum heißt es "Neudefinition; verschiedene Grundtypen"? Der Compiler muss nicht wissen, wie die Definition von myStruct aussieht, oder? Unabhängig davon, ob es sich um einen Code handelt (den typedef-Code oder den Forward-Deklarationscode), bezeichnet myStruct einen Strukturtyp, oder?
Rich
@Rich Es beschwert sich, dass es einen Namenskonflikt gibt. Es gibt eine Vorwärtsdeklaration mit der Aufschrift "Suche nach einer Struktur namens myStruct" und dann das typedef, das eine namenlose Struktur in "myStruct" umbenennt.
Yochai Timmer
Sie meinen, sowohl typedef als auch forward-Deklaration in dieselbe Datei zu setzen? Ich habe es getan und gcc hat es gut zusammengestellt. myStruct wird korrekt als namenlose Struktur interpretiert. Tag myStructlebt im Tag-Namespace und typedef_ed myStructlebt 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.
Rich
@ Rich GCC gibt den gleichen Fehler, Text variiert ein wenig: gcc.godbolt.org/…
Yochai Timmer
Ich glaube, ich verstehe, dass, wenn Sie nur eine typedefForward-Deklaration mit dem typedefed-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 typedefed-Namen verwendet, nicht legal. Daher müssen wir das gesamte typedef einschließen, wann immer wir myStructeinen Typ bezeichnen müssen. Korrigiere mich, wenn ich dich missverstanden habe. Vielen Dank.
Rich
0

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; };
user2796283
quelle
3
Nicht wahr. In beiden Fällen xwird 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).
Colin D Bennett
Eigentlich habe ich es in Visual Studio 2013 getestet und es wurde nicht initialisiert. Dies war ein Problem, auf das wir im Produktionscode gestoßen sind. Alle Compiler sind unterschiedlich und müssen nur bestimmte Kriterien erfüllen.
user2796283
-3

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;
xian
quelle
3
Schauen Sie sich die Antwort von @ dirkgently an - es gibt einen Unterschied, aber er ist subtil.
Keith Pinson
-3

Struktur ist, einen Datentyp zu erstellen. Mit typedef wird ein Kurzname für einen Datentyp festgelegt.

David Tu
quelle
12
Sie haben die Frage nicht verstanden.
Winter