Diferença entre ‘struct’ e ‘typedef struct’ em C ++?

Em C ++, existe alguma diferença entre:

struct Foo { ... }; 

e

 typedef struct { ... } Foo; 

   

Em C ++, há apenas uma diferença sutil. É um resquício do C, no qual faz a diferença.

O padrão de linguagem C ( C89 §3.1.2.3 , C99 §6.2.3 e C11 §6.2.3 ) ordena namespaces separados para diferentes categorias de identificadores, incluindo identificadores de tag (para struct / union / enum ) e identificadores ordinários (para typedef e outros identificadores).

Se você acabou de dizer:

 struct Foo { ... }; Foo x; 

você obteria um erro do compilador, porque Foo é definido apenas no namespace da tag.

Você teria que declará-lo como:

 struct Foo x; 

Sempre que você quiser se referir a um Foo , você sempre terá que chamá-lo de struct Foo . Isso fica irritante, então você pode adicionar um typedef :

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

Agora struct Foo (no namespace do tag) e simplesmente Foo (no namespace do identificador comum) referem-se à mesma coisa, e você pode declarar livremente objects do tipo Foo sem a palavra-chave struct .


O constructo:

 typedef struct Foo { ... } Foo; 

é apenas uma abreviação para a declaração e typedef .


Finalmente,

 typedef struct { ... } Foo; 

declara uma estrutura anônima e cria um typedef para ela. Assim, com essa construção, ela não possui um nome no namespace da tag, apenas um nome no namespace typedef. Isso significa que também não pode ser declarado para a frente. Se você quiser fazer uma declaração de encaminhamento, terá que fornecer um nome no namespace da tag .


Em C ++, todas class declarações struct / union / enum / class agem como se fossem implicitamente typedef , desde que o nome não esteja oculto por outra declaração com o mesmo nome. Veja a resposta de Michael Burr para os detalhes completos.

Neste artigo do DDJ , Dan Saks explica uma pequena área onde os bugs podem se infiltrar se você não digitar suas estruturas (e classs!):

Se você quiser, você pode imaginar que o C ++ gera um typedef para cada nome de tag, como

 typedef class string string; 

Infelizmente, isso não é totalmente exato. Eu queria que fosse assim tão simples, mas não é. C ++ não pode gerar tais typedefs para structs, unions ou enums sem introduzir incompatibilidades com C.

Por exemplo, suponha que um programa C declare uma function e um struct chamado status:

 int status(); struct status; 

Novamente, isso pode ser uma má prática, mas é C. Neste programa, o status (por si só) refere-se à function; struct status refere-se ao tipo.

Se o C ++ gerasse automaticamente typedefs para tags, então quando você compilasse este programa como C ++, o compilador geraria:

 typedef struct status status; 

Infelizmente, esse nome de tipo entraria em conflito com o nome da function e o programa não seria compilado. É por isso que o C ++ não pode simplesmente gerar um typedef para cada tag.

Em C ++, tags agem exatamente como nomes typedef, exceto que um programa pode declarar um object, function ou enumerador com o mesmo nome e o mesmo escopo de uma tag. Nesse caso, o nome do object, function ou enumerador oculta o nome da marca. O programa pode se referir ao nome da tag apenas usando a palavra-chave class, struct, union ou enum (conforme apropriado) na frente do nome da tag. Um nome de tipo que consiste em uma dessas palavras-chave seguidas por uma tag é um especificador de tipo elaborado. Por exemplo, struct status e enum month são especificadores de tipos elaborados.

Assim, um programa C que contém ambos:

 int status(); struct status; 

se comporta da mesma forma quando compilado como C ++. Apenas o status do nome se refere à function. O programa pode se referir ao tipo usando apenas o status struct do tipo especificador elaborado.

Então, como isso permite que bugs invadam os programas? Considere o programa na Listagem 1 . Este programa define uma class foo com um construtor padrão e um operador de conversão que converte um object foo em char const *. A expressão

 p = foo(); 

no main deve construir um object foo e aplicar o operador de conversão. A declaração de saída subsequente

 cout < < p << '\n'; 

deve exibir class foo, mas isso não acontece. Exibe a function foo.

Esse resultado surpreendente ocorre porque o programa inclui o header lib.h mostrado na Listagem 2 . Este header define uma function também chamada foo. O nome da function foo oculta o nome da class foo, portanto, a referência a foo no main refere-se à function, não à class. main pode se referir à class apenas usando um especificador do tipo elaborado, como em

 p = class foo(); 

A maneira de evitar essa confusão em todo o programa é adicionar o seguinte typedef para o nome da class foo:

 typedef class foo foo; 

imediatamente antes ou depois da definição da turma. Esse typedef causa um conflito entre o nome do tipo foo e o nome da function foo (da biblioteca) que triggersrá um erro de tempo de compilation.

Eu não conheço ninguém que realmente escreva esses typedefs como uma coisa natural. Isso requer muita disciplina. Como a incidência de erros, como o da Listagem 1, provavelmente é muito pequena, muitos nunca entram em conflito com esse problema. Mas se um erro no seu software pode causar lesões corporais, então você deve escrever os typedefs, não importa o quão improvável o erro.

Não consigo imaginar por que alguém iria querer esconder um nome de class com uma function ou nome de object no mesmo escopo da class. As regras de ocultação em C foram um erro, e elas não deveriam ter sido estendidas para classs em C ++. De fato, você pode corrigir o erro, mas requer disciplina e esforço extra de programação que não deveriam ser necessários.

Uma diferença mais importante: typedef s não pode ser declarado para a frente. Portanto, para a opção typedef você deve #include o arquivo que contém o typedef , o que significa que tudo que #include o .h também inclui esse arquivo, quer ele precise dele diretamente ou não, e assim por diante. Pode definitivamente impactar seus tempos de construção em projetos maiores.

Sem o typedef , em alguns casos, você pode simplesmente adicionar uma declaração antecipada de struct Foo; no topo do seu arquivo .h , e somente #include a definição da estrutura no seu arquivo .cpp .

Há uma diferença, mas sutil. Veja desta forma: struct Foo introduz um novo tipo. O segundo cria um alias chamado Foo (e não um novo tipo) para um tipo de struct sem nome.

7.1.3 O especificador typedef

1 […]

Um nome declarado com o especificador typedef se torna um typedef-name. Dentro do escopo de sua declaração, um typedef-name é sintaticamente equivalente a uma palavra-chave e nomeia o tipo associado ao identificador da maneira descrita na Cláusula 8. Um typedef-name é, portanto, um sinônimo para outro tipo. Um typedef-name não introduz um novo tipo da maneira que uma declaração de class (9.1) ou uma declaração enum faz.

8 Se a declaração typedef define uma class sem nome (ou enum), o primeiro typedef-name declarado pela declaração como aquele tipo de class (ou tipo enum) é usado para denotar o tipo de class (ou tipo enum) apenas para fins de vinculação ( 3,5). [Exemplo:

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

Portanto, um typedef sempre é usado como um marcador de posição / sinônimo para outro tipo.

Você não pode usar declaração direta com o typedef struct.

A estrutura em si é um tipo anônimo, portanto, você não tem um nome real para encaminhar declarar.

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

Uma declaração de encaminhamento como essa não funciona:

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

Uma diferença importante entre um ‘typedef struct’ e um ‘struct’ em C ++ é que a boot inline do membro em ‘typedef structs’ não funcionará.

 // 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 é criar um tipo de dados. O typedef é para definir um apelido para um tipo de dados.

Não há diferença em C ++, mas eu acredito em C que permitiria declarar instâncias do struct Foo sem fazer explicitamente:

 struct Foo bar;