Różnica między „struct” a „typedef struct” w C ++?

Odpowiedzi:

1201

W C ++ istnieje tylko subtelna różnica. Jest to pozostałość po C, w której robi różnicę.

Standard języka C ( C89 §3.1.2.3 , C99 §6.2.3 i C11 §6.2.3 ) nakazuje oddzielne przestrzenie nazw dla różnych kategorii identyfikatorów, w tym identyfikatorów znaczników (dla struct/ union/ enum) i zwykłych identyfikatorów (dla typedefi innych identyfikatorów) .

Jeśli właśnie powiedziałeś:

struct Foo { ... };
Foo x;

pojawi się błąd kompilatora, ponieważ Foojest on zdefiniowany tylko w przestrzeni nazw znaczników.

Musisz to zadeklarować jako:

struct Foo x;

Za każdym razem, gdy chcesz odnieść się do Foo, zawsze będziesz musiał nazwać to struct Foo. Szybko to irytuje, więc możesz dodać typedef:

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

Teraz struct Foo(w przestrzeni nazw znacznika) i po prostu Foo(w zwykłej przestrzeni nazw identyfikatora) oba odnoszą się do tej samej rzeczy i można swobodnie deklarować obiekty typu Foobez structsłowa kluczowego.


Konstrukt:

typedef struct Foo { ... } Foo;

jest tylko skrótem od deklaracji i typedef.


Wreszcie,

typedef struct { ... } Foo;

deklaruje anonimową strukturę i tworzy typedefdla niej. Tak więc w przypadku tej konstrukcji nie ma ona nazwy w przestrzeni nazw znacznika, tylko nazwa w przestrzeni nazw typedef. Oznacza to, że nie można go również zadeklarować w przód. Jeśli chcesz złożyć deklarację przesyłania dalej, musisz nadać jej nazwę w przestrzeni nazw znaczników .


W C ++ wszystkie struct deklaracje / union/ enum/ classdziałają tak, jakby były niejawnie typedefedytowane, o ile nazwa nie jest ukryta przez inną deklarację o tej samej nazwie. Zobacz szczegółowe informacje Michaela Burra .

Adam Rosenfield
źródło
53
Chociaż to, co mówisz, jest prawdą, AFAIK, stwierdzenie „typedef struct {...} Foo;” tworzy alias dla nienazwanej struktury.
reż.
25
Dobry haczyk, istnieje subtelna różnica między „typedef struct Foo {...} Foo;” i „typedef struct {...} Foo;”.
Adam Rosenfield
8
W C tagi struct, tagi unii i tagi wyliczeń mają jedną przestrzeń nazw, a nie (struct i union), używając dwóch, jak zastrzeżono powyżej; przestrzeń nazw dla nazw typedef jest rzeczywiście osobna. Oznacza to, że nie możesz mieć jednocześnie „union x {...};” i „struct x {...};” w jednym zakresie.
Jonathan Leffler,
10
Oprócz niezupełnie typowanej rzeczy, kolejną różnicą między dwoma fragmentami kodu w pytaniu jest to, że Foo może zdefiniować konstruktor w pierwszym przykładzie, ale nie w drugim (ponieważ anonimowe klasy nie mogą zdefiniować konstruktorów lub destrukterów) .
Steve Jessop
1
@Lazer: Tam subtelne różnice, ale Adam środki (jak sam mówi dalej), które można wykorzystać „typ var” zadeklarować zmienne bez typedef.
Fred Nurk
226

W tym artykule DDJ Dan Saks wyjaśnia jeden mały obszar, w którym mogą się pojawiać błędy, jeśli nie wpiszesz własnych struktur (i klas!):

Jeśli chcesz, możesz sobie wyobrazić, że C ++ generuje typedef dla każdej nazwy znacznika, na przykład

typedef class string string;

Niestety nie jest to do końca dokładne. Chciałbym, żeby to było takie proste, ale tak nie jest. C ++ nie może generować takich typedefs dla struktur, związków lub wyliczeń bez wprowadzenia niezgodności z C.

