Циклы файла заголовка C - PullRequest
       64

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

10 голосов
/ 29 сентября 2008

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

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 ]

27 голосов
/ 29 сентября 2008

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

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

* 1005 Е.Г. *

Внутри 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-guard вокруг ваших заголовочных файлов.

6 голосов
/ 29 сентября 2008

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

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

#include "element.h"

сделать:

typedef struct element_ element;

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

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

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

4 голосов
/ 29 сентября 2008

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

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

/* 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    */
2 голосов
/ 29 сентября 2008

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

т.


// 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
0 голосов
/ 03 апреля 2017

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

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

0 голосов
/ 13 ноября 2010

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

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

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

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

0 голосов
/ 06 октября 2009

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

0 голосов
/ 29 сентября 2008

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

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

Другой подход заключается в том, чтобы заранее объявить структуры, подобные этой: element.h: struct tree_; struct element_ { struct tree_ *tree_parent; char *name; };</p> <p>tree.h: struct element_; struct tree_ { struct tree_* first_child; struct tree_* next_sibling; int tag; struct element_ *obj; };

0 голосов
/ 29 сентября 2008

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

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

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

struct element_;
typedef struct element_ element;

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

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

0 голосов
/ 29 сентября 2008

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

...