Search code examples
c++memberforward-declaration

The great c++ forward declaration confusion


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?


Solution

  • 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.