2

Consider I have a Class A and a Class B and their corresponding header:

a.h

#ifndef CLASS_A
#define CLASS_A

/* forward declare A */
class A;
/* includes */
#include "b.h"
/* define class A */
class A {
public:
    A() : p_b(nullptr) {}
    B *p_b;
};
#endif

b.h

#ifndef CLASS_B
#define CLASS_B

/* forward declare B */
class B;
/* includes */
#include "a.h"
/* define class B */
class B {
public:
    B() : m_a() {}
    A m_a;
};
#endif

This does not work:

To compile the implementation of A into an object-file, I first include a.h, that forward declares A and then includes b.h that then declares and defines B. But when B is defined it does not know the size of A and therefore can not declare an object of A as a member of B.

A however does not need to know the size of B, as it only has a pointer to B and could be completely defined before B get's defined. Therefore the size of B can be completely known before it gets used as a member and the complete declaration SHOULD be fine.

Common sense though tells that the a.c file should always look like this:

#include "a.h"

[...]

Can I actually solve the problem by including b.h before a.h in a.c ? Would this be against some holy convention of having the first line of an implementation file being the include of it's header?

salbeira
  • 2,375
  • 5
  • 26
  • 40
  • Have you thought about including b.h after defining A? – user253751 Feb 07 '18 at 02:03
  • Then A would fail because B would not define a type when I create a pointer to it I presume. – salbeira Feb 07 '18 at 02:05
  • 3
    Typically, you put forward declarations in the file that needs them, not make the header itself include a different header that uses the first. – chris Feb 07 '18 at 02:06
  • 3
    You want to use the forward declarations to eliminate the circular dependency between a.h and b.h. Remove the include of b.h in a.h and replace the forward declaration of A with a forward declaration of B. No sense forward declaring a class in its own header (unless you have a complex header). – user4581301 Feb 07 '18 at 02:06
  • Very good reading, if not an outright duplicate: [Resolve build errors due to circular dependency amongst classes](https://stackoverflow.com/questions/625799/resolve-build-errors-due-to-circular-dependency-amongst-classes) – user4581301 Feb 07 '18 at 02:08

3 Answers3

4

You are using forward declarations in a backwards manner. The code should look more like this instead:

a.h

#ifndef CLASS_A
#define CLASS_A

/* forward declare B */
class B;

/* define class A */
class A {
public:
    A() : p_b(nullptr) {}
    B *p_b;
};

#endif

b.h

#ifndef CLASS_B
#define CLASS_B

#include "a.h"

/* define class B */
class B {
public:
    B() : m_a() {}
    A m_a;
};

#endif

a.h doesn't need to know what B actually is, since A contains a B* pointer and not a B object. So a.h should not be using #include "b.h" at all, it should be forward declaring B instead.

b.h does need to know what A actually is, since B contains an A object and not an A* pointer. So b.h should be using #include "a.h", which already forward declares B before defining A, then b.h finishes defining B.

a.c can then use #include "a.h" to bring in the declaration of A so it can finish defining the implementation, and it can use #include "b.h" only if A's methods need to access members of B.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • So my error lies in the assumption that I should always forward declare a class in it's header file before any includes because eventual cyclic dependencies might want to know if a symbol refers to a type or something else. Therefore I must rather get used to always forward declaring classnames I USE, not I PROVIDE to get rid of these errors, as long as I use ONLY pointers of the forwarded class. – salbeira Feb 07 '18 at 02:20
  • @salbeira if I read what you said correctly, then yes. A header file should not forward declare a class that it also defines (unless the header defines multiple classes with dependancies on each other). Forward declare anything you can get away with that doesn't require including other header files, let the implementation file include whatever headers it needs. This reduces header dependancies on each other, thus reducing the amount of code that needs to be recompiled when a header is altered. And it better optimizes a compiler's ability to use precompiled headers. – Remy Lebeau Feb 07 '18 at 02:26
1

Lets look at what the compiler sees after the preprocessor has done its thing:

/* forward declare A */
class A;
/* includes */
/* forward declare B */
class B;
/* includes */
/* define class B */
class B {
public:
    B() : m_a() {}
    A m_a;
};
/* define class A */
class A {
public:
    A() : p_b(nullptr) {}
    B *p_b;
};

As you can see, B's class definition comes before A is fully defined, and so your program is ill-formed.

Here, you only need one forward-declaration (of B), and it should come just before the definition of A in A.h:

#ifndef CLASS_A
#define CLASS_A

// Forward declare B so that B* p_b is legal
class B;

// Note that B.h is *not* included here

class A {
public:
    A() : p_b(nullptr) {}
    B *p_b;
};
#endif

Here, you break the circular include cycle by forward-declaring B instead of including the full definition of B. Presumably in A.cpp you would then #include the full definition of B so that you can use its members.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
0

Since each class depends upon the other, both classes should be defined in the same header file (and in the same namespace). If for some reason they must be in different header files, this would work.

A.h

#ifndef CLASS_A
#define CLASS_A

class B;

class A {
public:
    A() : p_b(nullptr) {}
    B *p_b;
};
#endif

B.h

#ifndef CLASS_B
#define CLASS_B

#include "a.h"

class B {
public:
    B() : m_a() {}
    A m_a;
};
#endif
Jive Dadson
  • 16,680
  • 9
  • 52
  • 65
  • Heh it is nice that you assume it is a homework assignment, but it is most definetly not :-) And the related classes are actually part of very different subsystems of the application, one defining a node in a hierarchical graph and the other reperesenting a widget that want's to be inserted in a menu, but that would be too much information for the question to work. – salbeira Feb 07 '18 at 02:14
  • @salbeira No offense taken, I hope. None intended. I removed the H word. – Jive Dadson Feb 07 '18 at 02:16