Let's talk about C++, and more precisely, circular imports and the hell they are
- how importations work in C++
- Header guards (bonus)
- Circular importations
- Solutions to deal with them
Let's imagine we have theses 3 files :
main.cpp :
#include<iostream>
#include "myClass.h"
int main(){
myClass obj;
return 0;
}
myClass.h
#include "mySecondClass.h"
class myClass{
public:
mySecondClass second;
};
mySecondClass.h
class mySecondClass{
public:
int data;
};
The include
statement in C++ does nothing more than copying the content of the file wanted, and pasting it in the calling file.
In this example, after executing the preprocessor (the one charged with interpreting the #include statements), we would have a code looking like this :
tmpfile
#include<iostream>
class mySecondClass{
public:
int data;
};
class myClass{
public:
mySecondClass second;
};
int main(){
myClass obj;
return 0;
}
This include
statement is dumb and will copy contents of the file without any verification of any kind. And that can cause problems, as we are going to see
I'm going to assume you already use header guards (the #ifndef statement in all your header files) If you don't/would like to know how they work, you can check this bonus chapter
Let's take the follow example :
main.cpp
#include "myClass.h"
using namespace std;
int main(){
myClass obj;
printClass(obj);
return 0;
}
myClass.h
#ifndef MYCLASS
#define MYCLASS
#include "myUtils.h"
class myClass{
public:
int data;
};
#endif
myUtils.h
#ifndef MYUTILS
#define MYUTILS
#include<iostream>
#include "myClass.h"
using namespace std;
void printClass(myClass& cl);
#endif
myUtils.cpp
#include "myUtils.h"
void printClass(myClass& cl){
cout << cl.data << endl;
}
I will not show the myUtils.cpp file every time since it won't change for now
Notice that the only differences between this example and the last one is that I added header guards, and that I moved the include "myUtils.h"
inside the myClass.h file.
This shouldn't break anything... right ?
When the code is compiled, we get a nice error inside myUtils.h about myClass not being declared. But we declared and imported it !
Let's take a look at this from the view of the preprocessor : main.cpp includes myClass.h, so myClass.h is copied but we see that myClass.h includes myUtils.h, so myUtils.h is copied but we see that myUtils.h also requires myClass.h, so... wait, we were already copying that file ! Thankfully, the header guards see this, and "cancel the importation". The problem is that now, the myClass.h importation inside myUtils.h is cancelled, so myUtils.h doesn't know about myClass.h !
What I just described is called a circular importation Two files mutually import themselves, and that REALLY causes a mess.
Don't be afraid to re-read this statement a few times, and read back the code to be sure to understand the problem, because you will not be able to fix it if you don't understand it
Short answer : there is no magical way to fix this, there is no "remove circular imports button", at least not in any IDE I know of. You will need to modify your code manually in a way to prevent these circular importations from happening
The first thing you should do, is break down your files in smaller pieces. If you have more files, more specific, you will be less likely to have a circular import error.
If you still encounter circular imports errors (and you will), here is a way to handle them :
You can avoid importing a class using class <myClassName>;
instead of #include "fileOfMyClass"
. That statement will tell C++ that a class with this name exists, and that even if it doesn't know its declaration, it should use it.
This only works in files where you only use pointers and references to these classes, because else C++ will need to know the declaration of the class
For example, you can replace
#ifndef MYUTILS
#define MYUTILS
#include<iostream>
#include "myClass.h"
using namespace std;
void printClass(myClass& cl);
#endif
with
#ifndef MYUTILS
#define MYUTILS
#include<iostream>
class myClass;
using namespace std;
void printClass(myClass& cl);
#endif
and yay, the code now compiles ! But you cannot do
#ifndef MYUTILS
#define MYUTILS
#include<iostream>
class myClass;
using namespace std;
void printClass(myClass cl);
#endif
Did you spot the difference ? I modified the printClass() function to take a myClass as argument instead of a myClass&. And this code will not compile because of it.
One last thing to modify : you remember the myUtils.cpp file ? Before, it was getting the declaration of myClass with the #include "myClass.h"
statement in myUtils.h
Now, we need to include that file directly inside the myUtils.cpp, that will now look like this :
myUtils.cpp
#include "myUtils.h"
#include "myClass.h"
void printClass(myClass& cl){
cout << cl.data << endl;
}
You have two ways to apply this solution. You can either use the class <name>;
statement every time it's possible to do so, so you are sure you never, ever (99%) run into circular import errors
Or you can code normally, and track circular errors to put class <name>;
where needed for the code to compiles
Both work :)
If this "article" helped you, write me a comment ! :)