¿Diferencia entre ‘struct’ y ‘typedef struct’ en C ++?

En C ++, ¿hay alguna diferencia entre:

struct Foo { ... }; 

y

 typedef struct { ... } Foo; 

En C ++, solo hay una diferencia sutil. Es un remanente de C, en el que hace la diferencia.

El estándar de lenguaje C ( C89 §3.1.2.3 , C99 §6.2.3 y C11 §6.2.3 ) ordena espacios de nombres separados para diferentes categorías de identificadores, incluidos los identificadores de etiqueta (para struct / union / enum ) y los identificadores ordinarios (para typedef y otros identificadores).

Si acabas de decir:

 struct Foo { ... }; Foo x; 

obtendría un error de comstackción, porque Foo solo está definido en el espacio de nombres de la etiqueta.

Deberías declararlo como:

 struct Foo x; 

Cada vez que quieras referirte a un Foo , siempre tendrás que llamarlo struct Foo . Esto se vuelve molesto rápidamente, por lo que puede agregar un typedef :

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

Ahora struct Foo (en el espacio de nombre de la etiqueta) y simplemente Foo (en el espacio de nombres del identificador ordinario) se refieren a la misma cosa, y usted puede declarar libremente objetos de tipo Foo sin la palabra clave struct .


El constructo:

 typedef struct Foo { ... } Foo; 

es solo una abreviatura para la statement y typedef .


Finalmente,

 typedef struct { ... } Foo; 

declara una estructura anónima y crea un typedef para ella. Por lo tanto, con este constructo, no tiene un nombre en el espacio de nombre de etiqueta, solo un nombre en el espacio de nombres typedef. Esto significa que tampoco puede ser declarado adelante. Si desea hacer una statement directa, debe darle un nombre en el espacio de nombre de la etiqueta .


En C ++, todas class declaraciones struct / union / enum / class actúan como si estuvieran implícitamente typedef , siempre y cuando el nombre no esté oculto por otra statement con el mismo nombre. Ver la respuesta de Michael Burr para los detalles completos.

En este artículo de DDJ , Dan Saks explica un área pequeña donde los errores pueden aparecer si no escribe las estructuras (¡y las clases!):

Si lo desea, puede imaginarse que C ++ genera un typedef para cada nombre de etiqueta, como

 typedef class string string; 

Desafortunadamente, esto no es del todo exacto. Ojalá fuera así de simple, pero no lo es. C ++ no puede generar tales typedefs para estructuras, uniones o enumeraciones sin introducir incompatibilidades con C.

Por ejemplo, supongamos que un progtwig C declara tanto una función como un estado de estructura llamada:

 int status(); struct status; 

De nuevo, esto puede ser una mala práctica, pero es C. En este progtwig, el estado (en sí mismo) se refiere a la función; estado de estructura se refiere al tipo.

Si C ++ generó automáticamente typedefs para las tags, cuando compiló este progtwig como C ++, el comstackdor generaría:

 typedef struct status status; 

Desafortunadamente, este tipo de nombre entraría en conflicto con el nombre de la función y el progtwig no se comstackría. Es por eso que C ++ no puede simplemente generar un typedef para cada etiqueta.

En C ++, las tags actúan como los nombres de tipodefde, excepto que un progtwig puede declarar un objeto, función o enumerador con el mismo nombre y el mismo scope que una etiqueta. En ese caso, el objeto, la función o el nombre del enumerador oculta el nombre de la etiqueta. El progtwig puede referirse al nombre de la etiqueta solo mediante la palabra clave class, struct, union o enum (según corresponda) delante del nombre de la etiqueta. Un nombre de tipo que consta de una de estas palabras clave seguido de una etiqueta es un especificador de tipo elaborado. Por ejemplo, el estado de la estructura y el mes enum son especificadores de tipo elaborados.

Por lo tanto, un progtwig de C que contiene ambos:

 int status(); struct status; 

se comporta igual cuando se comstack como C ++. El estado del nombre solo se refiere a la función. El progtwig puede referirse al tipo solo mediante el uso del estado de estructura del especificador de tipo elaborado.

