0

In C, when I needed this :

File a.h:

#ifndef A_H
#define A_H

#include "b.h"

typedef struct A { B* second_struct; } A;

#endif /* !A_H */

File b.h:

#ifndef B_H
#define B_H

#include "a.h"

typedef struct B { A* first_struct; } B;

#endif /* !B_H */

I took the liberty to rearrange this in this way:

typedef struct A A;

#ifndef A_H
...

(And the same goes for b.h.)

I did exactly the same thing in C++ with class A; in the firsts lines of my headers.


However, someone told me it was a bad practice, in C, since typedef should create another type and may conflicts with the previous same typedef, in case of multiple inclusion.

He also told me not a single declaration should be outside of the header guard, for those reasons.

So, he advised me to put a typedef struct A A; in b.h right before the declaration of my struct B (and vice-versa), since it's where I need it.

My question is:

In this case, isn't it dangerous to have a typedef .. A; lost in b.h?

More generally, what's the best practice to deal with this kind of dependancies?

Luka
  • 83
  • 1
  • 9
  • 2
    Cyclic dependencies are typically a design flaw. If you can't remove them, it can be helpful to split in two header files, one with forward declarations and one with the struct and class declarations. If you "only" have to forward a single type which will never change you maybe can put it into the header which needs it. But that scatter the information's which is also bad practice. – Klaus Dec 02 '17 at 10:50
  • Possible duplicate of [Resolve build errors due to circular dependency amongst classes](https://stackoverflow.com/questions/625799/resolve-build-errors-due-to-circular-dependency-amongst-classes) – Klaus Dec 02 '17 at 10:52
  • When dealing with trees where nodes are polymorphic objects (e.g. ASTs) those kind of cyclic dependencies are often unavoidable. – Richard Dec 02 '17 at 11:08
  • @Klaus I'm not sure if I really understand your first approach. In this case, this will be a `global.h` (or whatever) forwarding declarations, then including `a.h` and `b.h`? It's sounds good to group types by category, maybe. – Luka Dec 02 '17 at 11:09
  • @Klaus I edited the title according to my intention, since I don't have my answers in this possible duplicate. Is that better like that? – Luka Dec 02 '17 at 11:15
  • The linked duplicate shows exact your case. I don't catch the difference you mention... – Klaus Dec 02 '17 at 11:22
  • I'd like to understand why some practices are preferred over others, but the other post just seems to ask about how compile. – Luka Dec 02 '17 at 11:28

1 Answers1

0

The best practice to deal with those kind of dependencies generally is to avoid them. That's not always possible though.

I'd do it this way:

a.h

#ifndef A_H
#define A_H

#include "b.h"

// Forward decls
struct B;    

typedef struct A { struct B* second_struct; } A;

#endif /* !A_H */

b.h

#ifndef B_H
#define B_H

#include "a.h"

// Forward decls
struct A;

typedef struct B { struct A* first_struct; } B;

#endif /* !B_H */

which is valid in both C and C++.

Forward declarations should be kept as simple as possible so typedefs in them should be avoided which is simple in this case. It only means that you have to use struct A* instead of A* in your definition of B.

Regarding typedefs outside of the include guards: It's definitely not standard practice.

It may sound tempting at first that #include "a.h" ensures that a forward declaration of struct A is present. Problem is that it's not obvious anymore that struct A may be an incomplete type.

Forward declarations are also a warning to a future reader that they may be dealing with an incomplete type.

Richard
  • 1,117
  • 11
  • 31
  • I understand for typedefs, and from now I'll better use structs than typedefs in struct declarations. However I don't understand why avoiding forward declarations outside the header guard. Is the case of dealing with an incomplete type occuring as often? – Luka Dec 02 '17 at 11:30
  • The problem is that it typically doesn't happen often so you will want to make extra sure that the few occurences of incomplete types are easily recognizable. Another reason is that if a header file is included more than once for a compilation unit it results in more code to parse after preprocessing and therefore increases compilation time. – Richard Dec 06 '17 at 09:24