Consider the following:
#include <iostream>
int add(int x, int y); // forward declaration
int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4) << '\\n';
}
int add(int x, int y) // Function definition
{
return x + y;
}
In main(), we call the function before its implementation. This is possible since we included a forward declaration before the function call. This tells the compiler:
Hey, just letting you know this function exists. However, I’ll implement it later
For advanced functions, you don’t want to include the function implementation before main() since it will occupy lots of LOC, thus a forward declaration can be used.
This is the basic mechanic of forward declarations. However, they have two far more powerful uses.
- Reducing compile times by avoiding unnecessary
#includechains. - Breaking circular dependencies between files that need to reference each other.
Forward declarations can reduce compilation times
The above example is usually organized like this. The header in main.cpp is necessary since in C++ each file is compiled independently.
// add.h
int add(int x, int y);
// add.cpp
int add(int x, int y) // Function definition
{
return x + y;
}
// main.cpp
#include "add.h"
int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4) << '\\n';
}
In this way, add.h contains the declaration and we #include it into main(). This removes the forward declaration from main(), which makes our code organized.
However, let’s say you’re working with a large codebase where add.h imports a dozen header files and those header files have header files which are imported. Suddenly, the compiler has to #include all of the additional pages of code just to use one function in main().
This can increase compilation times.
We like to avoid this as time is money.
Adding a forward declaration reduces the compilation time as the compiler doesn’t need to #include files for a file that only uses a single function.
When the linker performs its job, it is able to resolve the function call.
Forward declarations eliminate circular dependencies
Frequently, you’ll have two files that have a relationship (cute).
In the following example we import Manager.h so the compiler knows what it is so it doesn’t freak out. Cool.
// Employee.h
#include "Manager.h"
#include <string>
class Employee {
Manager* boss; // pointer to the employee's manager
std::string name;
};
In Manager.h, we must import Employee.h so the compiler can create reports.
// Manager.h
#include "Employee.h"
#include <vector>
class Manager {
std::vector<Employee*> reports; // pointers to the employees they manage
};
Let’s examine this. Employee.h imports Manager.h which imports Employee.h. Aaahhhhh.
We’ve created a circular dependency. The compiler cannot resolve this
Enter forward declarations
Since Employee only stores a Manager* (a pointer, which has a known size regardless of what Manager looks like), the compiler doesn’t need the full definition of Manager here. The forward declaration is enough. ****Now, the linker is satisfied.
// Employee.h
class Manager; // forward declaration
class Employee {
Manager* boss;
std::string name;
};
Now, if we change Manager.h by adding another method, we don’t need to recompile Employee.h and every other file that imports it.