Разница между «struct» и «typedef struct» в C ++?

Ответы:

1202

В 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;

В любое время, когда вы хотите сослаться на a Foo, вы всегда должны называть это a 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« под ред, пока имя не спрятался другой декларации с тем же именем. См . Ответ Майкла Берра для полной информации.

Адам Розенфилд
источник
53
В то время как то, что вы говорите, верно, AFAIK, утверждение, 'typedef struct {...} Foo;' создает псевдоним для неназванной структуры.
Dirkgently
25
Хороший улов, есть небольшая разница между "typedef struct Foo {...} Foo;" и "typedef struct {...} Foo;".
Адам Розенфилд,
8
В C теги struct, теги union и теги перечисления совместно используют одно пространство имен, а не (struct и union) используют два, как заявлено выше; пространство имен, на которое ссылаются typedef, действительно является отдельным. Это означает, что вы не можете иметь оба 'union x {...};' и 'struct x {...};' в одном объеме.
Джонатан Леффлер
10
Помимо не совсем типового определения, еще одно различие между двумя фрагментами кода в вопросе заключается в том, что Foo может определить конструктор в первом примере, но не во втором (поскольку анонимные классы не могут определять конструкторы или деструкторы) ,
Стив Джессоп
1
@Lazer: Там являются тонкими различиями, но Адам означает (как он продолжает говорить) , что вы можете использовать «Type вар» объявлять переменные без ЬурейеГо.
Фред Нурк
226

В этой статье о DDJ Дэн Сакс объясняет одну маленькую область, в которой могут появляться ошибки, если вы не наберете определение своих структур (и классов!):

Если вы хотите, вы можете представить, что C ++ генерирует typedef для каждого имени тега, например

typedef class string string;

К сожалению, это не совсем точно. Хотелось бы, чтобы все было так просто, но это не так. C ++ не может генерировать такие определения типов для структур, объединений или перечислений, не вводя несовместимости с C.

Например, предположим, что программа на C объявляет как функцию, так и структуру с именем status:

int status(); struct status;

Опять же, это может быть плохой практикой, но это C. В этой программе статус (сам по себе) относится к функции; Структура состояния относится к типу.

Если C ++ автоматически сгенерировал typedef для тегов, то, когда вы скомпилировали эту программу как C ++, компилятор сгенерирует:

typedef struct status status;

К сожалению, это имя типа будет конфликтовать с именем функции, и программа не будет компилироваться. Вот почему C ++ не может просто сгенерировать typedef для каждого тега.

В C ++ теги действуют так же, как имена typedef, за исключением того, что программа может объявлять объект, функцию или перечислитель с тем же именем и той же областью действия, что и тег. В этом случае имя объекта, функции или перечислителя скрывает имя тега. Программа может ссылаться на имя тега только с помощью ключевого слова class, struct, union или enum (в зависимости от ситуации) перед именем тега. Имя типа, состоящее из одного из этих ключевых слов, за которым следует тег, является подробным спецификатором типа. Например, struct status и enum month являются уточненными спецификаторами типов.

Таким образом, программа на C, которая содержит оба:

int status(); struct status;

ведет себя так же, когда компилируется как C ++. Только название статуса относится к функции. Программа может ссылаться на тип, только используя структуру развитого спецификатора типа.

Так как же это позволяет ошибкам проникать в программы? Рассмотрим программу в листинге 1 . Эта программа определяет класс foo с помощью конструктора по умолчанию и оператора преобразования, который преобразует объект foo в char const *. Выражение

p = foo();

В main следует создать объект foo и применить оператор преобразования. Последующий выходной оператор

cout << p << '\n';

должен отображать класс foo, но это не так. Отображает функцию foo.

Этот неожиданный результат происходит потому, что программа включает заголовок lib.h, показанный в листинге 2 . Этот заголовок определяет функцию также с именем foo. Имя функции foo скрывает имя класса foo, поэтому ссылка на foo в main ссылается на функцию, а не на класс. main может ссылаться на класс только с помощью разработанного спецификатора типа, как в

p = class foo();

Чтобы избежать путаницы во всей программе, добавьте следующий typedef для имени класса foo:

typedef class foo foo;

непосредственно перед или после определения класса. Этот typedef вызывает конфликт между именем типа foo и именем функции foo (из библиотеки), что приведет к ошибке времени компиляции.

Я не знаю никого, кто бы действительно писал эти typedef как само собой разумеющееся. Это требует много дисциплины. Поскольку количество ошибок, таких как в листинге 1 , вероятно, довольно мало, многие из вас никогда не сталкиваются с этой проблемой. Но если ошибка в вашем программном обеспечении может привести к травмам, вы должны написать typedefs независимо от того, насколько вероятна ошибка.

Я не могу представить, почему кто-то захочет скрыть имя класса с именем функции или объекта в той же области, что и класс. Правила сокрытия в C были ошибкой, и их не следовало распространять на классы в C ++. Действительно, вы можете исправить ошибку, но это требует дополнительной дисциплины программирования и усилий, которые не должны быть необходимыми.

