Em C ++ , existe alguma diferença entre:
struct Foo { ... };
e:
typedef struct { ... } Foo;
Em C ++ , existe alguma diferença entre:
struct Foo { ... };
e:
typedef struct { ... } Foo;
Respostas:
No C ++, há apenas uma diferença sutil. É uma remanescente de C, na qual faz a diferença.
O padrão da linguagem C ( C89 §3.1.2.3 , C99 §6.2.3 e C11 §6.2.3 ) exige espaços de nomes separados para diferentes categorias de identificadores, incluindo identificadores de tag (para struct
/ union
/ enum
) e identificadores comuns (para typedef
e outros identificadores) .
Se você acabou de dizer:
struct Foo { ... };
Foo x;
você receberia um erro do compilador, porque Foo
é definido apenas no namespace da tag.
Você teria que declarar como:
struct Foo x;
Sempre que você quiser se referir a Foo
, sempre precisará chamá-lo de struct Foo
. Isso fica irritante rapidamente, então você pode adicionar um typedef
:
struct Foo { ... };
typedef struct Foo Foo;
Agora struct Foo
(no namespace da tag) e simplesmente Foo
(no namespace do identificador comum) se referem à mesma coisa, e você pode declarar livremente objetos do tipo Foo
sem a struct
palavra - chave.
A construção:
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. Portanto, com essa construção, ele não tem um nome no espaço para nome da tag, apenas um nome no espaço para nome typedef. Isso significa que também não pode ser declarado para a frente. Se você deseja fazer uma declaração de encaminhamento, é necessário dar um nome a ela no espaço de nome da tag .
Em C ++, todos os struct
/ union
/ enum
/ class
declarações agir como eles são implicitamente typedef
'ed, desde que o nome não está oculta por outra declaração com o mesmo nome. Veja a resposta de Michael Burr para obter todos os detalhes.
Em este artigo DDJ , Dan Saks explica uma pequena área onde os erros podem rastejar através Se você não typedef suas estruturas (e classes!):
Se desejar, você pode imaginar que o C ++ gera um typedef para cada nome de tag, como
typedef class string string;
Infelizmente, isso não é totalmente preciso. Eu gostaria que fosse assim tão simples, mas não é. O C ++ não pode gerar esses typedefs para estruturas, uniões ou enumerações sem introduzir incompatibilidades com C.
Por exemplo, suponha que um programa C declare uma função e uma estrutura denominada status:
int status(); struct status;
Novamente, isso pode ser uma prática ruim, mas é C. Nesse programa, status (por si só) refere-se à função; status de estrutura refere-se ao tipo
Se o C ++ gerou automaticamente typedefs para tags, quando você compilou este programa como C ++, o compilador geraria:
typedef struct status status;
Infelizmente, esse nome de tipo entra em conflito com o nome da função e o programa não é compilado. É por isso que o C ++ não pode simplesmente gerar um typedef para cada tag.
No C ++, as tags agem exatamente como os nomes de typedef, exceto que um programa pode declarar um objeto, função ou enumerador com o mesmo nome e o mesmo escopo de uma tag. Nesse caso, o nome do objeto, função ou enumerador oculta o nome da marca. O programa pode se referir ao nome da marca apenas usando a classe de palavra-chave, struct, union ou enum (conforme apropriado) na frente do nome da marca. Um nome de tipo que consiste em uma dessas palavras-chave seguidas por uma tag é um especificador de tipo elaborado. Por exemplo, o status da estrutura e o mês enum são especificadores de tipo elaborado.
Assim, um programa C que contém ambos:
int status(); struct status;
se comporta da mesma maneira quando compilado como C ++. Somente o status do nome se refere à função. O programa pode se referir ao tipo apenas usando o status de estrutura do especificador de tipo elaborado.
Então, como isso permite que os erros entrem nos programas? Considere o programa na Listagem 1 . Este programa define uma classe foo com um construtor padrão e um operador de conversão que converte um objeto foo em char const *. A expressão
p = foo();
O main deve construir um objeto foo e aplicar o operador de conversão. A instrução de saída subsequente
cout << p << '\n';
deve exibir a classe foo, mas não. Ele exibe a função foo.
Esse resultado surpreendente ocorre porque o programa inclui o cabeçalho lib.h mostrado na Listagem 2 . Este cabeçalho define uma função também chamada foo. O nome da função foo oculta o nome da classe foo, portanto, a referência a foo no main se refere à função, não à classe. main pode se referir à classe apenas usando um especificador de tipo elaborado, como em
p = class foo();
A maneira de evitar essa confusão em todo o programa é adicionar o seguinte typedef ao nome da classe foo:
typedef class foo foo;
imediatamente antes ou depois da definição da classe. Esse typedef causa um conflito entre o nome do tipo foo e o nome da função foo (da biblioteca) que acionará um erro em tempo de compilação.
Não conheço ninguém que realmente escreva esses typedefs, como é óbvio. Exige muita disciplina. Como a incidência de erros como o da Listagem 1 é provavelmente muito pequena, você nunca enfrenta esse problema. Mas se um erro no seu software puder causar lesões corporais, você deverá escrever os typedefs, independentemente do quão improvável seja o erro.
Não consigo imaginar por que alguém iria querer ocultar um nome de classe com uma função ou nome de objeto no mesmo escopo da classe. As regras de ocultação em C foram um erro e não deveriam ter sido estendidas às classes em C ++. Na verdade, você pode corrigir o erro, mas requer disciplina e esforço extra de programação que não devem ser necessários.
Listing 1
e os Listing 2
links estão quebrados. Dar uma olhada.
Mais uma diferença importante: typedef
s não podem ser declarados para a frente. Portanto, para a typedef
opção, você deve #include
o arquivo que contém o typedef
, ou seja, tudo o que #include
é seu .h
também inclui esse arquivo, se ele precisa diretamente ou não, e assim por diante. Definitivamente, pode impactar seus tempos de construção em projetos maiores.
Sem o typedef
, em alguns casos, você pode adicionar uma declaração de encaminhamento struct Foo;
no topo do seu .h
arquivo e apenas #include
a definição de estrutura no seu .cpp
arquivo.
Não é uma diferença, mas sutil. Veja desta maneira: struct Foo
introduz um novo tipo. O segundo cria um alias chamado Foo (e não um novo tipo) para um struct
tipo sem nome .
7.1.3 O especificador typedef
1 [...]
Um nome declarado com o especificador typedef se torna um nome typedef. No 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, sinônimo de outro tipo. Um typedef-name não introduz um novo tipo da mesma forma que uma declaração de classe (9.1) ou enum.
8 Se a declaração typedef definir uma classe (ou enum) sem nome, o primeiro nome de typedef declarado pela declaração como esse tipo de classe (ou tipo de enum) será usado para indicar o tipo de classe (ou tipo de 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 espaço reservado / sinônimo para outro tipo.
Você não pode usar a declaração de encaminhamento com a estrutura typedef.
A estrutura em si é do tipo anônimo, portanto, você não tem um nome real para encaminhar a declaração.
typedef struct{
int one;
int two;
}myStruct;
Uma declaração encaminhada como esta não funcionará:
struct myStruct; //forward declaration fails
void blah(myStruct* pStruct);
//error C2371: 'myStruct' : redefinition; different basic types
myStruct
vive no namespace da tag e typedef_ed myStruct
vive no namespace normal, onde outros identificadores, como nome da função, nomes de variáveis locais, residem. Portanto, não deve haver nenhum conflito. Eu posso mostrar meu código se você duvidar de algum erro.
typedef
declaração encaminhada com o typedef
nome ed, não se refere à estrutura sem nome. Em vez disso, a declaração de encaminhamento declara uma estrutura incompleta com tag myStruct
. Além disso, sem ver a definição de typedef
, o protótipo de função que usa o typedef
nome ed não é legal. Portanto, temos que incluir todo o typedef sempre que precisarmos usar myStruct
para denotar um tipo. Corrija-me se eu não entendi você. Obrigado.
Uma diferença importante entre uma 'typedef struct' e uma 'struct' no C ++ é que a inicialização do membro embutido 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; };
x
é inicializado. Veja o teste no IDE online do Coliru (eu o inicializei para 42, portanto é mais óbvio do que com zero que a atribuição realmente ocorreu).
Não há diferença em C ++, mas acredito que em C permitiria declarar instâncias da estrutura Foo sem fazer explicitamente:
struct Foo bar;