Categories
c++ c++-faq circular-dependency compiler-errors

Resolve build errors due to circular dependency amongst classes

447

I often find myself in a situation where I am facing multiple compilation/linker errors in a C++ project due to some bad design decisions (made by someone else 🙂 ) which lead to circular dependencies between C++ classes in different header files (can happen also in the same file). But fortunately(?) this doesn’t happen often enough for me to remember the solution to this problem for the next time it happens again.

So for the purposes of easy recall in the future I am going to post a representative problem and a solution along with it. Better solutions are of-course welcome.


  • A.h

    class B;
    class A
    {
        int _val;
        B *_b;
    public:
    
        A(int val)
            :_val(val)
        {
        }
    
        void SetB(B *b)
        {
            _b = b;
            _b->Print(); // COMPILER ERROR: C2027: use of undefined type 'B'
        }
    
        void Print()
        {
            cout<<"Type:A val="<<_val<<endl;
        }
    };
    

  • B.h

    #include "A.h"
    class B
    {
        double _val;
        A* _a;
    public:
    
        B(double val)
            :_val(val)
        {
        }
    
        void SetA(A *a)
        {
            _a = a;
            _a->Print();
        }
    
        void Print()
        {
            cout<<"Type:B val="<<_val<<endl;
        }
    };
    

  • main.cpp

    #include "B.h"
    #include <iostream>
    
    int main(int argc, char* argv[])
    {
        A a(10);
        B b(3.14);
        a.Print();
        a.SetB(&b);
        b.Print();
        b.SetA(&a);
        return 0;
    }
    

2

  • 26

    When working with Visual Studio, the /showIncludes flag helps a lot to debug this kind of problems.

    – wip

    Sep 12, 2012 at 3:08


  • Is there something similar for Visual studio code?

    – Erik

    Nov 9, 2021 at 13:41

361

The way to think about this is to “think like a compiler”.

Imagine you are writing a compiler. And you see code like this.

// file: A.h
class A {
  B _b;
};

// file: B.h
class B {
  A _a;
};

// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
  A a;
}

When you are compiling the .cc file (remember that the .cc and not the .h is the unit of compilation), you need to allocate space for object A. So, well, how much space then? Enough to store B! What’s the size of B then? Enough to store A! Oops.

Clearly a circular reference that you must break.

You can break it by allowing the compiler to instead reserve as much space as it knows about upfront – pointers and references, for example, will always be 32 or 64 bits (depending on the architecture) and so if you replaced (either one) by a pointer or reference, things would be great. Let’s say we replace in A:

// file: A.h
class A {
  // both these are fine, so are various const versions of the same.
  B& _b_ref;
  B* _b_ptr;
};

Now things are better. Somewhat. main() still says:

// file: main.cc
#include "A.h"  // <-- Houston, we have a problem

#include, for all extents and purposes (if you take the preprocessor out) just copies the file into the .cc. So really, the .cc looks like:

// file: partially_pre_processed_main.cc
class A {
  B& _b_ref;
  B* _b_ptr;
};
#include "B.h"
int main (...) {
  A a;
}

You can see why the compiler can’t deal with this – it has no idea what B is – it has never even seen the symbol before.

So let’s tell the compiler about B. This is known as a forward declaration, and is discussed further in this answer.

// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
  A a;
}

This works. It is not great. But at this point you should have an understanding of the circular reference problem and what we did to “fix” it, albeit the fix is bad.

The reason this fix is bad is because the next person to #include "A.h" will have to declare B before they can use it and will get a terrible #include error. So let’s move the declaration into A.h itself.

// file: A.h
class B;
class A {
  B* _b; // or any of the other variants.
};

And in B.h, at this point, you can just #include "A.h" directly.

// file: B.h
#include "A.h"
class B {
  // note that this is cool because the compiler knows by this time
  // how much space A will need.
  A _a; 
}

HTH.

11

  • 25

    “Telling the compiler about B” is known as a forward declaration of B.

    Nov 17, 2010 at 1:57

  • 12

    Omg! totally missed the fact that references are known in terms of occupied space. Finally, now I can design properly!

    – kellogs

    Nov 7, 2011 at 2:31

  • 64

    But still You cannot use any function on B (as in the question _b->Printt())

    – rank1

    Apr 17, 2013 at 11:02

  • 4

    @sydan: You can’t. Resolving circular dependencies requires out-of-class definitions.

    – Ben Voigt

    Apr 11, 2015 at 14:03

  • 4

    But I need to use in A class B as a complete type and in B class A as a complete type. By saying complete type, I mean, calling a function from an object of that type. How would I do it? I just get error, invalid use of incomplete type B in class A.

    – Silidrone

    Sep 5, 2017 at 12:47

116

You can avoid compilation errors if you remove the method definitions from the header files and let the classes contain only the method declarations and variable declarations/definitions. The method definitions should be placed in a .cpp file (just like a best practice guideline says).

The down side of the following solution is (assuming that you had placed the methods in the header file to inline them) that the methods are no longer inlined by the compiler and trying to use the inline keyword produces linker errors.