Майкл Берр
источник
11
Если вы попробуете "class foo ()", и он потерпит неудачу: в ISO C ++ "class foo ()" является недопустимой конструкцией (кажется, статья была написана в '97, до стандартизации). Вы можете поставить "typedef class foo foo;" в main, тогда вы можете сказать "foo ();" (потому что тогда typedef-name лексически ближе, чем имя функции). Синтаксически, в T (), T должен быть указателем простого типа. подробные спецификаторы типов не допускаются. Тем не менее, это хороший ответ, конечно.
Йоханнес Шауб -
Listing 1и Listing 2ссылки не работают. Посмотри.
Prasoon Saurav
3
Если вы используете разные соглашения об именах для классов и функций, вы также избежите конфликта имен без необходимости добавлять дополнительные определения типов.
zstewart
64

Еще одно важное отличие: typedefs не может быть объявлено заранее. Таким образом, для этой typedefопции вы должны указать #includeфайл, содержащий typedefвсе, что у #includeвас есть, .hи этот файл, независимо от того, нужен он ему напрямую или нет, и так далее. Это может определенно повлиять на время сборки больших проектов.

Без typedef, в некоторых случаях вы можете просто добавить предварительное объявление struct Foo;в верхней части вашего .hфайла, и только #includeопределение структуры в вашем .cppфайле.

Джо
источник
Почему раскрытие определения структуры влияет на время сборки? Проводит ли компилятор дополнительную проверку, даже если в этом нет необходимости (учитывая опцию typedef, чтобы компилятор знал определение), когда он видит что-то вроде Foo * nextFoo; ?
Рич
3
Это на самом деле не дополнительная проверка, это просто больше кода, с которым должен иметь дело компилятор. Для каждого файла cpp, который встречает этот typedef где-то в его цепочке включений, он компилирует typedef. В более крупных проектах файл .h, содержащий typedef, может легко скомпилироваться сотни раз, хотя предварительно скомпилированные заголовки очень помогают. Если вы можете обойтись без использования предварительного объявления, тогда проще ограничить включение .h, содержащего полную спецификацию структуры, только кодом, который действительно заботится, и, следовательно, соответствующий включаемый файл компилируется реже.
Джо
пожалуйста @ предыдущий комментатор (кроме владельца поста). Я почти пропустил твой ответ. Но спасибо за информацию.
Rich
Это уже не так в C11, где вы можете несколько раз печатать одну и ту же структуру с одним и тем же именем.
Майклмейер
32

Там является разница, но тонкий. Посмотрите на это так: struct Fooвводит новый тип. Второй создает псевдоним Foo (а не новый тип) для безымянного structтипа.

7.1.3 Спецификатор typedef

1 [...]

Имя, объявленное с помощью спецификатора typedef, становится typedef-name. В рамках своей декларации typedef-name синтаксически эквивалентно ключевому слову и называет тип, связанный с идентификатором, способом, описанным в разделе 8. Таким образом, typedef-name является синонимом для другого типа. Typedef-name не вводит новый тип, как объявление класса (9.1) или enum.

8 Если объявление typedef определяет безымянный класс (или enum), первое имя typedef-типа, объявленное объявлением как этот тип класса (или тип enum), используется для обозначения типа класса (или типа enum) только для целей связывания ( 3,5). [ Пример:

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

Таким образом, typedef всегда используется как заполнитель / синоним для другого типа.

dirkgently
источник
10

Вы не можете использовать предварительное объявление со структурой 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 или предварительное объявление), myStruct обозначает тип struct, верно?
Rich
@Rich Жалуется на конфликт имен. Есть предварительное объявление, говорящее «ищите структуру с именем myStruct», а затем есть typedef, который переименовывает безымянную структуру в «myStruct».
Йохай Тиммер
Вы имеете в виду поместить и typedef, и forward forward в один и тот же файл? Я сделал, и GCC скомпилировал это нормально. myStruct правильно интерпретируется как безымянная структура. Тег myStructживет в пространстве имен тегов, а typedef_ed myStructживет в обычном пространстве имен, в котором живут другие идентификаторы, такие как имя функции, имена локальных переменных. Поэтому не должно быть никакого конфликта. Я могу показать вам мой код, если вы сомневаетесь, есть ли в нем какая-либо ошибка.
Rich
@Rich GCC выдает ту же ошибку, текст немного меняется: gcc.godbolt.org/…
Йохай Тиммер
Я думаю, что понимаю, что когда у вас есть только предварительное typedefобъявление с typedefименем ed, это не относится к безымянной структуре. Вместо этого предварительное объявление объявляет неполную структуру с тегом myStruct. Также, не видя определения typedef, прототип функции, использующий typedefимя ed, является недопустимым. Таким образом, мы должны включать весь typedef всякий раз, когда нам нужно использовать myStructдля обозначения типа. Поправь меня, если я тебя неправильно понял. Спасибо.
Rich
0

Важное различие между «typedef struct» и «struct» в C ++ заключается в том, что встроенная инициализация члена в «typedef structs» не будет работать.

// 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; };
user2796283
источник
3
Не правда. В обоих случаях xинициализируется. См. Тест в онлайн-среде Coliru (я инициализировал его как 42, так что это более очевидно, чем с нулем, что назначение действительно имело место).
Колин Д. Беннетт
На самом деле я тестировал его в Visual Studio 2013, и он не был инициализирован. Это была проблема, с которой мы столкнулись в производственном коде. Все компиляторы разные и должны соответствовать только определенным критериям.
user2796283
-3

В C ++ нет никакой разницы, но я верю, что C позволит вам объявлять экземпляры структуры Foo без явного выполнения:

struct Foo bar;
сянь
источник
3
Посмотрите на ответ @ dirkgently - разница есть, но она неуловима.
Кит Пинсон
-3

Структура заключается в создании типа данных. Typedef - установить псевдоним для типа данных.

Дэвид Ту
источник
12
Вы не поняли вопрос.
зима