У C ++ , чи є різниця між:
struct Foo { ... };
і:
typedef struct { ... } Foo;
У C ++ , чи є різниця між:
struct Foo { ... };
і:
typedef struct { ... } Foo;
Відповіді:
У C ++ є лише тонка різниця. Це перехід від C, в якому він має значення.
Стандарт мови C ( C89 §3.1.2.3 , C99 §6.2.3 та C11 §6.2.3 ) наказує окремі простори імен для різних категорій ідентифікаторів, включаючи ідентифікатори тегів (для struct
/ union
/ enum
) та звичайні ідентифікатори (для typedef
та інших ідентифікаторів) .
Якщо ви щойно сказали:
struct Foo { ... };
Foo x;
ви отримаєте помилку компілятора, оскільки Foo
вона визначена лише в просторі імен тегів.
Вам доведеться оголосити це як:
struct Foo x;
Кожен раз, коли ви хочете звернутися до цього Foo
, ви завжди повинні називати його struct Foo
. Це стає дратує, тому ви можете додати typedef
:
struct Foo { ... };
typedef struct Foo Foo;
Тепер struct Foo
(у просторі імен тегів) і просто Foo
(у звичайному просторі імен ідентифікаторів) обидва посилаються на одне і те ж, і ви можете вільно оголошувати об'єкти типу Foo
без struct
ключового слова.
Конструкція:
typedef struct Foo { ... } Foo;
- лише абревіатура для декларації та typedef
.
Нарешті,
typedef struct { ... } Foo;
оголошує анонімну структуру та створює typedef
для неї. Таким чином, для цієї конструкції він не має імені в просторі імен тегів, а лише ім'я в просторі імен typedef. Це означає, що вона також не може бути оголошена вперед. Якщо ви хочете зробити переадресацію, вам потрібно вказати її ім’я в просторі імен тегів .
У C ++ всі struct
/ union
/ enum
/ class
декларації діють так, як вони неявно typedef
відредаговані, доки ім'я не приховано іншою декларацією з тим же ім'ям. Детальну інформацію див. У відповіді Майкла Берра .
У цій статті про DDJ Дан Сакс пояснює одну невелику область, через яку можуть пролазити помилки, якщо ви не вводите свої структури (та класи!):
Якщо ви хочете, ви можете уявити, що C ++ генерує typedef для кожного імені тегу, наприклад
typedef class string string;
На жаль, це не зовсім точно. Я б хотів, щоб це було так просто, але це не так. C ++ не може генерувати такі typedefs для структур, об'єднань чи перерахунків без введення несумісності з C.
Наприклад, припустимо, що програма C оголошує як функцію, так і структуру з назвою статусу:
int status(); struct status;
Знову ж, це може бути поганою практикою, але це C. У цій програмі статус (сам по собі) посилається на функцію; статус struct відноситься до типу.
Якщо C ++ автоматично генерував typedefs для тегів, тоді, коли ви компілювали цю програму як C ++, компілятор генерував би:
typedef struct status status;
На жаль, назва цього типу суперечить імені функції, і програма не буде компілюватися. Тому C ++ не може просто створити typedef для кожного тегу.
У C ++ теги діють так само, як імена typedef, за винятком того, що програма може оголосити об'єкт, функцію чи нумератор з тим самим іменем та тією самою областю, що і тег. У такому випадку ім'я об'єкта, функції або перелічувача приховує ім'я тега. Програма може посилатися на ім'я тега лише за допомогою клавіш ключових слів, struct, union або enum (відповідно) перед іменем тегу. Ім'я типу, що складається з одного з цих ключових слів, за яким слідує тег, є розробленим специфікатором типу. Наприклад, статус структури та місяць перерахунку - це розроблені специфікатори типу.
Таким чином, програма C, яка містить обидва:
int status(); struct status;
поводиться так само, коли компілюється як C ++. Сам статус імені відноситься до функції. Програма може посилатися на тип лише за допомогою розробленого статусу структури структури-специфікатора.
Тож як це дозволяє помилкам проникати в програми? Розгляньте програму в Лістингу 1 . Ця програма визначає клас foo з конструктором за замовчуванням та оператором перетворення, який перетворює об'єкт foo в char const *. Вираз
p = foo();
в основному слід побудувати об'єкт foo та застосувати оператор перетворення. Подальший висновок виводу
cout << p << '\n';
має відображати клас foo, але це не так. Він відображає функцію foo.
Цей дивовижний результат виникає тому, що програма включає в себе заголовок lib.h, показаний у Лістингу 2 . Цей заголовок визначає функцію, яка також називається foo. Ім'я функції foo приховує ім'я класу foo, тому посилання на foo в основному стосується функції, а не класу. main може посилатися на клас лише за допомогою розробленого специфікатора типу, як у
p = class foo();
Спосіб уникнення такої плутанини у всій програмі полягає в тому, щоб додати наступний typedef для імені класу foo:
typedef class foo foo;
безпосередньо перед або після визначення класу. Цей typedef викликає конфлікт між ім'ям типу foo та ім'ям функції foo (з бібліотеки), що спричинить помилку часу компіляції.
Я не знаю нікого, хто насправді пише ці typedefs як зрозуміло. Це вимагає великої дисципліни. Оскільки частота помилок, таких як одна в Лістингу 1 , мабуть, досить мала, багато хто ніколи не стикаються з цією проблемою. Але якщо помилка у вашому програмному забезпеченні може спричинити тілесні ушкодження, тоді ви повинні написати typedefs незалежно від того, наскільки це малоймовірно помилка.
Я не можу уявити, чому хто-небудь хотів би приховати ім’я класу з функцією або ім'ям об'єкта в тому ж обсязі, що і клас. Правила приховування в C були помилкою, і їх не слід було поширювати на класи в C ++. Дійсно, ви можете виправити помилку, але це вимагає додаткової дисципліни програмування та зусиль, які не повинні бути необхідними.
Listing 1
і Listing 2
посилання порушені. Гляньте.
Ще одна важлива відмінність: typedef
s не може бути оголошено вперед. Отже, для typedef
параметра ви повинні мати #include
файл, що містить typedef
, тобто все, що є #include
у вас, .h
також включає цей файл, незалежно від того, потрібен він чи ні, і так далі. Це однозначно може вплинути на час вашої збірки на більші проекти.
Без цього typedef
в деяких випадках ви можете просто додати попереднє оголошення struct Foo;
вгорі вашого .h
файлу та лише #include
визначення структури у вашому .cpp
файлі.
Там є різниця, але тонкий. Подивіться на це так: struct Foo
вводить новий тип. Другий створює псевдонім під назвою Foo (а не новий тип) для безіменного struct
типу.
7.1.3 Специфікатор typedef
1 [...]
Ім'я, оголошене за допомогою специфікатора typedef, стає іменем typedef. В межах декларації ім'я typedef є синтаксично еквівалентним ключовому слову та називає тип, пов'язаний з ідентифікатором, у спосіб, описаний у пункті 8. Ім'я typedef, таким чином, є синонімом іншого типу. Ім'я typedef не вводить новий тип так, як це робить оголошення класу (9.1) або enum.
8 Якщо декларація typedef визначає неназваний клас (або enum), перше ім'я typedef, оголошене декларацією, є типом класу (або enum type), що використовується для позначення типу класу (або типу enum) лише для цілей зв'язку ( 3.5). [Приклад:
typedef struct { } *ps, S; // S is the class name for linkage purposes
Отже, typedef завжди використовується як заповнювач / синонім іншого типу.
Ви не можете використовувати переадресацію із структурою typedef.
Сама структура анонімного типу, тому у вас немає власного імені для переадресації.
typedef struct{
int one;
int two;
}myStruct;
Попередня декларація, як ця робота:
struct myStruct; //forward declaration fails
void blah(myStruct* pStruct);
//error C2371: 'myStruct' : redefinition; different basic types
myStruct
живе в просторі імен тегів, а typedef_ed myStruct
живе в звичайному просторі імен, де живуть інші ідентифікатори, такі як ім'я функції, імена локальних змінних. Отже, конфлікту не повинно бути.
typedef
переадресація декларації з typedef
ім'ям ed, не посилається на безіменну структуру. Натомість пряма декларація оголошує неповну структуру з тегом myStruct
. Крім того, не бачачи визначення typedef
, прототип функції, що використовує typedef
ім'я ed, не є законним. Таким чином, ми повинні включати весь typedef, коли нам потрібно використовувати myStruct
для позначення типу. Виправте мене, якщо я неправильно зрозумів u. Дякую.
Важлива відмінність 'typedef structure' від 'struct' у C ++ полягає в тому, що ініціалізація вбудованих членів у структурах 'typedef' не буде працювати.
// 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
ініціалізується. Дивіться тест в Coliru online IDE (я ініціалізував його до 42, тому очевидно, ніж з нуля, що призначення дійсно відбулося).
У C ++ різниці немає, але я вважаю, що це дозволить вам оголосити екземпляри структури Foo, не роблячи явного:
struct Foo bar;