Diferença entre 'struct' e 'typedef struct' em C ++?


Respostas:


1202

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 typedefe 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 Foosem a structpalavra - 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 typedefpara 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/ classdeclaraçõ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.


53
Enquanto o que você diz é verdade, AFAIK, a instrução 'typedef struct {...} Foo;' cria um alias para uma estrutura sem nome.
Dirkgently 04/03/09

25
Boa captura, há uma diferença sutil entre "typedef struct Foo {...} Foo;" e "typedef struct {...} Foo;".
Adam Rosenfield

8
Em C, as tags struct, tags union e tags de enumeração compartilham um namespace, em vez de (struct e union) usando dois, conforme reivindicado acima; o espaço de nomes referenciado para nomes de typedef é de fato separado. Isso significa que você não pode ter os dois 'union x {...};' e 'struct x {...};' em um único escopo.
11139 Jonathan Leffler

10
Além da coisa não-muito-tipada, outra diferença entre os dois trechos de código na pergunta é que Foo pode definir um construtor no primeiro exemplo, mas não no segundo (já que classes anônimas não podem definir construtores ou destruidores) .
21609 Steve Jessop

1
@Lazer: Não são diferenças sutis, mas Adam meios (como ele continua a dizer) que você pode usar 'Tipo var' para declarar variáveis sem um typedef.
Fred Nurk

226

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.


11
Caso você tente "class foo ()" e ele falhe: No ISO C ++, "class foo ()" é uma construção ilegal (o artigo foi escrito '97, antes da padronização, ao que parece). Você pode colocar "typedef class foo foo;" no main, então você pode dizer "foo ();" (porque então, o typedef-name é lexicamente mais próximo do que o nome da função). Sintaticamente, em T (), T deve ser um especificador de tipo simples. especificadores de tipo elaborados não são permitidos. Ainda assim, é uma boa resposta, é claro.
Johannes Schaub - litb

Listing 1e os Listing 2links estão quebrados. Dar uma olhada.
Prasoon Saurav 13/10/10

3
Se você usar convenções de nomenclatura diferentes para classes e funções, também evitará o conflito de nomenclatura sem precisar adicionar typedefs extras.
Zstewart

64

Mais uma diferença importante: typedefs não podem ser declarados para a frente. Portanto, para a typedefopção, você deve #includeo arquivo que contém o typedef, ou seja, tudo o que #includeé seu .htambé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 .harquivo e apenas #includea definição de estrutura no seu .cpparquivo.


Por que a exposição da definição de estrutura afeta o tempo de construção? O compilador faz uma verificação extra, mesmo que não precise (dada a opção typedef, para que o compilador conheça a definição) quando vê algo como Foo * nextFoo; ?
Rich

3
Não é realmente uma verificação extra, é apenas mais um código que o compilador precisa lidar. Para cada arquivo cpp que encontra esse typedef em algum lugar de sua cadeia de inclusão, compila o typedef. Em projetos maiores, o arquivo .h contendo o typedef pode facilmente ser compilado centenas de vezes, embora os cabeçalhos pré-compilados ajudem bastante. Se você conseguir usar uma declaração de encaminhamento, será mais fácil limitar a inclusão do .h que contém a especificação de estrutura completa apenas ao código que realmente importa e, portanto, o arquivo de inclusão correspondente é compilado com menos frequência.
31414 Joe

@ no comentário anterior (que não seja o proprietário da postagem). Eu quase perdi sua resposta. Mas obrigado pela informação.
Rich

Isso não é mais verdade no C11, onde é possível digitar a mesma estrutura com o mesmo nome várias vezes.
michaelmeyer

32

Não é uma diferença, mas sutil. Veja desta maneira: struct Foointroduz um novo tipo. O segundo cria um alias chamado Foo (e não um novo tipo) para um structtipo 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.


10

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

Não recebo o segundo erro para o protótipo de função. Por que diz "redefinição; diferentes tipos básicos"? o compilador não precisa saber como é a definição do myStruct, certo? Independentemente do código (o tipo typedef ou a declaração forward), myStruct indica um tipo de estrutura, certo?
Rich

@ Rich Está reclamando que há um conflito de nomes. Há uma declaração direta dizendo "procure uma estrutura chamada myStruct" e, em seguida, há o typedef que está renomeando uma estrutura sem nome como "myStruct".
Yochai Timmer #

Você quer dizer colocar declaração typedef e forward no mesmo arquivo? Eu fiz, e o gcc o compilou bem. myStruct é corretamente interpretado como a estrutura sem nome. Tag myStructvive no namespace da tag e typedef_ed myStructvive 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.
Rich

@ Rich GCC dá o mesmo erro, o texto varia um pouco: gcc.godbolt.org/…
Yochai Timmer

Acho que entendo que quando você só tem uma typedefdeclaração encaminhada com o typedefnome 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 typedefnome ed não é legal. Portanto, temos que incluir todo o typedef sempre que precisarmos usar myStructpara denotar um tipo. Corrija-me se eu não entendi você. Obrigado.
Rich

0

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; };

3
Não é verdade. Nos dois casos, 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).
Colin D Bennett

Na verdade, eu testei no Visual Studio 2013 e não foi inicializado. Esse foi um problema que encontramos no código de produção. Todos os compiladores são diferentes e só precisam atender a certos critérios.
user2796283

-3

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;

3
Olhada resposta de @ dirkgently --- há é uma diferença, mas é sutil.
amigos estão dizendo sobre keith

-3

Struct é criar um tipo de dados. O typedef é definir um apelido para um tipo de dados.


12
Você não entendeu a pergunta.
Inverno
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.