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 atout pour lequel cela fait une différence.
Le standard de langage C ( C89 §3.1.2. , C99 §6.2. , et C11 §6.2. ) exige que les espaces de noms soient séparés. catégories d'identificateurs, y compris identificateurs de balise (pour struct
/union
/enum
) et identificateurs ordinaires (pour typedef
et autres identificateurs).
Si vous venez de dire:
struct Foo { ... };
Foo x;
vous obtiendrez une erreur de compilation, car Foo
n'est défini que dans l'espace de noms de balise.
Vous devez le déclarer comme:
struct Foo x;
Chaque fois que vous voulez faire référence à un Foo
, vous devez toujours l'appeler un struct Foo
. Cela devient vite agaçant, vous pouvez donc ajouter un typedef
:
struct Foo { ... };
typedef struct Foo Foo;
Maintenant, struct Foo
(dans l'espace de noms de balise) et simplement Foo
(dans l'espace de noms d'identificateur ordinaire) se réfèrent tous deux à la même chose, et vous pouvez déclarer librement des objets de type Foo
sans le struct
mot 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 une typedef
pour elle. Ainsi, avec cette construction, elle n’a pas de nom dans l’espace de noms de balises, mais seulement 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 forward, vous devez lui donner un nom dans l'espace de nommage de la balise .
En C++, toutes les déclarations struct
/union
/enum
/class
agissent comme si elles étaient implicitement typedef
'ed, tant que le nom n'est pas caché par une autre déclaration portant le même nom. Voir réponse de Michael Burr pour tous les détails.
Dans cet article de DDJ , Dan Saks explique un petit domaine dans lequel des bogues peuvent se glisser si vous ne dactylographiez pas vos structures (et vos classes!):
Si vous le souhaitez, vous pouvez imaginer que C++ génère une 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, mais ce n'est pas le cas. C++ ne peut pas générer de telles typefefs pour les structures, les unions ou les enums sans introduire d'incompatibilités avec C.
Par exemple, supposons qu'un programme C déclare à la fois une fonction et une structure appelée status:
int status(); struct status;
Encore une fois, cela peut être une mauvaise pratique, mais il s’agit de C. Dans ce programme, le statut (par lui-même) fait référence à la fonction; Le statut de la structure fait référence au type.
Si C++ générait automatiquement des typedefs pour les balises, lorsque vous compilerez ce programme en tant que C++, le compilateur générera:
typedef struct status status;
Malheureusement, ce nom de type serait en conflit avec le nom de la fonction et le programme ne serait pas compilé. C'est pourquoi C++ ne peut pas simplement générer un typedef pour chaque balise.
En C++, les balises agissent comme des 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é.
Ainsi, un programme C contenant à la fois:
int status(); struct status;
se comporte de la même manière lorsqu’il est compilé en C++. Le nom du statut fait uniquement référence à la fonction. Le programme peut faire référence au type uniquement à l'aide du statut de structure de spécificateur de type élaboré.
Alors, comment cela permet-il aux bogues de se glisser dans les programmes? Considérez le programme dans 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 devrait 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 est dû au fait que le programme comprend l’en-tête lib.h présenté dans 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. La référence à foo in main fait donc 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();
Pour éviter cette confusion tout au long du programme, vous devez ajouter 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. Cette typedef provoque un conflit entre le nom du type foo et le nom de la fonction foo (de la bibliothèque), ce qui déclenche une erreur lors de la compilation.
Je ne connais personne qui écrive ces textes automatiquement. Cela demande beaucoup de discipline. Etant donné que l’incidence d’erreurs telles que celle de Listing 1 est probablement assez faible, vous ne risquez jamais de vous en prendre à ce problème. Toutefois, si une erreur dans votre logiciel pouvait entraîner des blessures corporelles, vous devez écrire les corrections de type, même si l’erreur est peu probable.
Je ne peux pas imaginer pourquoi quiconque voudrait jamais cacher un nom de classe avec un nom de fonction ou d'objet dans la même étendue 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 supplémentaire et des efforts qui ne devraient pas être nécessaires.
Une autre différence importante: typedef
s ne peut pas être déclaré en avant. Donc, pour l'option typedef
, vous devez #include
le fichier contenant le typedef
, ce qui signifie tout ce que #include
s votre .h
inclut également ce fichier, qu'il en ait besoin directement ou non, et ainsi de suite. Cela peut certainement avoir un impact sur vos temps de construction sur des projets plus importants.
Sans la variable typedef
, dans certains cas, vous pouvez simplement ajouter une déclaration avancée de struct Foo;
en haut de votre fichier .h
et uniquement #include
la définition de structure dans votre .cpp
fichier.
Là est une différence, mais subtile. Regardez-le de cette façon: struct Foo
introduit un nouveau type. Le second crée un alias appelé Foo (et non un nouveau type) pour un type non nommé struct
.
7.1.3 Le spécificateur typedef
1 [...]
Un nom déclaré avec le spécificateur typedef devient un nom de typedef. Dans le cadre de sa déclaration, un nom de typedef est syntaxiquement équivalent à un mot-clé et nomme le type associé à l'identificateur de la manière décrite à l'Article 8. Un nom de typedef est un synonyme pour un autre type. Un nom de type 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 (ou enum) non nommée, 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 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 toujours est utilisé comme 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.
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
Struct est de créer un type de données. Le typedef est de définir un pseudonyme pour un type de données.
Une différence importante entre une structure 'typedef' et une 'structure' en C++ est que l'initialisation de membre en ligne dans 'typedef structs' 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; };