Différence entre «struct» et «typedef struct» en C ++?

Réponses:

1202

En C ++, il n'y a qu'une différence subtile. C'est un reliquat de C, dans lequel cela fait une différence.

La norme du langage C ( C89 §3.1.2.3 , C99 §6.2.3 et C11 §6.2.3 ) impose des espaces de noms séparés pour différentes catégories d'identifiants, y compris les identifiants de balise (pour struct/ union/ enum) et les identifiants ordinaires (pour typedefet d'autres identifiants) .

Si vous venez de dire:

struct Foo { ... };
Foo x;

vous obtiendrez une erreur de compilation, car elle Foon'est définie que dans l'espace de noms des balises.

Vous devez le déclarer comme:

struct Foo x;

Chaque fois que vous voulez vous référer à un Foo, vous devez toujours l'appeler a struct Foo. Cela devient rapidement ennuyeux, vous pouvez donc ajouter un typedef:

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

Maintenant struct Foo(dans l'espace de noms des balises) et tout simplement Foo(dans l'espace de noms des identifiants ordinaires) font tous deux référence à la même chose, et vous pouvez déclarer librement des objets de type Foosans le structmot - clé.


La construction:

typedef struct Foo { ... } Foo;

est juste une abréviation pour la déclaration et typedef.


Finalement,

typedef struct { ... } Foo;

déclare une structure anonyme et crée un typedefpour elle. Ainsi, avec cette construction, il n'a pas de nom dans l'espace de noms des balises, seulement un nom dans l'espace de noms typedef. Cela signifie qu'il ne peut pas non plus être déclaré à l'avance. Si vous voulez faire une déclaration directe, vous devez lui donner un nom dans l'espace de noms des balises .


En C ++, toutes les déclarations struct/ union/ enum/ classagissent comme si elles étaient implicitement typedeféditées, tant que le nom n'est pas masqué par une autre déclaration du même nom. Voir la réponse de Michael Burr pour tous les détails.

Adam Rosenfield
la source
53
Alors que ce que vous dites est vrai, AFAIK, la déclaration, 'typedef struct {...} Foo;' crée un alias pour une structure sans nom.
dirkgently
25
Bonne capture, il y a une différence subtile entre "typedef struct Foo {...} Foo;" et "typedef struct {...} Foo;".
Adam Rosenfield
8
En C, les balises struct, les balises union et les balises d'énumération partagent un espace de nom, plutôt que (struct et union) en utilisant deux comme revendiqué ci-dessus; l'espace de noms référencé pour les noms typedef est en effet distinct. Cela signifie que vous ne pouvez pas avoir à la fois 'union x {...};' et 'struct x {...};' dans une seule portée.
Jonathan Leffler
10
Mis à part la chose pas tout à fait typée, une autre différence entre les deux morceaux de code dans la question est que Foo peut définir un constructeur dans le premier exemple, mais pas dans le second (car les classes anonymes ne peuvent pas définir de constructeurs ou de destructeurs) .
Steve Jessop
1
@Lazer: Il y a des différences subtiles, mais Adam veut dire (comme il continue) que vous pouvez utiliser 'Type var' pour déclarer des variables sans typedef.
Fred Nurk
226

Dans cet article DDJ , Dan Saks explique une petite zone dans laquelle les bogues peuvent se glisser si vous ne saisissez pas vos structures (et classes!):

Si vous le souhaitez, vous pouvez imaginer que C ++ génère un typedef pour chaque nom de balise, tel que

typedef class string string;

Malheureusement, ce n'est pas tout à fait exact. J'aimerais que ce soit aussi simple que ça, mais ce n'est pas le cas. C ++ ne peut pas générer de tels typedefs pour les structures, les unions ou les énumérations sans introduire d'incompatibilités avec C.

Par exemple, supposons qu'un programme C déclare à la fois une fonction et une structure nommée status:

int status(); struct status;

Encore une fois, cela peut être une mauvaise pratique, mais c'est C. Dans ce programme, l'état (en soi) se réfère à la fonction; statut de structure fait référence au type.

Si C ++ générait automatiquement des typedefs pour les balises, alors lorsque vous compileriez ce programme en C ++, le compilateur générerait:

typedef struct status status;

Malheureusement, ce nom de type entrerait en conflit avec le nom de la fonction et le programme ne compilerait pas. C'est pourquoi C ++ ne peut pas simplement générer un typedef pour chaque balise.

En C ++, les balises agissent exactement comme les noms de typedef, sauf qu'un programme peut déclarer un objet, une fonction ou un énumérateur avec le même nom et la même portée qu'une balise. Dans ce cas, le nom de l'objet, de la fonction ou de l'énumérateur masque le nom de la balise. Le programme peut faire référence au nom de la balise uniquement en utilisant le mot-clé class, struct, union ou enum (selon le cas) devant le nom de la balise. Un nom de type composé d'un de ces mots clés suivi d'une balise est un spécificateur de type élaboré. Par exemple, struct status et enum month sont des spécificateurs de type élaborés.

