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

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

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

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

Ответ 1

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

Способ решения этих проблем на 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.

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

Ответ 2

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

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

#include "element.h"

делать:

typedef struct element_ element;

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

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

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

Ответ 3

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

Включить защиту

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

// Your code here

#endif
/* end foo.h */

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

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

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

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

Ответ 4

Читайте передовые объявления.

т.


// 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

Ответ 6

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

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

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

struct element_;
typedef struct element_ element;

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

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

Ответ 7

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

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

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


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

Ответ 8

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

Ответ 9

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

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

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

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

Ответ 10

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

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