¿Diferencia entre 'struct' y 'typedef struct' en C ++?

Respuestas:

1202

En C ++, solo hay una sutil diferencia. 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 ) exige espacios de nombres separados para diferentes categorías de identificadores, incluidos identificadores de etiqueta (para struct/ union/ enum) e identificadores ordinarios (para typedefy otros identificadores) .

Si acabas de decir:

struct Foo { ... };
Foo x;

obtendría un error del compilador, porque Foosolo se define en el espacio de nombres de la etiqueta.

Tendría que declararlo como:

struct Foo x;

Cada vez que desee hacer referencia a un Foo, siempre tendrá que llamarlo a 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 nombres de la etiqueta) y simplemente Foo(en el espacio de nombres del identificador ordinario) ambos se refieren a lo mismo, y puede declarar libremente objetos de tipo Foosin la structpalabra clave.


El constructo:

typedef struct Foo { ... } Foo;

es solo una abreviatura de la declaración y typedef.


Finalmente,

typedef struct { ... } Foo;

declara una estructura anónima y crea un typedefpara ello. Por lo tanto, con esta construcción, no tiene un nombre en el espacio de nombres de la etiqueta, solo un nombre en el espacio de nombres typedef. Esto significa que tampoco se puede declarar hacia adelante. Si desea hacer una declaración directa, debe darle un nombre en el espacio de nombres de la etiqueta .


En C ++, todas las declaraciones struct/ union/ enum/ classactúan como si estuvieran implícitamente typedefeditadas, siempre que el nombre no esté oculto por otra declaración con el mismo nombre. Vea la respuesta de Michael Burr para los detalles completos.

Adam Rosenfield
fuente
53
Si bien lo que usted dice es cierto, AFAIK, la declaración, 'typedef struct {...} Foo;' crea un alias para una estructura sin nombre.
Dirkgently
25
Buena captura, hay una sutil diferencia entre "typedef struct Foo {...} Foo;" y "typedef struct {...} Foo;".
Adam Rosenfield
8
En C, las etiquetas de estructura, las etiquetas de unión y las etiquetas de enumeración comparten un espacio de nombres, en lugar de (estructura y unión) usando dos como se afirmó anteriormente; el espacio de nombres al que se hace referencia para los nombres de typedef es de hecho separado. Eso significa que no puede tener ambas 'union x {...};' y 'struct x {...};' en un solo alcance.
Jonathan Leffler
10
Aparte de lo que no es del todo tipo de definición, otra diferencia entre las dos piezas de código en la pregunta es que Foo puede definir un constructor en el primer ejemplo, pero no en el segundo (ya que las clases anónimas no pueden definir constructores o destructores) .
Steve Jessop el
1
@Lazer: No son diferencias sutiles, pero Adam medios (como él va a decir) que se puede utilizar 'Tipo var' para declarar variables sin un typedef.
Fred Nurk
226

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 imaginar que C ++ genera un typedef para cada nombre de etiqueta, como

typedef class string string;

Desafortunadamente, esto no es del todo exacto. Desearía que 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 programa en C declara tanto una función como una estructura denominada estado:

int status(); struct status;

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

Si C ++ generó automáticamente typedefs para etiquetas, cuando compiló este programa como C ++, el compilador generaría:

typedef struct status status;

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

En C ++, las etiquetas actúan como los nombres typedef, excepto que un programa puede declarar un objeto, función o enumerador con el mismo nombre y el mismo alcance que una etiqueta. En ese caso, el nombre del objeto, función o enumerador oculta el nombre de la etiqueta. El programa puede hacer referencia al nombre de la etiqueta solo mediante el uso de 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 de enumeración son especificadores de tipo elaborado.

Por lo tanto, un programa en C que contiene ambos:

int status(); struct status;

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

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

p = foo();

en main debe construir un objeto foo y aplicar el operador de conversión. La siguiente declaración de salida

cout << p << '\n';

debería mostrar class foo, pero no lo hace. Muestra la función foo.

Este sorprendente resultado ocurre porque el programa 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 la clase foo, por lo que la referencia a foo en main se refiere a la función, no a la clase. main solo puede referirse a la clase utilizando un especificador de tipo elaborado, como en

p = class foo();

La forma de evitar tal confusión en todo el programa 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 clase. Este typedef causa un conflicto entre el nombre de tipo foo y el nombre de función foo (de la biblioteca) que desencadenará un error en tiempo de compilación.

No conozco a nadie que realmente escriba estos typedefs como algo natural. Requiere mucha disciplina. Dado que la incidencia de errores como el del Listado 1 es probablemente bastante pequeña, es posible que nunca se enfrente a 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 ocultar un nombre de clase con una función o un 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 programación adicional y un esfuerzo que no debería ser necesario.

