C Структура typedef с форвардной декларацией

Я использовал typedef для структур везде в моем приложении. Затем я начал рефакторинг в несколько файлов заголовков, когда он начал становиться неуклюжим. Я заметил, что мне нужно переслать объявление Object и Klass. К моему удивлению, я не мог переслать объявление Object или Klass. Это связано с тем, что, как вы видите в структуре Object и Klass, я использую typedef Object и Klass.

//Klass.h typedef struct Klass Klass_t; struct Klass { void (*_initialize)(Object_t* object, Klass_t* klass); }; //Object.h typedef struct Object Object_t; struct Object { Klass_t* _klass; }; 

Сначала использование typedef было отличным. Но попытка переслать объявление объекта:

 struct Object_t; 

Не работает, поскольку мне нужно будет переписать объявления функций как:

 void (*_initialize)(struct Object_t* object, Klass_t* klass); 

Поэтому я решил просто typedef Object внутри заголовочного файла Klass.h:

 typedef struct Object Object_t; 

Ну, когда все файлы заголовков включены в мой файл Main.c, он соглашается:

 Object.h:5: error: redefinition of typedef 'Object_t' 

Итак, я решил просто отбросить все структурные typedefs, а экспликация объявить мои структуры.

Есть ли способ напечатать структуру и переслать объявление в другом файле без явного использования struct Object?

Я хочу сохранить структуру typedefs внутри файла заголовка, где объявлена ​​структура. Если мне нужно сгруппировать все typedef внутри одного файла заголовка, я бы предпочел не использовать typedef вообще. В любом случае, спасибо за ваше время.

Помните, что typedef – это просто альтернативное имя для типа. Причина в том, что kernel ​​Linux не использует typedef для типов структуры, и вы это демонстрируете.

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

TL; DR

Несмотря на то, что kernel ​​Linux не использует typedef , я обычно использую typedef s, но в основном избегаю типов взаимной ссылочной структуры (я не могу придумать какой-либо код, где я использовал такой тип). И, как отмечает Йенс Густедт в своем ответе , я почти всегда использую обозначения:

 typedef struct SomeTag SomeTag; 

так что имя типа и тег структуры одинаковы (они находятся в разных пространствах имен). Эта операция не требуется в C ++; когда вы определяете struct SomeTag или class SomeTag , имя SomeTag становится именем типа без необходимости явного typedef (хотя typedef не наносит вреда, кроме выявления того, что автор более опытен в C, чем C ++, или код, созданный как C код).

Я также отмечаю, что имена, начинающиеся с подчеркивания, лучше всего рассматривать как «зарезервированные для реализации». Правила немного сложнее, но вы рискуете своей реализацией, узурпируя свои имена, и в рамках своих прав на узурпацию ваших имен, когда вы используете имена, начинающиеся с подчеркивания, так что не делайте этого. Аналогично, POSIX резервирует имена типов, заканчивающиеся на _t для реализации, если вы включаете заголовки POSIX (например, когда вы не компилируете в строгом режиме только Standard C). Избегайте создания таких имен; они рано или поздно причинят вам боль. (Я не исправил код ниже, чтобы разобраться с любой из этих проблем: caveat emptor !).

В приведенных ниже fragmentах кода я в основном игнорирую код, который предотвращает множественные включения (но вы должны иметь его в своем коде).

Дополнительный заголовок

typedefs.h:

 typedef struct Object Object_t; typedef struct Klass Klass_t; 

klass.h

 #include "typedefs.h" struct Klass { void (*_initialize)(Object_t *object, Klass_t *klass); }; 

object.h

 #include "typedefs.h" struct Object { Klass_t *_klass; }; 

Это работает, потому что два типа имен Klass_t и Object_t объявляются до их использования.

Использовать struct Object в прототипе

klass.h

 typedef struct Klass Klass_t; struct Object; struct Klass { void (*_initialize)(struct Object *object, Klass_t *klass); }; 

Или, для согласованности, он может даже использовать:

  void (*_initialize)(struct Object *object, struct Klass *klass); 

object.h

 #include "klass.h" struct Object { Klass_t *_klass; }; 

Это работает потому, что (в широких пределах – в основном, если типы определены в области файлов, а не внутри функции) struct Object всегда относится к одному типу, независимо от того, все ли детали полностью определены.

GCC 4.8.2

При всех -std=c89 , -std=c99 и -std=c11 , GCC 4.8.2 принимает реплицированные typedef s, как в приведенном ниже коде. Для этого требуется -std=c89 -pedantic или -std=c99 -pedantic чтобы получить ошибки в повторяющихся typedef .

Даже без -pedantic варианта GCC 4.5.2 отклоняет этот код; однако GCC 4.6.0 и более поздние версии принимают его без опции -pedantic .

klass.h

 #ifndef KLASS_H_INCLUDED #define KLASS_H_INCLUDED typedef struct Klass Klass_t; typedef struct Object Object_t; struct Klass { void (*_initialize)(Object_t *object, Klass_t *klass); }; #endif /* KLASS_H_INCLUDED */ 

object.h

 #ifndef OBJECT_H_INCLUDED #define OBJECT_H_INCLUDED typedef struct Klass Klass_t; typedef struct Object Object_t; struct Object { Klass_t *klass; }; #endif /* OBJECT_H_INCLUDED */ 

consumer.c

 #include "klass.h" #include "object.h" Klass_t k; Object_t o; 

Вам придется решить, будет ли это риск для вашего кода – насколько важна переносимость и какие версии C (и какие компиляторы C) должны быть переносимыми.

Вам не хватает ключевого слова struct . Так должно быть

 typedef struct Object Object_t; 

В этом случае всегда должна быть предусмотрена форвардная декларация (но см. Ниже). Этот форвард объявляет идентификатор typedef и тег struct одновременно.

Просто поставьте такие форвардные объявления всей своей struct перед реальными объявлениями. Пока вы используете указатели на эту struct внутри деклараций, все должно быть хорошо.

nitpick: имена с _t зарезервированы POSIX. Это означает, что вам следует избегать этого, потому что когда-нибудь на какой-то платформе может существовать Object_t который предопределен и будет конфликтовать с вашим типом.

Я лично предпочитаю следующее соглашение

 typedef struct Object Object; 

поэтому слово Object, с или без struct , всегда относится к одному и тому же.