Załóżmy na przykład, że program C deklaruje zarówno funkcję, jak i strukturę o nazwie status:

int status(); struct status;

Ponownie może to być zła praktyka, ale jest to C. W tym programie status (sam w sobie) odnosi się do funkcji; status struktury odnosi się do typu.

Jeśli C ++ automatycznie wygenerowałby typedefs dla tagów, to po skompilowaniu tego programu jako C ++ kompilator wygenerowałby:

typedef struct status status;

Niestety, nazwa tego typu byłaby w konflikcie z nazwą funkcji, a program się nie skompilował. Właśnie dlatego C ++ nie może po prostu wygenerować typedef dla każdego znacznika.

W C ++ znaczniki działają tak jak nazwy typedef, z tym wyjątkiem, że program może zadeklarować obiekt, funkcję lub moduł wyliczający o tej samej nazwie i tym samym zakresie co znacznik. W takim przypadku nazwa obiektu, funkcji lub modułu wyliczającego ukrywa nazwę znacznika. Program może odwoływać się do nazwy znacznika tylko przy użyciu klasy słowa kluczowego, struktury, unii lub wyliczenia (odpowiednio) przed nazwą znacznika. Nazwa typu składająca się z jednego z tych słów kluczowych, po których następuje znacznik, jest specyfikatorem typu opracowanego. Na przykład status struktury i miesiąc wyliczenia są specyfikatorami typu rozwiniętego.

Tak więc program C, który zawiera zarówno:

int status(); struct status;

zachowuje się tak samo po skompilowaniu jako C ++. Sam status nazwy odnosi się do funkcji. Program może odwoływać się do typu tylko za pomocą statusu struktury typu szczegółowego.

Więc w jaki sposób pozwala to na wkradanie się błędów do programów? Rozważ program z Listingu 1 . Ten program definiuje klasę foo z domyślnym konstruktorem oraz operator konwersji, który konwertuje obiekt foo na char const *. Ekspresja

p = foo();

w zasadzie powinien skonstruować obiekt foo i zastosować operator konwersji. Kolejna instrukcja wyjściowa

cout << p << '\n';

powinien wyświetlać klasę foo, ale tak nie jest. Wyświetla funkcję foo.

Ten zaskakujący wynik występuje, ponieważ program zawiera nagłówek lib.h pokazany na Listingu 2 . Ten nagłówek definiuje funkcję o nazwie również foo. Nazwa funkcji foo ukrywa nazwę klasy foo, więc odwołanie do foo w main odnosi się do funkcji, a nie do klasy. main może odnosić się do klasy tylko przy użyciu specyfikatora typu rozwiniętego, jak w

p = class foo();

Aby uniknąć takich nieporozumień w całym programie, dodaj następujący typedef dla nazwy klasy foo:

typedef class foo foo;

bezpośrednio przed definicją klasy lub po niej. Ten typedef powoduje konflikt między nazwą typu foo a nazwą funkcji foo (z biblioteki), który wywoła błąd czasu kompilacji.

Oczywiście nie znam nikogo, kto napisałby te maszynopisy. Wymaga dużej dyscypliny. Ponieważ częstość występowania błędów, takich jak ta z Listingu 1, jest prawdopodobnie niewielka, wielu z nich nigdy nie ma nic przeciwko temu problemowi. Ale jeśli błąd w oprogramowaniu może spowodować obrażenia ciała, powinieneś napisać typedefs bez względu na to, jak mało prawdopodobny jest błąd.

Nie mogę sobie wyobrazić, dlaczego ktokolwiek miałby kiedykolwiek chcieć ukryć nazwę klasy z nazwą funkcji lub obiektu w tym samym zakresie, co klasa. Zasady ukrywania w C były błędem i nie powinny były zostać rozszerzone na klasy w C ++. Rzeczywiście, możesz poprawić błąd, ale wymaga to dodatkowej dyscypliny programistycznej i wysiłku, który nie powinien być konieczny.

