Циклы заголовка C

У меня есть несколько файлов заголовков, которые сводятся к:

tree.h:

#include "element.h" typedef struct tree_ { struct *tree_ first_child; struct *tree_ next_sibling; int tag; element *obj; .... } tree; 

и element.h:

 #include "tree.h" typedef struct element_ { tree *tree_parent; char *name; ... } element; 

Проблема в том, что они оба ссылаются друг на друга, поэтому элемент дерева должен быть включен, а элемент нуждается в дереве для включения.

Это не работает, потому что для определения структуры «дерева» структура элемента должна быть уже известна, но для определения структуры элемента должна быть известна древовидная структура.

Как разрешить эти типы циклов (я думаю, что это может иметь какое-то отношение к «форвардной декларации»?)?

    Я думаю, проблема здесь не в недостатке, а в том, что две структуры нуждаются друг в друге в своем определении. Так что это определение типа hann и egg.

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

    Например

    Внутри tree.h:

     // tell the compiler that element is a structure typedef: typedef struct element_ element; typedef struct tree_ tree; struct tree_ { tree *first_child; tree *next_sibling; int tag; // now you can declare pointers to the structure. element *obj; }; 

    Таким образом, вам больше не нужно включать element.h внутри tree.h.

    Вы также должны включить защитные элементы вокруг ваших заголовочных файлов.

    Важное замечание здесь состоит в том, что элемент не должен знать структуру дерева, поскольку он содержит только указатель на него. То же самое для дерева. Все, что нужно знать, это то, что существует тип с соответствующим именем, а не то, что в нем.

    Итак, в tree.h вместо:

     #include "element.h" 

    делать:

     typedef struct element_ element; 

    Это «объявляет» типы «element» и «struct element_» (говорит, что они существуют), но не «определяет» их (скажите, что они есть). Все, что вам нужно для хранения указателя на blah, – это то, что объявлен blah, а не то, что он определен. Только если вы хотите его уважать (например, для чтения членов), вам нужно определение. Код в вашем «.c» файле должен сделать это, но в этом случае ваши заголовки этого не делают.

    Некоторые люди создают один заголовочный файл, который пересылает все типы в кластере заголовков, а затем каждый заголовок включает в себя это вместо того, чтобы определять, какие типы ему действительно нужны. Это не важно и не совсем глупо.

    Ответы о том, как охранники ошибаются, – это хорошая идея в целом, и вы должны прочитать о них и получить некоторые из них, но они не решают вашу проблему в частности.

    Правильный ответ заключается в том, чтобы использовать включенные охранники и использовать форвардные объявления.

    Включить охранников

     /* begin foo.h */ #ifndef _FOO_H #define _FOO_H // Your code here #endif /* end foo.h */ 

    Visual C ++ также поддерживает #pragma один раз. Это нестандартная директива препроцессора. В обмен на мобильность компилятора вы уменьшаете вероятность конфликтов имен препроцессора и повышаете удобочитаемость.

    Передовые декларации

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

     struct tree; /* element.h */ struct element; /* tree.h */ 

    Читайте о форвардных декларациях .

    то есть.

     // tree.h: #ifndef TREE_H #define TREE_H struct element; struct tree { struct element *obj; .... }; #endif // element.h: #ifndef ELEMENT_H #define ELEMENT_H struct tree; struct element { struct tree *tree_parent; ... }; #endif 

    Они известны как «одноразовые заголовки». См. http://developer.apple.com/DOCUMENTATION/DeveloperTools/gcc-4.0.1/cpp/Once_002dOnly-Headers.html#Once_002dOnly-Headers

    Включить охранники полезны, но не адресуйте проблему плаката, которая является рекурсивной зависимостью от двух структур данных.

    Решением здесь является объявление дерева и / или элемента в качестве указателей на структуры внутри файла заголовка, поэтому вам не нужно включать .h

    Что-то вроде:

     struct element_; typedef struct element_ element; 

    В верхней части tree.h должно быть достаточно, чтобы удалить необходимость включить element.h

    С частичным объявлением, как это, вы можете делать только с указателями элементов, которые не требуют от компилятора ничего знать о макете.

    ИМХО лучший способ – избегать таких циклов, потому что они являются признаком физического перемещения, которого следует избегать.

    Например (насколько я помню) «Объектно-ориентированная эвристика дизайна», чтобы избежать включения защиты, поскольку они только маскируют циклическую (физическую) зависимость.

    Другой подход состоит в том, чтобы предусмотреть такие структуры:

     element.h:
     struct tree_;
     struct element_
       {
         struct tree_ * tree_parent;
         название символа;
       }; 

    tree.h:
    struct element_;
    struct tree_
    {
    struct tree_ * first_child;
    struct tree_ * next_sibling;
    int tag;
    struct element_ * obj;
    };

    Forward declaratio – это способ, которым вы можете гарантировать, что будет существовать структура, которая будет определена позже.

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

    Вы должны думать о включении в качестве копии-вставки, когда препроцессор c находит строку #include, просто помещает все содержимое myheader.h в том же месте, где была найдена строка #include.

    Ну, если вы напишете включить охранников, код myheader.h будет вставлен только один раз, когда будет найден первый #include.

    Если ваша программа компилируется с несколькими объектными файлами и проблема сохраняется, вы должны использовать форвардные объявления между объектными файлами (это похоже на использование extern), чтобы сохранить только объявление типа во всех объектных файлах (компилятор смешивает все объявления в одной таблице, а идентификаторы должны быть уникальный).

    Простым решением является просто отсутствие отдельных файлов заголовков. В конце концов, если они зависят друг от друга, вы никогда не будете использовать один без другого, так зачем их разделять? У вас могут быть отдельные файлы .c, которые используют один и тот же заголовок, но обеспечивают более сфокусированную функциональность.

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