ricardo ricardo

H files including H files = cyclic dependency :  bad design or natural for complex/big  ones?

Discussion created by ricardo ricardo on Aug 25, 2009
Latest reply on Aug 26, 2009 by Tom Thompson

Hi people

I read a lot of docs before post it here, anf for my surprise this subject is extremely debated in some foruns and tutorials, either in C or C++ (specially) programming..(I use C++)

In big designs ( although you can do it in a small one, if you want ) sometimes you need to build a data structure based on another data structure.

Lets considere only 2 units: a.cpp / a.h and b.cpp / b.h.

Lets call them simply as A and B, for simple commodity...

// a.h

#ifndef A_H       // avoid 2+ including

#define A_H      // avoid 2+ including

struct a_t

{

   uint8 a,b,c;

};

#endif             // avoid 2+ including

 

// b.h

#ifndef B_H       // avoid 2+ including

#define B_H      // avoid 2+ including

#include a.h     // need to know about a_t

struct b_t

{

   a_t    my_a;   // type from  a.h !!

   uint8  c,d,e;

};

#endif             // avoid 2+ including

 

// a.cpp

#include a.h       // self include (C++ approach)

#include b.h      // to know about b_t

void main(void)

{

b_t  data;         // a instance of b_t;

    // using b_t type variable ....

   data.my_a.a=1;  

   data.c=2;  

}; 

Everything is ok: A depend on B, BUT not vice versa....easy...

 

Now imagine that A must create  a new struct based on b_t (declared in b.h )

Lets modify a.h to:

// a.h

#ifndef A_H       // avoid 2+ including

#define A_H      // avoid 2+ including

#include "b.h"   // to know about b_t type

struct a_t

{

   uint8 a,b,c;

};

struct a1   // NEW !!!

{

   b_t      my_b;   // from b.h !!!

   uint32  t;

};

#endif             // avoid 2+ including

 

Now start the confusion....

a.h wants to create a type based on a struct declared on b.h, so a.h must include b.h.

BUT, b.h was already including a.h, sine the beginning...

So we have a mutual depenedency, also called cyclical dependence...

I understood that the #ifdef XX_H  statements (called "compilation guard" ) will make the compilation skipp over one of these files, since that after the first compilation, its definition will occur, so when the next file compile, it will find a already defined guard condition, skipping on it...

 

Forward declaration:

After many readings, I learned there is a solution for mutual inclusion SINCE IF the depenedence is based ONLY in pointers / references.

To use a_t in a b.h WITHOUT include a.h into b.h , you can do:

 

struct a_t;   // forward declaration : like a prototype for functions...

struct b_t

{

   a_t  *my_a; // it is a pointer to the type, not a type it self !!!

 

BUT, not solve the case when you really need export structures between 2 files, when they depends each other.

 

I saw lot of rules been afirmed, but criticised late, and vice-versa.

The 2 polemic ones:

1- Only c (cpp) files can #include H files...

2- All #includes must be made into H files, so it can work alone...

Many tradeoffs were mentioned against the 2 rules above.

It seems there is not a "unique" true about it...

 

In my case, I´m in troubles due to cyclic depenedence.

I have a file xxx.h  that simply ignores a type that was defined into another H file wich WAS really included into xxx.h>

 

The trick I did, is have a globals.h where I put some structures that causes this cyclical depenedcies.

It works, but is ugly and not professional.

 

Well, this example I wrote is just to ilustrate about the "nature" of this issue.

There can be some sintax error, some wrong detail,  etc...BUT, in fact such cyclical condition exists and leads to a good headache in bigger design.

Some guys says it is a bad design, other says it is needed if the project gets complex / big.

My design has 82 filles...it´s not huuuggee, but enough big to reach such complexity.

 

Thanks in advance guys !!!

 

Ricardo Raupp

Outcomes