Ainsi, un programme C qui contient à la fois:

int status(); struct status;

se comporte de la même manière lors de la compilation en C ++. Le statut de nom seul fait référence à la fonction. Le programme ne peut faire référence au type qu'en utilisant le statut struct structuré-spécificateur de type.

Alors, comment cela permet-il aux bogues de se glisser dans les programmes? Considérez le programme du Listing 1 . Ce programme définit une classe foo avec un constructeur par défaut et un opérateur de conversion qui convertit un objet foo en char const *. L'expression

p = foo();

in main doit construire un objet foo et appliquer l'opérateur de conversion. L'instruction de sortie suivante

cout << p << '\n';

devrait afficher la classe foo, mais ce n'est pas le cas. Il affiche la fonction foo.

Ce résultat surprenant se produit car le programme inclut l'en-tête lib.h indiqué dans le listing 2 . Cet en-tête définit une fonction également appelée foo. Le nom de la fonction foo masque le nom de la classe foo, donc la référence à foo dans la référence principale fait référence à la fonction, pas à la classe. main ne peut faire référence à la classe qu'en utilisant un spécificateur de type élaboré, comme dans

p = class foo();

Le moyen d'éviter une telle confusion tout au long du programme consiste à ajouter le typedef suivant pour le nom de classe foo:

typedef class foo foo;

immédiatement avant ou après la définition de classe. Ce typedef provoque un conflit entre le nom de type foo et le nom de fonction foo (de la bibliothèque) qui déclenchera une erreur de compilation.

Je ne connais personne qui écrit ces caractères typographiques comme une évidence. Cela demande beaucoup de discipline. Étant donné que l'incidence d'erreurs telles que celle du Listing 1 est probablement assez faible, vous ne rencontrez jamais ce problème. Mais si une erreur dans votre logiciel peut provoquer des blessures corporelles, vous devez écrire les typedefs, peu importe la probabilité de l'erreur.

Je ne peux pas imaginer pourquoi quelqu'un voudrait jamais cacher un nom de classe avec un nom de fonction ou d'objet dans la même portée que la classe. Les règles de masquage en C étaient une erreur, et elles n'auraient pas dû être étendues aux classes en C ++. En effet, vous pouvez corriger l'erreur, mais cela nécessite une discipline de programmation et des efforts supplémentaires qui ne devraient pas être nécessaires.

Michael Burr
la source
11
Dans le cas où vous essayez "class foo ()" et qu'il échoue: En ISO C ++, "class foo ()" est une construction illégale (l'article a été écrit '97, avant la normalisation, semble-t-il). Vous pouvez mettre "typedef class foo foo;" en main, alors vous pouvez dire "foo ();" (car alors, le nom-typedef est lexicalement plus proche que le nom de la fonction). Syntaxiquement, dans T (), T doit être un spécificateur de type simple. les spécificateurs de type élaborés ne sont pas autorisés. C'est toujours une bonne réponse, bien sûr.
Johannes Schaub - litb
Listing 1et les Listing 2liens sont rompus. Regarde.
Prasoon Saurav
3
Si vous utilisez des conventions de dénomination différentes pour les classes et les fonctions, vous évitez également le conflit de dénomination sans avoir besoin d'ajouter des typedefs supplémentaires.
zstewart
64

Une autre différence importante: les typedefs ne peuvent pas être déclarés en avant. Donc, pour l' typedefoption, vous devez #includele fichier contenant le typedef, ce qui signifie que tout ce qui #includevous .happartient inclut également ce fichier, qu'il en ait directement besoin ou non, etc. Cela peut certainement avoir un impact sur vos temps de construction sur des projets plus importants.

Sans le typedef, dans certains cas, vous pouvez simplement ajouter une déclaration directe de struct Foo;en haut de votre .hfichier, et uniquement #includela définition de la structure dans votre .cppfichier.

Joe
la source
Pourquoi l'exposition de la définition de struct a-t-elle un impact sur le temps de construction? Le compilateur effectue-t-il des vérifications supplémentaires même s'il n'en a pas besoin (étant donné l'option typedef, afin que le compilateur connaisse la définition) lorsqu'il voit quelque chose comme Foo * nextFoo; ?
Rich
3
Ce n'est pas vraiment une vérification supplémentaire, c'est juste plus de code que le compilateur doit gérer. Pour chaque fichier cpp qui rencontre ce typedef quelque part dans sa chaîne d'inclusion, il compile le typedef. Dans les projets plus importants, le fichier .h contenant le typedef pourrait facilement être compilé des centaines de fois, bien que les en-têtes précompilés soient très utiles. Si vous parvenez à utiliser une déclaration directe, il est plus facile de limiter l'inclusion du .h contenant la spécification de structure complète au code qui se soucie vraiment, et donc le fichier include correspondant est compilé moins souvent.
Joe
s'il vous plaît @ le commentateur précédent (autre que le propriétaire du message). J'ai failli rater ta réponse. Mais merci pour l'info.
Rich
Ce n'est plus le cas en C11, où vous pouvez taper plusieurs fois la même structure avec le même nom.
michaelmeyer
32