Michael Burr
źródło
11
Jeśli spróbujesz „class foo ()”, a to się nie powiedzie: w ISO C ++ „class foo ()” jest konstrukcją niedozwoloną (wydaje się, że artykuł został napisany w '97, przed standaryzacją). Możesz wpisać „typedef class foo foo;” na main, możesz powiedzieć „foo ();” (ponieważ wtedy nazwa typedef jest bliższa leksykalnie niż nazwa funkcji). Składniowo, w T (), T musi być specyfikatorem prostego typu. opracowane specyfikatory typów są niedozwolone. Oczywiście jest to dobra odpowiedź.
Johannes Schaub - litb
Listing 1i Listing 2linki są zepsute. Spójrz.
Prasoon Saurav
3
Jeśli stosujesz różne konwencje nazewnictwa dla klas i funkcji, unikniesz również konfliktu nazewnictwa bez potrzeby dodawania dodatkowych typów czcionek.
zstewart
64

Jeszcze jedna ważna różnica: typedefnie można zadeklarować w przód. Więc dla typedefopcji musisz #includeplik zawierający typedef, co oznacza, że ​​wszystko #includejest twoje.h również zawiera ten plik, bez względu na to, czy jest on bezpośrednio potrzebny, czy nie, i tak dalej. Może to zdecydowanie wpłynąć na czas kompilacji większych projektów.

Bez typedef, w niektórych przypadkach można po prostu dodać do przodu deklarację struct Foo;w górnej części .hpliku, a tylko #includedefinicji struct w Twoim .cpppliku.

Joe
źródło
Dlaczego ujawnienie definicji struktury wpływa na czas kompilacji? Czy kompilator wykonuje dodatkowe sprawdzanie, nawet jeśli nie musi (biorąc pod uwagę opcję typedef, aby kompilator znał definicję), gdy widzi coś takiego jak Foo * nextFoo; ?
Bogaty
3
To naprawdę nie jest dodatkowe sprawdzanie, to tylko więcej kodu, z którym kompilator musi sobie poradzić. Dla każdego pliku cpp, który napotyka typedef gdzieś w swoim łańcuchu dołączania, jego kompilacja to typedef. W większych projektach plik .h zawierający typedef mógłby łatwo zostać skompilowany setki razy, chociaż wstępnie skompilowane nagłówki bardzo pomagają. Jeśli można uniknąć użycia deklaracji przesyłania dalej, łatwiej jest ograniczyć dołączanie .h zawierającej pełną specyfikację struktury do tylko kodu, który naprawdę go obchodzi, dlatego odpowiedni plik dołączany jest kompilowany rzadziej.
Joe
proszę @ poprzedniego komentującego (innego niż właściciel wpisu). Prawie przegapiłem twoją odpowiedź. Ale dzięki za informację.
Bogaty
Nie jest to już prawdą w C11, gdzie można wpisać kilka razy tę samą strukturę o tej samej nazwie.
michaelmeyer,
32

Nie ma różnicy, ale subtelne. Spójrz na to w ten sposób: struct Foowprowadza nowy typ. Drugi tworzy alias o nazwie Foo (a nie nowy typ) dla typu nienazwanego struct.

7.1.3 Specyfikator typedef

1 [...]

Nazwa zadeklarowana za pomocą specyfikatora typedef staje się nazwą typedef. W ramach deklaracji nazwa typedef jest składniowo równoważna słowu kluczowemu i nazywa typ powiązany z identyfikatorem w sposób opisany w klauzuli 8. Nazwa typedef jest zatem synonimem innego typu. Typedef-name nie wprowadza nowego typu tak jak deklaracja klasy (9.1) lub deklaracja enum.

8 Jeśli deklaracja typedef definiuje nienazwaną klasę (lub wyliczenie), pierwsza nazwa typedef zadeklarowana przez deklarację jako ten typ klasy (lub typ wyliczeniowy) jest używana do oznaczenia typu klasy (lub typu wyliczeniowego) wyłącznie w celu powiązania ( 3.5). [Przykład:

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

Więc typedef zawsze jest używany jako symbol zastępczy / synonim innego typu.

bezpośrednio
źródło
10

Nie można używać deklaracji przesyłania z strukturą typedef.

Sama struktura jest typem anonimowym, więc nie masz rzeczywistej nazwy do przekazania.

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

Deklaracja przekazująca taka jak ta nie będzie działać:

struct myStruct; //forward declaration fails

void blah(myStruct* pStruct);

//error C2371: 'myStruct' : redefinition; different basic types
Yochai Timmer
źródło
Nie otrzymuję drugiego błędu dla prototypu funkcji. Dlaczego mówi „redefinicja; różne podstawowe typy”? kompilator nie musi wiedzieć, jak wygląda definicja myStruct, prawda? Bez względu na fragment kodu (typedef lub deklaracja przesyłania dalej), myStruct oznacza typ struktury, prawda?
Bogaty
@Rich Narzeka, że ​​istnieje konflikt nazwisk. Jest deklaracja do przodu z napisem „poszukaj struktury o nazwie myStruct”, a następnie typedef, który zmienia nazwę bezimiennej struktury na „myStruct”.
Yochai Timmer
Masz na myśli umieszczenie deklaracji typedef i forward w tym samym pliku? Zrobiłem to i gcc skompilowało to dobrze. myStruct jest poprawnie interpretowany jako bezimienna struktura. Znacznik myStructżyje w przestrzeni nazw znaczników, a typedef_ed myStructżyje w normalnej przestrzeni nazw, w której mieszkają inne identyfikatory, takie jak nazwa funkcji, nazwy zmiennych lokalnych. Więc nie powinno być żadnego konfliktu .. Mogę pokazać ci mój kod, jeśli masz wątpliwości, że jest w nim jakiś błąd.
Rich
@Rich GCC podaje ten sam błąd, tekst jest nieco inny
Yochai Timmer
Wydaje mi się, że rozumiem, że kiedy masz tylko typedefdeklarację przekazania o typedefnazwie ed, nie odnosi się ona do struktury bez nazwy. Zamiast tego deklaracja forward deklaruje niepełną strukturę ze znacznikiem myStruct. Ponadto, bez zobaczenia definicji typedef, prototyp funkcji wykorzystujący typedefnazwę ed jest niezgodny z prawem. Musimy więc uwzględnić cały typedef, ilekroć musimy użyć myStructdo oznaczenia typu. Popraw mnie, jeśli cię źle zrozumiałem. Dzięki.
Bogaty
0

Ważną różnicą między „strukturą typedef” a „strukturą” w C ++ jest to, że inicjalizacja elementu wbudowanego w „strukturach typedef” nie będzie działać.

// 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; };
użytkownik2796283
źródło
3
Nie prawda. W obu przypadkach xjest inicjowany. Zobacz test w internetowym środowisku IDE Coliru (zainicjowałem go na 42, więc jest bardziej oczywiste niż zero, że przypisanie naprawdę miało miejsce).
Colin D Bennett
Właściwie przetestowałem go w Visual Studio 2013 i nie został zainicjowany. To był problem, na który natrafiliśmy w kodzie produkcyjnym. Wszystkie kompilatory są różne i muszą tylko spełniać określone kryteria.
user2796283,
-3

Nie ma różnicy w C ++, ale wierzę, że w C pozwoliłoby to zadeklarować wystąpienia struktury Foo bez wyraźnego wykonywania:

struct Foo bar;
Xian
źródło
3
Spójrz na odpowiedź @ dirkgently --- jest różnica, ale jest subtelna.
Keith Pinson,
-3

Struct ma na celu utworzenie typu danych. Typedef ma ustawić pseudonim dla typu danych.

David Tu
źródło
12
Nie zrozumiałeś pytania.
Zima