Вопрос

У меня есть пара заголовочных файлов, которые сводятся к следующему:

дерево.ч:

#include "element.h"

typedef struct tree_
{
    struct *tree_ first_child;
    struct *tree_ next_sibling;
    int tag;
    element *obj;
    ....
} tree;

и элемент.h:

#include "tree.h"

typedef struct element_
{
    tree *tree_parent;
    char *name;
    ...
} element;

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

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

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

Это было полезно?

Решение

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

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

Например.

Внутри дерева.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 Once.Это нестандартная директива препроцессора.В обмен на переносимость компилятора вы уменьшаете вероятность конфликтов имен препроцессоров и повышаете читаемость.

Форвардные заявления

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

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;
    char *name;
  };

дерево.ч:структура element_;struct tree_ {struct tree_* first_child;структура Tree_* next_sibling;int-тег;структура element_ *obj;};

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top