Domanda

Ho un paio di file di intestazione, che si riducono a:

tree.h:

#include "element.h"

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

ed element.h:

#include "tree.h"

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

Il problema è che entrambi fanno riferimento l'uno all'altro, quindi l'albero ha bisogno di elementi da includere e l'elemento deve essere incluso.

Questo non funziona perché per definire la struttura 'albero', la struttura dell'elemento deve essere già nota, ma per definire la struttura dell'elemento, la struttura ad albero deve essere conosciuta.

Come risolvere questi tipi di loop (penso che questo possa avere qualcosa a che fare con la "dichiarazione anticipata"?)

È stato utile?

Soluzione

Penso che il problema qui non sia la mancanza di protezione, ma il fatto che le due strutture hanno bisogno l'una dell'altra nella loro definizione. Quindi è un tipo che definisce il problema dell'hann e dell'uovo.

Il modo per risolverli in C o C ++ è fare dichiarazioni in avanti sul tipo. Se dici al compilatore che l'elemento è una struttura di qualche tipo, il compilatore è in grado di generare un puntatore ad esso.

per es.

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

In questo modo non è più necessario includere element.h all'interno di tree.h.

Dovresti anche includere include-guards nei file header.

Altri suggerimenti

L'osservazione cruciale qui è che l'elemento non ha bisogno di conoscere la struttura dell'albero, poiché contiene solo un puntatore ad esso. Lo stesso per l'albero. Tutto ciò che ognuno deve sapere è che esiste un tipo con il nome pertinente, non quello che contiene.

Quindi in tree.h, invece di:

#include "element.h"

fare:

typedef struct element_ element;

Questo " dichiara " i tipi "elemento" e " elemento element_ " (dice che esistono), ma non " definisce " loro (dì quello che sono). Tutto ciò che serve per memorizzare un pointer-to-blah è che blah sia dichiarato, non che sia definito. Solo se vuoi deferirlo (ad esempio per leggere i membri) hai bisogno della definizione. Codice nel tuo " .c " file deve farlo, ma in questo caso le intestazioni no.

Alcune persone creano un singolo file di intestazione che dichiara in avanti tutti i tipi in un cluster di intestazioni, quindi ogni intestazione lo include, invece di capire di quali tipi ha realmente bisogno. Non è né essenziale né completamente stupido.

Le risposte su include guards sono sbagliate: sono una buona idea in generale e dovresti leggere su di loro e prenderti un po ', ma non risolvono il tuo problema in particolare.

La risposta corretta è usare include guards e usare dichiarazioni forward.

Includi guardie

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

// Your code here

#endif
/* end foo.h */

Visual C ++ supporta anche #pragma una volta. È una direttiva preprocessore non standard. In cambio della portabilità del compilatore, si riduce la possibilità di collisioni del nome del preprocessore e si aumenta la leggibilità.

Dichiarazioni a termine

Forward dichiara le tue strutture. Se i membri di una struttura o di una classe non sono esplicitamente necessari, puoi dichiarare la loro esistenza all'inizio di un file di intestazione.

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

Leggi dichiarazioni a termine .

es.


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

Includi protezioni sono utili, ma non affrontano il problema del poster che è la dipendenza ricorsiva da due strutture di dati.

La soluzione qui è dichiarare l'albero e / o l'elemento come puntatori a strutture all'interno del file di intestazione, quindi non è necessario includere il .h

Qualcosa del tipo:

struct element_;
typedef struct element_ element;

Nella parte superiore di tree.h dovrebbe essere sufficiente per rimuovere la necessità di includere element.h

Con una dichiarazione parziale come questa puoi fare solo cose con puntatori di elementi che non richiedono al compilatore di sapere qualcosa sul layout.

IMHO il modo migliore è evitare tali loop perché sono un segno di accoppiamento fisico che dovrebbe essere evitato.

Ad esempio (per quanto mi ricordo) " Euromanistica di progettazione orientata agli oggetti " scopo di evitare le guardie di inclusione perché mascherano solo la dipendenza ciclica (fisica).

Un altro approccio è quello di predeclare le strutture in questo modo:

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

La dichiarazione a termine è il modo in cui puoi garantire che ci sarà un tipo di struttura che verrà definita in seguito.

Non mi piacciono le dichiarazioni a termine perché sono ridondanti e difettose. Se vuoi che tutte le tue dichiarazioni siano nello stesso posto, dovresti usare i file include e header con guardie include.

Dovresti pensare alle inclusioni come copia-incolla, quando il preprocesor c trova una riga #include posiziona semplicemente l'intero contenuto di myheader.h nella stessa posizione in cui è stata trovata la riga #include.

Bene, se scrivi include guards, il codice di myheader.h verrà incollato solo una volta in cui è stato trovato il primo #include.

Se il programma viene compilato con più file oggetto e il problema persiste, è necessario utilizzare le dichiarazioni in avanti tra i file oggetto (è come usare extern) per mantenere solo una dichiarazione di tipo su tutti i file oggetto (il compilatore mescola tutte le dichiarazioni nella stessa tabella e gli identificatori devono essere univoci).

Una soluzione semplice è semplicemente non avere file di intestazione separati. Dopotutto, se sono dipendenti l'uno dall'altro, non userete mai l'uno senza l'altro, quindi perché separarli? Puoi avere file .c separati che utilizzano entrambi la stessa intestazione ma offrono la funzionalità più mirata.

So che questo non risponde alla domanda su come usare correttamente tutte le cose fantasiose, ma l'ho trovato utile quando cercavo una soluzione rapida a un problema simile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top