//A.h
#ifndef A_H
#define A_H
class B;
class A
{
    int _val;
    B* _b;
public:

    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

//B.h
#ifndef B_H
#define B_H
class A;
class B
{
    double _val;
    A* _a;
public:

    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

//A.cpp
#include "A.h"
#include "B.h"

#include <iostream>

using namespace std;

A::A(int val)
:_val(val)
{
}

void A::SetB(B *b)
{
    _b = b;
    cout<<"Inside SetB()"<<endl;
    _b->Print();
}

void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

//B.cpp
#include "B.h"
#include "A.h"
#include <iostream>

using namespace std;

B::B(double val)
:_val(val)
{
}

void B::SetA(A *a)
{
    _a = a;
    cout<<"Inside SetA()"<<endl;
    _a->Print();
}

void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

//main.cpp
#include "A.h"
#include "B.h"

int main(int argc, char* argv[])
{
    A a(10);
    B b(3.14);
    a.Print();
    a.SetB(&b);
    b.Print();
    b.SetA(&a);
    return 0;
}

4

  • Thanks. This solved the problem easily. I simply moved the circular includes to the .cpp files.

    Oct 4, 2014 at 18:16

  • 5

    What if you have a template method? Then you can’t really move it into a CPP file unless you instantiate the templates manually.

    – Malcolm

    Sep 1, 2016 at 12:55

  • You always include “A.h” and “B.h” together. Why don’t you include “A.h” in “B.h” and then include only “B.h” in both “A.cpp” and “B.cpp”?

    Sep 30, 2018 at 4:25

  • Thanks, Nice answer for those who need this interdependence between 2 classes and cannot refactor it differently

    Dec 22, 2020 at 14:34

43

I’m late answering this, but there’s not one reasonable answer to date, despite being a popular question with highly upvoted answers….

Best practice: forward declaration headers

As illustrated by the Standard library’s <iosfwd> header, the proper way to provide forward declarations for others is to have a forward declaration header. For example:

a.fwd.h:

#pragma once
class A;

a.h:

#pragma once
#include "a.fwd.h"
#include "b.fwd.h"

class A
{
  public:
    void f(B*);
};

b.fwd.h:

#pragma once
class B;

b.h:

#pragma once
#include "b.fwd.h"
#include "a.fwd.h"

class B
{
  public:
    void f(A*);
};

The maintainers of the A and B libraries should each be responsible for keeping their forward declaration headers in sync with their headers and implementation files, so – for example – if the maintainer of “B” comes along and rewrites the code to be…

b.fwd.h:

template <typename T> class Basic_B;
typedef Basic_B<char> B;

b.h:

template <typename T>
class Basic_B
{
    ...class definition...
};
typedef Basic_B<char> B;

…then recompilation of the code for “A” will be triggered by the changes to the included b.fwd.h and should complete cleanly.


Poor but common practice: forward declare stuff in other libs

Say – instead of using a forward declaration header as explained above – code in a.h or a.cc instead forward-declares class B; itself:

  • if a.h or a.cc did include b.h later:
    • compilation of A will terminate with an error once it gets to the conflicting declaration/definition of B (i.e. the above change to B broke A and any other clients abusing forward declarations, instead of working transparently).
  • otherwise (if A didn’t eventually include b.h – possible if A just stores/passes around Bs by pointer and/or reference)
    • build tools relying on #include analysis and changed file timestamps won’t rebuild A (and its further-dependent code) after the change to B, causing errors at link time or run time. If B is distributed as a runtime loaded DLL, code in “A” may fail to find the differently-mangled symbols at runtime, which may or may not be handled well enough to trigger orderly shutdown or acceptably reduced functionality.

If A’s code has template specialisations / “traits” for the old B, they won’t take effect.

8

  • 2

    This is a really clean way to handle the forward declarations. The only “disadvantage” would be in the extra files. I assume you always include a.fwd.h in a.h, to assure they stay in sync. The example code is missing where these classes are used. a.h and b.h will both need to be included since they won’t function in isolation: “` //main.cpp #include “a.h” #include “b.h” int main() { … } “` Or one of them needs to be fully included in the other like in the opening question. Where b.h includes a.h and main.cpp includes b.h

    – Farway

    May 5, 2017 at 16:37


  • 2

    @Farway Right on all counts. I didn’t bother showing main.cpp, but nice that you’ve documented what it should contain in your comment. Cheers

    May 5, 2017 at 20:42

  • 1

    One of the better answers with a nice detailed explanation of why with the does and don’ts due to the pros and cons…

    Jan 16, 2018 at 5:06

  • 1

    @RezaHajianpour: it makes sense to have a forward declaration header for all classes that you want forward declarations of, circular or not. That said, you will only want them when: 1) including the actual declaration is (or can be anticipated to later become) costly (e.g. it includes a lot of headers your translation unit might not otherwise need), and 2) client code is likely to be able to make use of pointers or references to the objects. <iosfwd> is a classic example: there can be a few stream objects referenced from many places, and <iostream> is a lot to include.

    Jan 23, 2019 at 5:02

  • 1

    @RezaHajianpour: I think you have the right idea, but there’s a terminological issue with your statement: “we just need the type to be declared” would be right. The type being declared means the forward declaration has been seen; it’s defined once the full definition has been parsed (and for that you may need more #includes).

    Jan 25, 2019 at 14:45