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

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

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

72,883 Views
Ricardo_RauppV
Contributor I

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

Labels (1)
Tags (1)
0 Kudos
Reply
3 Replies

708 Views
Ricardo_RauppV
Contributor I

Hello guys

Thanks for your attention to help me...

If you know about some material regarding these good practices wich help to avoid such dependecies, please let me know....

 

Ricardo Raupp

 

0 Kudos
Reply

708 Views
jbezem
Contributor III

If a_t and b_t are so dependent on one another, it's a sign of bad design (or a code smell, if you will). I'd define a type a_and_b_t combining the two, including their functionality, as a replacement for both.

If then your app is merged into one big type (because all types are interdependent), go back and re-do yourSW-architecture.

Working with pointers/references if you can is a solution, but also a design change, improving the design, at least a little.

Of course, this is just my opinion...

 

HTH,

 

Johan

0 Kudos
Reply

708 Views
Lundin
Senior Contributor IV
I agree, if you have such dependencies in your program, it is poorly designed, showing that the programmer has not grasped the concept of object orientation. The best way to modify it would be to either merge the two modules and write a new one, or as the previous poster suggested, introduce a new module containing them both.

0 Kudos
Reply