Il y a une différence, mais subtile. Regardez-le de cette façon: struct Foointroduit un nouveau type. Le second crée un alias appelé Foo (et non un nouveau type) pour un structtype sans nom .

7.1.3 Le spécificateur typedef

1 [...]

Un nom déclaré avec le spécificateur typedef devient un nom typedef. Dans le cadre de sa déclaration, un nom de typedef est syntaxiquement équivalent à un mot-clé et nomme le type associé à l'identifiant de la manière décrite à l'article 8. Un nom de typedef est donc synonyme d'un autre type. Un nom typedef n'introduit pas un nouveau type comme le fait une déclaration de classe (9.1) ou une déclaration enum.

8 Si la déclaration typedef définit une classe sans nom (ou enum), le premier nom typedef déclaré par la déclaration comme étant ce type de classe (ou type enum) est utilisé pour désigner le type de classe (ou le type enum) à des fins de liaison uniquement ( 3.5). [ Exemple:

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

Ainsi, un typedef est toujours utilisé comme espace réservé / synonyme pour un autre type.

dirkgently
la source
10

Vous ne pouvez pas utiliser la déclaration directe avec la structure typedef.

La structure elle-même est un type anonyme, vous n'avez donc pas de nom réel à transmettre.

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

Une déclaration avancée comme celle-ci ne fonctionnera pas:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types
Yochai Timmer
la source
Je n'obtiens pas la deuxième erreur pour le prototype de fonction. Pourquoi dit-on "redéfinition; différents types de base"? le compilateur n'a pas besoin de savoir à quoi ressemble la définition de myStruct, non? Peu importe avec un morceau de code (celui de typedef ou celui de déclaration directe), myStruct désigne un type struct, non?
Rich
@Rich Il se plaint qu'il y a un conflit de noms. Il y a une déclaration avant disant "recherchez une structure appelée myStruct" et puis il y a le typedef qui renomme une structure sans nom en "myStruct".
Yochai Timmer
Vous voulez dire mettre à la fois typedef et forward declaration dans le même fichier? Je l'ai fait, et gcc l'a bien compilé. myStruct est correctement interprété comme la structure sans nom. Tag myStructvit dans l'espace de noms des balises et typedef_ed myStructvit dans l'espace de noms normal où d'autres identifiants comme le nom de la fonction, les noms de variables locales vivent. Il ne devrait donc pas y avoir de conflit ... Je peux vous montrer mon code si vous doutez qu'il y ait une erreur.
Rich
@Rich GCC donne la même erreur, le texte varie un peu: gcc.godbolt.org/…
Yochai Timmer
Je pense que je comprends que lorsque vous n'avez qu'une typedefdéclaration directe avec le typedefnom ed, ne fait pas référence à la structure sans nom. Au lieu de cela, la déclaration directe déclare une structure incomplète avec une balise myStruct. De plus, sans voir la définition de typedef, le prototype de fonction utilisant le typedefnom ed n'est pas légal. Nous devons donc inclure le typedef entier chaque fois que nous devons utiliser myStructpour désigner un type. Corrigez-moi si je vous ai mal compris. Merci.
Rich
0

Une différence importante entre une «structure typedef» et une «struct» en C ++ est que l'initialisation des membres en ligne dans les «structures typedef» ne fonctionnera pas.

// 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
la source
3
Pas vrai. Dans les deux cas xest initialisé. Voir test dans l'IDE en ligne de Coliru (je l'ai initialisé à 42 donc il est plus évident qu'avec zéro que la mission a vraiment eu lieu).
Colin D Bennett
En fait, je l'ai testé dans Visual Studio 2013 et il n'a pas été initialisé. C'est un problème que nous avons rencontré dans le code de production. Tous les compilateurs sont différents et ne doivent répondre qu'à certains critères.
user2796283
-3

Il n'y a pas de différence en C ++, mais je crois qu'en C cela vous permettrait de déclarer des instances de la structure Foo sans faire explicitement:

struct Foo bar;
xian
la source
3
Regardez la réponse de @ dirkgently --- il y a une différence, mais c'est subtil.
Keith Pinson
-3

La structure consiste à créer un type de données. Le typedef consiste à définir un surnom pour un type de données.

David Tu
la source
12
Vous n'avez pas compris la question.
Hiver