In a previous section, we saw how we could declare and define a function as two separate steps.
The declaration of a function includes its return type, its name, and the types of its parameters.
// A function declaration
int Add(int x, int y);
The definition includes all those things, but also includes the body, or the implementation, of the function:
// A function definition
int Add(int x, int y) {
return x + y;
}
What is the difference between a function declaration and definition?
We also introduced how we could do something similar with class functions. Lets imagine we have this class:
class Character {
void TakeDamage(int Damage) {
// Implementation
};
};
The above code could instead be written like this:
// character.cpp
class Character {
void TakeDamage(int Damage);
}
void Character::TakeDamage(int Damage) {
// Implementation
}
How can we provide a definition for this function?
class Maths {
int Add(int x, int y);
}
This separation may have seemed like a niche quirk when first introduced. However, it is actually the most common way C++ code is written. This section will explain why.
It has become standard practice to split the declaration of classes into a separate file from their implementation, and then link them back together using the #include
directive.
The files where the declarations live are called Header Files and they typically have a .h
extension. The files were the definitions live are .cpp
files, like we've been using so far.
Whilst not necessary, it is often a good idea to have one class per header file, and for each .cpp
file to provide implementations for only one class:
// character.h
#pragma once
class Character {
void TakeDamage(int Damage);
}
// character.cpp
#include "character.h"
void Character::TakeDamage(int Damage) {
// Implementation
}
The C++ community have found it much easier to work with medium-to-large scale code bases in this way. In many contexts, it is also a practical necessity.
The introduction of header files has at least two benefits:
The second point is worth exploring more. It is best practice to avoid including .cpp
files as much as possible, and instead prefer to #include
header files.
Lets update our main.cpp
to include only the header files, and not the .cpp
files.
// main.cpp
#include "character.h"
int main() {
Character PlayerCharacter;
PlayerCharacter.TakeDamage(50);
}
Seen from the perspective of main.cpp
, the fact that this code works is perhaps surprising.
It imports the character.h
header file, but not the character.cpp
implementation file.
Our character.h
file doesn't include the character.cpp
file either. So main.cpp
doesn't have the contents of character.cpp
, even indirectly.
Therefore, main.cpp
never has the implementation of the TakeDamage
function, even as it is being compiled.
But somehow, it is still able to call this function. If we check in a debugger, the call would do exactly what the function is programmed to do.
To understand how everything is working given this scenario, we will introduce the linker in the next lesson.
What is the convention on where to create declarations and definitions?
Sometimes, our classes will be small enough that we do not want to split them up in this way. Our first instinct might be to tackle these classes as we have been doing so far - by just putting everything in a .cpp
file.
Instead, we should put everything in a .h
file. The header-only convention is much more common and useful than a "no-header" approach.
Additionally, if the class gets big enough to warrant separating into two files later, it's much easier to generate a .cpp
file from a header file than it is to generate a header file from a .cpp
.
This also has the benefit that, if we started with a header file, all of our #include
directives would already be referencing that header file so would not need to be updated.
virtual
and override
FunctionsIn previous lessons, we saw how we could add specifiers like virtual
and override
to our class methods.
When splitting the declaration and definition of functions into separate steps, these specifiers only need to be used with the declaration, typically in the header file.
Using these specifiers outside of a class definition will result in a compilation error.
class Character {
virtual void FunctionA();
virtual void FunctionB();
};
void Character::FunctionA() {
}
// No specifiers are used in the implementation
virtual void Character::FunctionB() {
}
Become a software engineer with C++. Starting from the basics, we guide you step by step along the way