Pregunta

Tengo un par de archivos de encabezado, que se reducen a:

tree.h:

#include "element.h"

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

y element.h:

#include "tree.h"

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

El problema es que ambos se hacen referencia entre sí, por lo que el árbol necesita que se incluya el elemento y el elemento necesita que se incluya el árbol.

Esto no funciona porque para definir la estructura del 'árbol', la estructura del elemento ya debe ser conocida, pero para definir la estructura del elemento, la estructura del árbol debe ser conocida.

¿Cómo resolver estos tipos de bucles (creo que esto puede tener algo que ver con la 'declaración de reenvío'?)?

¿Fue útil?

Solución

Creo que el problema aquí no es que falte la guardia, sino el hecho de que las dos estructuras se necesitan mutuamente en su definición. Así que es un tipo que define el problema de hann y egg.

La forma de resolverlos en C o C ++ es hacer declaraciones hacia adelante sobre el tipo. Si le dices al compilador que el elemento es una estructura de algún tipo, el compilador puede generar un puntero a él.

Por ejemplo,

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

De esa manera ya no tienes que incluir element.h dentro de tree.h.

También deberías incluir los protectores de inclusión alrededor de tus archivos de encabezado.

Otros consejos

La observación crucial aquí es que el elemento no necesita conocer la estructura del árbol, ya que solo tiene un puntero hacia él. Lo mismo para el árbol. Todo lo que cada uno debe saber es que existe un tipo con el nombre relevante, no lo que contiene.

Así que en tree.h, en lugar de:

#include "element.h"

hacer:

typedef struct element_ element;

Esto " declara " los tipos " elemento " y " struct element_ " (dice que existen), pero no " define " ellos (decir lo que son). Todo lo que necesita para almacenar un puntero a bla es que bla se declara, no se define. Solo si quiere deferencia (por ejemplo, para leer los miembros) necesita la definición. Codifique en su " .c " El archivo debe hacer eso, pero en este caso sus encabezados no.

Algunas personas crean un solo archivo de encabezado que declara de forma progresiva todos los tipos en un grupo de encabezados, y luego cada encabezado incluye eso, en lugar de determinar qué tipos realmente necesita. Eso no es esencial ni completamente estúpido.

Las respuestas sobre incluir guardias están equivocadas. Son una buena idea en general, y debes leer sobre ellas y obtener algunas, pero no resuelven tu problema en particular.

La respuesta correcta es usar incluir guardas y usar declaraciones a plazo.

Incluir guardias

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

// Your code here

#endif
/* end foo.h */

Visual C ++ también admite #pragma una vez. Es una directiva de preprocesador no estándar. A cambio de la portabilidad del compilador, reduce la posibilidad de colisiones de nombres del preprocesador y aumenta la legibilidad.

Declaraciones hacia adelante

Adelante declara tus estructuras. Si los miembros de una estructura o clase no son necesarios explícitamente, puede declarar su existencia al comienzo de un archivo de encabezado.

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

Lea sobre declaraciones de reenvío .

es decir.


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

Incluir guardias es útil, pero no aborda el problema del póster, que es la dependencia recursiva de dos estructuras de datos.

La solución aquí es declarar el árbol y / o el elemento como punteros a las estructuras dentro del archivo de encabezado, por lo que no es necesario incluir el .h

Algo como:

struct element_;
typedef struct element_ element;

En la parte superior de tree.h debería ser suficiente para eliminar la necesidad de incluir element.h

Con una declaración parcial como esta, solo puedes hacer cosas con punteros a elementos que no requieren que el compilador sepa nada sobre el diseño.

En mi humilde opinión, la mejor manera es evitar dichos bucles porque son una señal de golpeo físico que deben evitarse.

Por ejemplo (por lo que recuerdo) " Heurística de diseño orientado a objetos " propósito de evitar Incluir Guardias porque solo enmascaran la dependencia (física) cíclica.

Otro enfoque es predecir las estructuras como esta:

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

Declaración de reenvío es la forma con la que puede garantizar que habrá un tipo de estructura que se definirá más adelante.

No me gustan las declaraciones de reenvío porque son redundantes y con errores. Si desea que todas sus declaraciones estén en el mismo lugar, debe usar los archivos de cabecera y de inclusión con guardas de inclusión.

Debería pensar en incluir como copia-pegar, cuando el preprocesor c encuentra una línea #include simplemente coloca el contenido completo de myheader.h en la misma ubicación donde se encontró la línea #include.

Bueno, si escribes incluir guardas, el código de myheader.h se pegará solo una vez donde se encontró el primer #include.

Si su programa se compila con varios archivos de objetos y el problema persiste, debe usar declaraciones hacia adelante entre archivos de objetos (es como usar extern) para mantener solo una declaración de tipo para todos los archivos de objetos (el compilador mezcla todas las declaraciones en la misma tabla y los identificadores deben ser únicos).

Una solución simple es simplemente no tener archivos de encabezado separados. Después de todo, si son dependientes el uno del otro, nunca usarán uno sin el otro, así que ¿por qué separarlos? Puede tener archivos .c separados que usan el mismo encabezado pero proporcionan una funcionalidad más enfocada.

Sé que esto no responde a la pregunta de cómo usar todas las cosas de lujo correctamente, pero me pareció útil cuando buscaba una solución rápida para un problema similar.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top