Entonces, ¿cómo permite esto que los errores entren en los progtwigs? Considere el progtwig en el Listado 1 . Este progtwig define un foo de clase con un constructor predeterminado y un operador de conversión que convierte un objeto foo en char const *. La expresion

 p = foo(); 

en main debería construir un objeto foo y aplicar el operador de conversión. La statement de salida posterior

 cout < < p << '\n'; 

debería mostrar la clase foo, pero no es así. Muestra la función foo.

Este sorprendente resultado se produce porque el progtwig incluye el encabezado lib.h que se muestra en el Listado 2 . Este encabezado define una función también llamada foo. El nombre de la función foo oculta el nombre de clase foo, por lo que la referencia a foo en main se refiere a la función, no a la clase. main puede referirse a la clase solo mediante el uso de un especificador de tipo elaborado, como en

 p = class foo(); 

La forma de evitar dicha confusión en todo el progtwig es agregar el siguiente typedef para el nombre de clase foo:

 typedef class foo foo; 

inmediatamente antes o después de la definición de la clase. Este tipodef provoca un conflicto entre el nombre de tipo foo y el nombre de la función foo (de la biblioteca) que desencadenará un error en tiempo de comstackción.

No conozco a nadie que realmente escriba estos typedefs por rutina. Requiere mucha disciplina. Dado que la incidencia de errores como la del Listado 1 es probablemente muy pequeña, muchos nunca se topan con este problema. Pero si un error en su software puede causar lesiones corporales, entonces debe escribir los typedefs sin importar cuán improbable sea el error.

No puedo imaginar por qué alguien querría esconder un nombre de clase con una función o nombre de objeto en el mismo ámbito que la clase. Las reglas de ocultación en C fueron un error, y no deberían haberse extendido a las clases en C ++. De hecho, puede corregir el error, pero requiere una disciplina de progtwigción y un esfuerzo adicionales que no deberían ser necesarios.

Una diferencia más importante: typedef s no puede ser declarado adelante. Por lo tanto, para la opción typedef , debe #include el archivo que contiene typedef , lo que significa que todo lo que #include es su .h también incluye ese archivo, ya sea que lo necesite directamente o no, y así sucesivamente. Definitivamente puede afectar sus tiempos de construcción en proyectos más grandes.

Sin typedef , en algunos casos puede agregar una statement de struct Foo; en la parte superior de su archivo .h , y solo #include la definición de estructura en su archivo .cpp .

Hay una diferencia, pero sutil. Mírelo de esta manera: struct Foo presenta un nuevo tipo. El segundo crea un alias llamado Foo (y no un nuevo tipo) para un tipo de struct sin nombre.

7.1.3 El especificador typedef

1 […]

Un nombre declarado con el especificador typedef se convierte en un typedef-name. Dentro del scope de su statement, un typedef-name es sintácticamente equivalente a una palabra clave y nombra el tipo asociado con el identificador en la forma descrita en la cláusula 8. Un typedef-name es, por lo tanto, un sinónimo de otro tipo. Un typedef-name no introduce un nuevo tipo como lo hace una statement de clase (9.1) o una statement enum.

8 Si la statement typedef define una clase no nombrada (o enum), el primer typedef-name declarado por la statement como ese tipo de clase (o tipo enum) se usa para indicar el tipo de clase (o tipo de enumeración) solo para fines de vinculación ( 3.5). [Ejemplo:

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

Por lo tanto, un typedef siempre se usa como un marcador de posición / sinónimo para otro tipo.

No puede usar la statement forward con la estructura typedef.

La estructura en sí es anónima, por lo que no tiene un nombre real para reenviar declarar.

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

Una statement avanzada como esta no funcionará:

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

Una diferencia importante entre una ‘typedef struct’ y una ‘struct’ en C ++ es que la inicialización de miembros en línea en ‘typedef structs’ no 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 es crear un tipo de datos. Typedef es establecer un apodo para un tipo de datos.

No hay diferencia en C ++, pero creo que en C le permitiría declarar instancias de la estructura Foo sin hacerlo explícitamente:

 struct Foo bar;