En C ++ , y a-t-il une différence entre:
struct Foo { ... };
et:
typedef struct { ... } Foo;
En C ++ , y a-t-il une différence entre:
struct Foo { ... };
et:
typedef struct { ... } Foo;
Réponses:
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 typedef
et d'autres identifiants) .
Si vous venez de dire:
struct Foo { ... };
Foo x;
vous obtiendrez une erreur de compilation, car elle Foo
n'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 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 un typedef
pour 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
/ class
agissent 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.
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.
Listing 1
et les Listing 2
liens sont rompus. Regarde.
Une autre différence importante: les typedef
s ne peuvent pas être déclarés en avant. Donc, pour l' typedef
option, vous devez #include
le fichier contenant le typedef
, ce qui signifie que tout ce qui #include
vous .h
appartient 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 .h
fichier, et uniquement #include
la définition de la structure dans votre .cpp
fichier.
Il y a 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 struct
type 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.
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
myStruct
vit dans l'espace de noms des balises et typedef_ed myStruct
vit 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.
typedef
déclaration directe avec le typedef
nom 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 typedef
nom ed n'est pas légal. Nous devons donc inclure le typedef entier chaque fois que nous devons utiliser myStruct
pour désigner un type. Corrigez-moi si je vous ai mal compris. Merci.
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; };
x
est 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).
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;