In C ++ , c'è qualche differenza tra:
struct Foo { ... };
e:
typedef struct { ... } Foo;
In C ++ , c'è qualche differenza tra:
struct Foo { ... };
e:
typedef struct { ... } Foo;
Risposte:
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 typedef
e 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 Foo
senza la struct
parola 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 class
comportano 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.
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.
Listing 1
e i Listing 2
collegamenti sono interrotti. Dare un'occhiata.
Un'altra differenza importante: typedef
s non può essere dichiarato in avanti. Quindi per l' typedef
opzione è necessario #include
il file contenente il typedef
, che significa che tutto ciò che #include
è .h
incluso 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 .h
file e solo #include
la definizione della struttura nel tuo .cpp
file.
V'è una differenza, ma sottile. Guardalo in questo modo: struct Foo
introduce un nuovo tipo. Il secondo crea un alias chiamato Foo (e non un nuovo tipo) per un struct
tipo 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.
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
myStruct
vive nello spazio dei nomi dei tag e typedef_ed myStruct
vive 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.
typedef
dichiarazione in avanti con il typedef
nome 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 typedef
nome ed non è legale. Quindi dobbiamo includere l'intero typedef ogni volta che dobbiamo usare myStruct
per indicare un tipo. Correggimi se ti ho frainteso. Grazie.
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; };
x
viene 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).
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;