Michael Burr
fuente
11
En caso de que intente "class foo ()" y falle: En ISO C ++, "class foo ()" es una construcción ilegal (el artículo fue escrito '97, antes de la estandarización, parece). Puede poner "typedef class foo foo;" en main, entonces puedes decir "foo ();" (porque entonces, el typedef-name está léxico más cerca que el nombre de la función). Sintácticamente, en T (), T debe ser un especificador de tipo simple. no se permiten especificadores de tipo elaborados. Aún así, esta es una buena respuesta, por supuesto.
Johannes Schaub - litb
Listing 1y los Listing 2enlaces están rotos. Echar un vistazo.
Prasoon Saurav el
3
Si utiliza diferentes convenciones de nomenclatura para clases y funciones, también evitará el conflicto de nomenclatura sin necesidad de agregar tipos de letra adicionales.
zstewart
64

Una diferencia más importante: typedefs no se puede declarar hacia adelante. Entonces, para la typedefopción, debe incluir #includeel archivo que contiene typedef, es decir, todo lo que #includees suyo .htambié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 el typedef, en algunos casos, solo puede agregar una declaración hacia adelante struct Foo;en la parte superior de su .harchivo, y solo #includela definición de estructura en su .cpparchivo.

Joe
fuente
¿Por qué exponer la definición de estructura impacta el tiempo de construcción? ¿El compilador realiza una comprobación adicional incluso si no es necesario (dada la opción typedef, para que el compilador conozca la definición) cuando ve algo como Foo * nextFoo; ?
Rico
3
No es realmente una comprobación adicional, es solo más código con el que el compilador necesita lidiar. Para cada archivo cpp que encuentra ese typedef en algún lugar de su cadena de inclusión, está compilando el typedef. En proyectos más grandes, el archivo .h que contiene typedef podría terminar fácilmente compilándose cientos de veces, aunque los encabezados precompilados ayudan mucho. Si puede salirse con la suya utilizando una declaración de reenvío, es más fácil limitar la inclusión de .h que contiene la especificación completa de estructura solo al código que realmente le importa, y por lo tanto, el archivo de inclusión correspondiente se compila con menos frecuencia.
Joe
Por favor, @ el comentarista anterior (que no sea el propietario de la publicación). Casi pierdo tu respuesta. Pero gracias por la información.
Rico
Esto ya no es así en C11, donde puede escribir la misma estructura con el mismo nombre varias veces.
michaelmeyer
32

No es una diferencia, pero sutil. Míralo de esta manera: struct Foointroduce un nuevo tipo. El segundo crea un alias llamado Foo (y no un tipo nuevo) para un structtipo sin nombre .

7.1.3 El especificador typedef

1 [...]

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

8 Si la declaración typedef define una clase sin nombre (o enum), el primer nombre typedef declarado por la declaración como ese tipo de clase (o tipo enum) se utiliza para denotar el tipo de clase (o tipo enum) 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 marcador de posición / sinónimo para otro tipo.

Dirkgently
fuente
10

No puede usar la declaración directa con la estructura typedef.

La estructura en sí es de tipo anónimo, por lo que no tiene un nombre real para declarar hacia adelante.

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

Una declaración como esta no funcionará:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types
Yochai Timmer
fuente
No obtengo el segundo error para el prototipo de la función. ¿Por qué dice "redefinición; diferentes tipos básicos"? el compilador no necesita saber cómo se ve la definición de myStruct, ¿verdad? No importa si se toma con un fragmento de código (el typedef one, o el de declaración directa), myStruct denota un tipo struct, ¿verdad?
Rich
@Rich Se queja de que hay un conflicto de nombres. Hay una declaración directa que dice "busca una estructura llamada myStruct" y luego está el typedef que renombra una estructura sin nombre como "myStruct".
Yochai Timmer
¿Te refieres a colocar tanto typedef como forward en el mismo archivo? Lo hice, y gcc lo compiló bien. myStruct se interpreta correctamente como la estructura sin nombre. La etiqueta myStructvive en el espacio de nombres de la etiqueta y typedef_ed myStructvive en el espacio de nombres normal donde viven otros identificadores como el nombre de la función, los nombres de las variables locales. Por lo tanto, no debería haber ningún conflicto ... Puedo mostrarle mi código si duda de que haya algún error.
Rico
@Rich GCC da el mismo error, el texto varía un poco: gcc.godbolt.org/…
Yochai Timmer
Creo que entiendo que cuando solo tiene una typedefdeclaración directa con el typedefnombre ed, no se refiere a la estructura sin nombre. En cambio, la declaración de avance declara una estructura incompleta con etiqueta myStruct. Además, sin ver la definición de typedef, el prototipo de la función que usa el typedefnombre ed no es legal. Por lo tanto, tenemos que incluir todo el typedef siempre que necesitemos usar myStructpara denotar un tipo. Corrígeme si te entendí mal. Gracias.
Rico
0

Una diferencia importante entre una 'estructura typedef' y una 'estructura' en C ++ es que la inicialización de miembros en línea en 'estructuras typedef' 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; };
usuario2796283
fuente
3
No es verdad. En ambos casos xse inicializa. Vea la prueba en el IDE en línea de Coliru (lo inicialicé a 42, por lo que es más obvio que con cero que la asignación realmente ha tenido lugar).
Colin D Bennett
En realidad, lo probé en Visual Studio 2013 y no se inicializó. Este fue un problema que encontramos en el código de producción. Todos los compiladores son diferentes y solo tienen que cumplir ciertos criterios.
user2796283
-3

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

struct Foo bar;
xian
fuente
3
Vistazo a la respuesta de @ dirkgently --- no es una diferencia, pero es sutil.
Keith Pinson
-3

Struct es crear un tipo de datos. El typedef es establecer un apodo para un tipo de datos.

David Tu
fuente
12
No entendiste la pregunta.
Invierno