Différence entre ‘struct’ et ‘typedef struct’ en C ++?

En C ++, existe-t-il une différence entre:

struct Foo { ... }; 

et

 typedef struct { ... } Foo; 

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

La norme de 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’identificateurs, y compris les identificateurs de balises (pour struct / union / enum ) et les identificateurs ordinaires (pour typedef et autres identificateurs).

Si tu viens de dire:

 struct Foo { ... }; Foo x; 

vous obtiendrez une erreur de compilation, car Foo est uniquement défini dans l’espace de noms des balises.

Vous devriez le déclarer comme:

 struct Foo x; 

Chaque fois que vous voulez vous référer à un Foo , vous devrez toujours l’appeler une struct Foo . Cela devient vite ennuyeux, vous pouvez donc append un typedef :

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

Maintenant struct Foo (dans l’espace de noms des balises) et simplement Foo (dans l’espace de noms des identificateurs ordinaires) se réfèrent tous deux à la même chose et vous pouvez librement déclarer des objects de type Foo sans le mot struct clé struct .


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 typedef pour elle. Ainsi, avec cette construction, elle n’a pas de nom dans l’espace de noms de la balise, seulement un nom dans l’espace de noms typedef. Cela signifie qu’il ne peut pas non plus être déclaré en avant. Si vous voulez faire une déclaration, vous devez lui donner un nom dans l’espace de noms .


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

Dans cet article de DDJ , Dan Saks explique un petit domaine où les bogues peuvent se glisser si vous ne tapez pas vos structures (et classes!):

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

 typedef class ssortingng ssortingng; 

Malheureusement, ce n’est pas tout à fait exact. Je souhaite que ce soit aussi simple, mais ce n’est pas le cas. C ++ ne peut pas générer de tels typedefs pour structs, unions ou enums 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, le statut (par lui-même) fait référence à 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 comstackriez 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 comstackrait pas. C’est pourquoi C ++ ne peut pas simplement générer un typedef pour chaque tag.

En C ++, les balises agissent comme les noms typedef, sauf qu’un programme peut déclarer un object, une fonction ou un énumérateur avec le même nom et la même scope qu’une balise. Dans ce cas, le nom de l’object, de la fonction ou de l’énumérateur cache 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é de l’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 lorsqu’il est compilé en C ++. Le statut du nom seul fait référence à la fonction. Le programme ne peut faire référence au type qu’en utilisant le statut struct de specified-type-specifier.

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

 p = foo(); 

in main doit construire un object foo et appliquer l’opérateur de conversion. La déclaration 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 cache le nom de la classe foo, donc la référence à foo dans main fait référence à la fonction et non à 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 est d'append le typedef suivant pour le nom de classe foo:

 typedef class foo foo; 

immédiatement avant ou après la définition de la 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 réellement ces typedefs. Cela demande beaucoup de discipline. Étant donné que l’incidence des erreurs telles que celle du Listing 1 est probablement assez faible, vous ne vous heurtez jamais à ce problème. Mais si une erreur dans votre logiciel peut causer des blessures corporelles, vous devriez é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 une fonction ou un nom d'object dans la même scope que la classe. Les règles de masquage dans C étaient une erreur, et elles ne devraient pas ê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.

Une différence plus importante: les typedef ne peuvent pas être déclarés en avant. Donc, pour l’option typedef , vous devez #include le fichier contenant le typedef , c’est-à-dire tout ce qui inclut #include s votre .h inclut également ce fichier, qu’il en ait besoin directement 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 append une déclaration forward de struct Foo; en haut de votre fichier .h , et seulement #include la définition de structure dans votre fichier .cpp .

Il y a une différence, mais subtile. Regardez comme ceci: struct Foo introduit un nouveau type. Le second crée un alias appelé Foo (et non un nouveau type) pour un type de struct 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 typedef-name est syntaxiquement équivalent à un mot-clé et nomme le type associé à l’identifiant de la manière décrite à l’Article 8. Un nom-typedef est donc synonyme d’un autre type. Un nom-typedef n’introduit pas un nouveau type comme le font 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) uniquement à des fins de liaison ( 3.5). [ Exemple:

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

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

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

La structure elle-même est un type anonyme, vous n’avez donc pas de nom à transmettre pour déclarer.

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

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

 struct myStruct; //forward declaration fails void blah(myStruct* pStruct); //error C2371: 'myStruct' : redefinition; different basic types 

Une différence importante entre une “structure de type” et une “structure” en C ++ est que l’initialisation des membres en ligne dans les “structures de type” 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; }; 

Struct consiste à créer un type de données. Le typedef consiste à définir un pseudonyme pour un type de données.

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

 struct Foo bar;