Программирование ARM В чем разница между struct и typedef struct? Sat, March 25 2017  

Поделиться

нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

В чем разница между struct и typedef struct? Печать
Добавил(а) microsin   

Давайте попробуем разобраться, в чем разница между двумя определениями структур (далее перевод [1]):

struct Foo { ... };

и

typedef struct { ... } Foo;

[C++]

С точки зрения объявления переменных Foo на языке C++ нет разницы, как определить Foo. После того, как Foo была определена либо через struct, либо через typedef struct, можно объявлять переменные Foo так:

Foo bar;

Однако между struct и typedef struct различие есть, и оно тонкое. Дело в том, что struct определяет новый тип. В отличие от этого typedef struct никакого типа не определяет, он только создает ссылку (alias) с именем Foo (ни в коем случае не новый тип) на неименованный тип struct.

Спецификатор typedef. Имя, заданное с участием спецификатора typedef, становится специальным именем. В области действия этой декларации typedef-имя синтаксически эквивалентно ключевому слову и именам типа, связанного с идентификатором. Таким образом, typedef-name является синонимом другого типа. Так что typedef-имя НЕ СОЗДАЕТ НОВЫЙ ТИП, как это делается при декларации класса или enum.

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

typedef struct { } *ps, S; // S является именем класса для линковки

Итак, typedef ВСЕГДА используется как контейнер/синоним для другого типа.

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

typedef class string string;

Сожалению, это не точно соответствует действительности. Хотелось бы, чтобы все было так просто, но это не так. C++ не может генерировать такие typedef для struct, union или enum без введения несовместимости с языком C. Например, программа C декларирует и функцию, и структуру под одним и тем же именем status:

int status();
struct status;

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

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

typedef struct status status;

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

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

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

int status();
struct status;

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

К каким ошибкам это может привести в программе? Вот пример кода:

// Листинг 1.
#include < iostream >
#include "lib.h"
using namespace std;
class foo {
public:
    foo();
    operator char const *() const;
};
foo::foo() { }
foo::operator char const *() const { return "class"; }
 
int main()
{
    char const *p;
    p = foo();
    cout << p << '\n';
    return 0;
}

Здесь программа определяет класс foo с конструктором по умолчанию, и оператор конверсии преобразует объект foo в char const *. Выражение

p = foo();

в основном должно создать объект foo, и применить оператор конверсии. Следующий оператор вывода

cout << p << '\n';

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

Этот неожиданный результат произошел потому, что программа подключила заголовок lib.h:

//Листинг 2.
inline
char const *foo()
{
    return "function";
}

Этот заголовок определяет функцию, которая так же носит имя foo. Имя функции foo скрывает имя класса foo, так что обычное обращение к foo относится к функции, не к классу. Именно к классу можно обратиться только через elaborated-type-specifier:

p = class foo();

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

typedef class foo foo;

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

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

Невозможно представить себе, зачем могло понадобиться скрыть имя класса именем функции или объекта, когда они находятся в одной области видимости. Так что скрывающие правила языка C были ошибкой, и они не должны быть расширены на классы в C++. Конечно же, Вы можете найти и исправить ошибку, но это требует дополнительной дисциплины в программировании и усилий, которых можно было бы избежать.

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

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

Итак, в C++ все декларации struct/union/enum/class действуют точно так же как если бы они были неявно объявлены через typedef, пока имя не скрыто другой декларацией с таким же именем.

[C]

Таким образом, тонкие отличия есть только в C++, и это пережиток от языка C, где применение struct и typedef struct имеет значение. На языке C есть два разных пространств имен типов: пространство имен тегов struct/union/enum, и пространство имен typedef. Если Вы просто напишете в программе:

struct Foo { ... };
Foo x;

то получите ошибку компиляции, потому что Foo определен только в пространстве имен тегов. Вы должны декларировать переменную x так:

struct Foo x;

В любой момент, когда Вы хотите обратиться к Foo, Вы всегда должны вызывать struct Foo. Это быстро раздражает, тогда Вы можете добавить typedef:

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

Теперь оба выражения, и struct Foo (в пространстве имен тегов) и просто Foo (в пространстве имен typedef), относятся к одному и тому же, и Вы можете свободно декларировать объекты с типом Foo без ключевого слова struct.

Конструкция

typedef struct Foo { ... } Foo;

Является просто объединением декларации структуры и typedef.

И, наконец, конструкция

typedef struct { ... } Foo;

декларирует безымянную структуру, и создает для нее typedef. Так что для такой конструкции у Вас нет имени Foo в пространстве имен тегов, имя Foo есть только в пространстве имен typedef. Это означает, что предварительное декларирование невозможно. Если Вы хотите применить предварительное декларирование, то нужно задать имя для пространства имен тегов.

[Ссылки]

1. Difference between struct and typedef struct in C++? site:answerstop.org.
2. C++ Theory and Practice site:drdobbs.com.
3. Что такое Elaborated Type Specifier?
4. Что такое forward declaration?
5. typedef struct vs struct definitions site:stackoverflow.com.
6. Difference between 'struct' and 'typedef struct' in C++? site:stackoverflow.com

 

Добавить комментарий


Защитный код
Обновить

Top of Page