Циклы файла заголовка 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;

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

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

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

10 ответов

Решение

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

Чтобы решить их в 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.

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

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

Так что в tree.h вместо:

#include "element.h"

делать:

typedef struct element_ element;

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

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

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

Правильный ответ - использовать include guard и использовать предварительные декларации.

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

/* begin foo.h */
#ifndef _FOO_H
#define _FOO_H

// Your code here

#endif
/* end foo.h */

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

Форвардные декларации

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

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

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

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

Что ж, если вы напишите include guards, код myheader.h будет вставлен только один раз, когда был найден первый #include.

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

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

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

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

struct element_;
typedef struct element_ element;

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

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

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

/* a.h - dependency of b.h */
#ifndef _A_H
#define _A_H

#include "b.h"

typedef struct a_p {
    b_t *b;
} a_t;

#endif // _A_H
/* b.h - dependency of a.h */
#ifndef _B_H
#define _B_H

typedef struct b_p b_t;

/** 
 * !!!
 * to avoid recursion, only include "a.h" 
 * when "a.h" isn't included before
 */
#ifndef _A_H
    #include "a.h"
    typedef struct b_p {
        a_t a;
    } b_t;
#endif

#endif // _B_H

Чтобы использовать оба файла заголовков, вам нужно только include один, то есть тот, который также безусловно включает в себя другой (в данном случае a.h). Но если вы хотите, вы также можете включить "bh". Но в любом случае это не будет иметь никакого значения (из-за форвардного объявления).

#include "a.h"

int main() {
    a_t aigh;

    return 0;
}

Вуаля! Это оно! Без лишних includeнет ничего. Мы получили их!

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

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

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

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

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

Другой подход заключается в том, чтобы заранее объявить структуры, подобные этой:

element.h:
struct tree_;
struct element_
  {
    struct tree_ *tree_parent;
    char *name;
  };

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

Другие вопросы по тегам