Differenza tra 'struct' e 'typedef struct' in C ++?


Risposte:


1202

In C ++, c'è solo una sottile differenza. È un holdover da C, in cui fa la differenza.

Lo standard del linguaggio C ( C89 §3.1.2.3 , C99 §6.2.3 e C11 §6.2.3 ) impone spazi dei nomi separati per diverse categorie di identificatori, inclusi identificatori di tag (per struct/ union/ enum) e identificatori ordinari (per typedefe altri identificatori) .

Se hai appena detto:

struct Foo { ... };
Foo x;

otterresti un errore del compilatore, perché Fooè definito solo nello spazio dei nomi dei tag.

Dovresti dichiararlo come:

struct Foo x;

Ogni volta che vuoi fare riferimento a a Foo, dovresti sempre chiamarlo a struct Foo. Questo diventa fastidioso velocemente, quindi puoi aggiungere un typedef:

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

Ora struct Foo(nello spazio dei nomi dei tag) e semplicemente Foo(nello spazio dei nomi dell'identificatore ordinario) entrambi si riferiscono alla stessa cosa, e puoi dichiarare liberamente oggetti di tipo Foosenza la structparola chiave.


Il costrutto:

typedef struct Foo { ... } Foo;

è solo un'abbreviazione per la dichiarazione e typedef.


Finalmente,

typedef struct { ... } Foo;

dichiara una struttura anonima e ne crea una typedef. Pertanto, con questo costrutto, non ha un nome nello spazio dei nomi dei tag, ma solo un nome nello spazio dei nomi typedef. Ciò significa che non può essere dichiarato in avanti. Se vuoi fare una dichiarazione diretta, devi dargli un nome nello spazio dei nomi dei tag .


In C ++, tutte le dichiarazioni struct/ union/ enum/ si classcomportano come se fossero implicitamente typedef" redatte ", purché il nome non sia nascosto da un'altra dichiarazione con lo stesso nome. Vedi la risposta di Michael Burr per i dettagli completi.


53
Mentre quello che dici è vero, AFAIK, l'affermazione, "typedef struct {...} Foo;" crea un alias per una struttura senza nome.
Dirkgently

25
Buona cattura, c'è una sottile differenza tra "typedef struct Foo {...} Foo;" e "typedef struct {...} Foo;".
Adam Rosenfield,

8
In C, i tag struct, i tag union e i tag di enumerazione condividono uno spazio dei nomi, anziché (struct e union) usando due come rivendicato sopra; lo spazio dei nomi a cui si fa riferimento per i nomi typedef è effettivamente separato. Ciò significa che non puoi avere entrambi "union x {...};" e 'struct x {...};' in un unico ambito.
Jonathan Leffler,

10
A parte la cosa non del tutto imprecisa, un'altra differenza tra i due pezzi di codice nella domanda è che Foo può definire un costruttore nel primo esempio, ma non nel secondo (poiché le classi anonime non possono definire costruttori o distruttori) .
Steve Jessop,

1
@Lazer: ci sono sottili differenze, ma Adam significa (come continua a dire) che puoi usare 'Type var' per dichiarare le variabili senza un typedef.
Fred Nurk,

226

In questo articolo del DDJ , Dan Saks spiega una piccola area in cui i bug possono insinuarsi se non scrivi le tue strutture (e classi!):

Se lo desideri, puoi immaginare che C ++ generi un typedef per ogni nome di tag, ad esempio

typedef class string string;

Sfortunatamente, questo non è del tutto esatto. Vorrei che fosse così semplice, ma non lo è. C ++ non può generare tali typedef per strutture, sindacati o enum senza introdurre incompatibilità con C.

Ad esempio, supponiamo che un programma C dichiari sia una funzione che una struttura denominata status:

int status(); struct status;

Ancora una volta, questa può essere una cattiva pratica, ma è C. In questo programma, lo stato (di per sé) si riferisce alla funzione; lo stato della struttura si riferisce al tipo.

Se C ++ generasse automaticamente typedefs per i tag, quindi quando si compila questo programma come C ++, il compilatore genererebbe:

typedef struct status status;

Sfortunatamente, questo nome di tipo sarebbe in conflitto con il nome della funzione e il programma non verrebbe compilato. Ecco perché C ++ non può semplicemente generare un typedef per ogni tag.

In C ++, i tag si comportano esattamente come i nomi typedef, tranne per il fatto che un programma può dichiarare un oggetto, una funzione o un enumeratore con lo stesso nome e lo stesso ambito di un tag. In tal caso, l'oggetto, la funzione o il nome dell'enumeratore nascondono il nome del tag. Il programma può fare riferimento al nome del tag solo usando la parola chiave class, struct, union o enum (come appropriato) davanti al nome del tag. Un nome di tipo costituito da una di queste parole chiave seguito da un tag è un identificatore di tipo elaborato. Ad esempio, lo stato di struct e il mese enum sono specificatori di tipo elaborato.

Pertanto, un programma C che contiene entrambi:

int status(); struct status;

si comporta allo stesso modo quando compilato come C ++. Il solo stato del nome si riferisce alla funzione. Il programma può fare riferimento al tipo solo usando lo stato struct dell'identificatore del tipo elaborato.

In che modo ciò consente ai bug di insinuarsi nei programmi? Considera il programma nel Listato 1 . Questo programma definisce una classe foo con un costruttore predefinito e un operatore di conversione che converte un oggetto foo in char const *. L'espressione

p = foo();

in linea di principio dovrebbe costruire un oggetto foo e applicare l'operatore di conversione. La successiva istruzione di output

cout << p << '\n';

dovrebbe mostrare classe foo, ma non lo è. Visualizza la funzione foo.

Questo risultato sorprendente si verifica perché il programma include l'intestazione lib.h mostrata nel Listato 2 . Questa intestazione definisce una funzione chiamata anche foo. Il nome della funzione foo nasconde il nome della classe foo, quindi il riferimento a foo in main si riferisce alla funzione, non alla classe. main può riferirsi alla classe solo usando uno specificatore di tipo elaborato, come in

p = class foo();

Il modo per evitare tale confusione in tutto il programma è aggiungere il seguente typedef per il nome della classe foo:

typedef class foo foo;

immediatamente prima o dopo la definizione della classe. Questo typedef provoca un conflitto tra il nome del tipo foo e il nome della funzione foo (dalla libreria) che attiverà un errore di compilazione.

Non conosco nessuno che scriva davvero questi dattiloscritti come ovvio. Richiede molta disciplina. Poiché l'incidenza di errori come quello nel Listato 1 è probabilmente piuttosto piccola, molti non si scontrano mai con questo problema. Ma se un errore nel tuo software potrebbe causare lesioni personali, allora dovresti scrivere i typedef, non importa quanto sia improbabile l'errore.

Non riesco a immaginare perché qualcuno vorrebbe mai nascondere un nome di classe con una funzione o un nome oggetto nello stesso ambito della classe. Le regole nascoste in C erano un errore e non avrebbero dovuto essere estese alle classi in C ++. In effetti, puoi correggere l'errore, ma richiede un'ulteriore disciplina di programmazione e sforzi che non dovrebbero essere necessari.


11
Nel caso provi "class foo ()" e fallisce: in ISO C ++, "class foo ()" è un costrutto illegale (sembra che l'articolo sia stato scritto '97, prima della standardizzazione). Puoi mettere "typedef class foo foo;" in main, quindi puoi dire "foo ();" (perché allora, il nome typedef è lessicamente più vicino del nome della funzione). Sintatticamente, in T (), T deve essere un identificatore di tipo semplice. non sono ammessi identificatori di tipo elaborati. Comunque questa è una buona risposta, ovviamente.
Johannes Schaub -

Listing 1e i Listing 2collegamenti sono interrotti. Dare un'occhiata.
Prasoon Saurav,

3
Se si utilizzano convenzioni di denominazione diverse per classi e funzioni, si evita anche il conflitto di denominazione senza la necessità di aggiungere altri typedef.
zstewart,

64

Un'altra differenza importante: typedefs non può essere dichiarato in avanti. Quindi per l' typedefopzione è necessario #includeil file contenente il typedef, che significa che tutto ciò che #includeè .hincluso include anche quel file, sia che ne abbia bisogno direttamente o meno, e così via. Può sicuramente influire sui tempi di costruzione su progetti più grandi.

Senza il typedef, in alcuni casi puoi semplicemente aggiungere una dichiarazione forward struct Foo;nella parte superiore del tuo .hfile e solo #includela definizione della struttura nel tuo .cppfile.


Perché esporre la definizione di struct influisce sul tempo di costruzione? Il compilatore esegue controlli extra anche se non è necessario (data l'opzione typedef, in modo che il compilatore conosca la definizione) quando vede qualcosa come Foo * nextFoo; ?
Ricco

3
Non è davvero un controllo extra, è solo più codice che il compilatore deve gestire. Per ogni file cpp che incontra quel typedef da qualche parte nella sua catena include, sta compilando il typedef. Nei progetti più grandi il file .h contenente il typedef potrebbe facilmente finire per essere compilato centinaia di volte, anche se le intestazioni precompilate aiutano molto. Se riesci a cavartela usando una dichiarazione diretta, allora è più facile limitare l'inclusione del file .h contenente la specifica di struttura completa solo al codice che interessa davvero, e quindi il file di inclusione corrispondente viene compilato meno spesso.
Joe,

per favore @ il commentatore precedente (diverso dal proprietario del post). Ho quasi perso la tua risposta. Ma grazie per le informazioni.
Rich

Questo non è più vero in C11, dove è possibile digitare più volte la stessa struttura con lo stesso nome.
michaelmeyer,

32

V'è una differenza, ma sottile. Guardalo in questo modo: struct Foointroduce un nuovo tipo. Il secondo crea un alias chiamato Foo (e non un nuovo tipo) per un structtipo senza nome .

7.1.3 L'identificatore typedef

1 [...]

Un nome dichiarato con l'identificatore typedef diventa un nome typedef. Nell'ambito della sua dichiarazione, un typedef-name è sintatticamente equivalente a una parola chiave e nomina il tipo associato all'identificatore nel modo descritto nella Clausola 8. Un typedef-name è quindi sinonimo di un altro tipo. Un typedef-name non introduce un nuovo tipo come fa una dichiarazione di classe (9.1) o enum.

8 Se la dichiarazione typedef definisce una classe (o enum) senza nome, il primo nome typedef dichiarato dalla dichiarazione come quel tipo di classe (o tipo enum) viene utilizzato per indicare il tipo di classe (o tipo enum) solo a scopo di collegamento ( 3.5). [ Esempio:

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

Pertanto, un typedef viene sempre utilizzato come segnaposto / sinonimo per un altro tipo.


10

Non è possibile utilizzare la dichiarazione diretta con la struttura typedef.

La stessa struttura è di tipo anonimo, quindi non hai un nome reale da dichiarare.

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

Una dichiarazione anticipata come questa non funzionerà:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types

Non ricevo il secondo errore per il prototipo di funzione. Perché dice "ridefinizione; diversi tipi di base"? il compilatore non ha bisogno di sapere come appare la definizione di myStruct, giusto? Indipendentemente dal pezzo di codice (quello typedef o quello di dichiarazione forward), myStruct indica un tipo di struttura, giusto?
Ricco

@Rich Si lamenta che c'è un conflitto di nomi. C'è una dichiarazione in avanti che dice "cerca una struttura chiamata myStruct" e poi c'è il typedef che sta rinominando una struttura senza nome come "myStruct".
Yochai Timmer,

Intendi mettere sia typedef che forward dichiarazione nello stesso file? L'ho fatto e gcc lo ha compilato bene. myStruct è interpretato correttamente come la struttura senza nome. Il tag myStructvive nello spazio dei nomi dei tag e typedef_ed myStructvive nello spazio dei nomi normale in cui vivono altri identificatori come il nome della funzione, i nomi delle variabili locali. Quindi non dovrebbero esserci conflitti .. Posso mostrarti il ​​mio codice se dubiti che ci sia un errore.
Rich

@Rich GCC dà lo stesso errore, il testo varia un po ': gcc.godbolt.org/…
Yochai Timmer

Penso di capire che quando hai solo una typedefdichiarazione in avanti con il typedefnome ed, non si riferisce alla struttura senza nome. Invece la dichiarazione diretta dichiara una struttura incompleta con etichetta myStruct. Inoltre, senza vedere la definizione di typedef, il prototipo di funzione che utilizza il typedefnome ed non è legale. Quindi dobbiamo includere l'intero typedef ogni volta che dobbiamo usare myStructper indicare un tipo. Correggimi se ti ho frainteso. Grazie.
Rich

0

Una differenza importante tra una "typedef struct" e una "struct" in C ++ è che l'inizializzazione dei membri inline in "typedef struct" non funzionerà.

// 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
Non vero. In entrambi i casi xviene inizializzato. Vedi test nell'IDE online di Coliru (l'ho inizializzato a 42, quindi è più ovvio che con zero che l'incarico ha davvero avuto luogo).
Colin D Bennett,

In realtà, l'ho provato in Visual Studio 2013 e non è stato inizializzato. Questo è stato un problema riscontrato nel codice di produzione. Tutti i compilatori sono diversi e devono soddisfare solo determinati criteri.
user2796283

-3

Non c'è alcuna differenza in C ++, ma credo in C che ti consentirebbe di dichiarare istanze della struttura Foo senza fare esplicitamente:

struct Foo bar;

3
Guardate la risposta di @ dirkgently --- c'è una differenza, ma è sottile.
Keith Pinson,

-3

Struct è creare un tipo di dati. Il typedef consiste nell'impostare un nickname per un tipo di dati.


12
Non hai capito la domanda.
Inverno
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.