In the previous lesson, we introduced the preprocessor, and how we can use it to conditionally remove parts of our code before we send it off for compilation.
The second aspect of the preprocessor that we're looking at essentially does the opposite. It give us a way to add additional code to our files.
This can be done using the #include
directive. You may have noticed we've already been using an include directive this whole time:
#include <iostream>
We can finally explain what that line is doing!
When we build our project, the preprocessor will see this include directive, and it will replace it with a load of code that is stored in a different file.
With the #include <iostream>
instruction specifically, it adds all the code that we've been using to log out to the console. This includes things like cout
and endl
.
More generally, the #include
directive unlocks the ability for us to split our code into multiple files. This is vital for organising non-trivial projects.
With our project seperated into multiple files, we can then use the #include
directive any time the code in one file needs to use something in one of our other files.
Commonly, this will be our classes. We can, for example, have the code for our Character
class in a different file, such as Character.cpp
, and still create Characters in our main.cpp
file, as long as we use #include
properly:
// Character.cpp
class Character {};
// main.cpp
#include <iostream>
#include "Character.cpp"
int main() {
Character PlayerCharacter;
}
Once the preprocessor encounters #include "Character.cpp"
in our main.cpp
, it will jump into action and replace that directive with the contains of our Character.cpp
file.
Because of this, the code that our compiler receives will be this:
// main.cpp
#include <iostream>
class Character {};
int main() {
Character PlayerCharacter;
}
This file now looks very similar to what we've seen in the past. Now, however, instead of having to work with a main.cpp
that constantly gets bigger, we're able to organise our project into multiple files, each of them much easier to work with.
Something might be immediately apparent here: when including <iostream>
we wrapped it in chevrons - <
and >
whilst, when we included "Character.cpp"
we used double quotes.
The difference in syntax is essentially telling the compiler where to look for the files we're trying to include.
#include
look for files?When we use the <
and >
syntax, we are letting our tools decide where to look for the requested file. This is covered in more detail in the _Setting Include Directories section below.
The other way we can use the directive is with double quotes "
, ie, #include "SomeFile.cpp"
. The behavior of this approach is that the preprocessor will look in the directory of the requesting file.
For example, if we had this directive in main.cpp
, the preprocessor would look in the folder where main.cpp
is located, to see if it can find a file called SomeFile.cpp
.
If it can't, it will fall back to the checking the locations defined by the compiler or the IDE.
This means that, whether we use <>
or ""
, the preprocessor will probably be able to find the file we need. The difference is mostly just convention.
Typically the ""
form would indicate we are trying to import a file we created, or a file we added to our project directly.
On the other hand, the <
and >
notation often indicates we are trying to import something from a library - often the C++ Standard Library.
The standard library is a collection of generally useful code - such as iostream
- that ships alongside C++.
By being separate to the language, we can #include
only the parts of the standard library that we need, keeping unnecessary bloat out of our software.
We'll cover the standard library more in later chapters.
Not all the files we will want to #include
will be in the same directory as the file we're adding the #include
directive to.
To solve this, we can add path traversal to our #include
directives, as shown below. These paths are relative to the file where the #include
directive is beign used.
// Look in the same directory as the current file
#include "Math.cpp"
// Look in a directory called Helpers, in the same directory as the current file
#include "Helpers/Math.cpp"
// Traverse up a level to look in the parent directory of the current file
#include "../Math.cpp"
// Traverse up two levels, and then into a directory called Helpers
#include "../../Helpers/Math.cpp"
How can we use the preprocessor to include the content from a file in the same directory called Utilities.cpp
?
How can we use the preprocessor to include the content from the parent directory called Utilities.cpp
?
In the previous section, we discussed how our tools (eg, the IDE) would control where the preprocessor looks for files referenced by the #include
directive.
Most of the time, our IDEs will set sensible default values for this. However, they will also let us add or modify these locations. In Visual Studio, this is under Project Settings > C/C++ > Include Directories
This is quite useful as, if we have a directory we import from frequently, we can add that to our project settings.
This can make our lives easier, and simplify our #include
directives greatly. We could go from something like this:
#include "../../Math.cpp"
#include "../../../Shared/Static/Physics.cpp"
To something much simpler:
#include "Math.cpp"
#include "Physics.cpp"
This also makes our projects easier to re-organise. Any time we try to update our directory structure, we could potentially have to update dozens of relative #include
directives. Instead, if the path is defined in the include directories, we would only have to update it in that one place.
When we use the #include
directive, we can literally think of it as copying and pasting the contents of a file into another file. This is quite a crude operation, and it has some issues we need to manage.
The next two lessons will focus on two techniques that have evolved to ensure the amalgamation of code still works how we expect.
However, the C++20 standard, published in December 2020, introduced a new feature to the language called modules. They use the C++ import
syntax, rather than the preprocessor's #include
.
// Before
#include "Character.cpp"
// After
import Character;
We cover modules in more depth on the intermediate course.
Modules will will supersede #include
directives eventually. However, things move slowly in the C++ world. As of 2022, few compilers fully support the new standard yet and, even once they do, #include
will still be more common for many years to come.
Therefore, for now, it's more important to understand how the #include
directive works.
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way