One of the peculiar and most difficult things to understand when working with C++ is that code files are divided into two types.
On one hand we have:
- Header files (
.h) that contain declarations (what classes, functions, or constants exist) - Implementation files (
.cpp) that contain the definitions (how those classes and functions work)
This can be surprising (and also a hassle), since most modern languages have declarations and definitions in a single file.
However, when computers were much less powerful, this division helped reduce compilation times ⌛, especially in large projects (nowadays it doesn’t make much sense).
This is a direct inheritance from C, where code was already separated into headers (.h) and sources (.c)
Although not always strictly necessary, .h and .cpp files are still part of the daily routine in any C++ project (so you’ll have to deal with them one way or another).
Modules are a more modern alternative for managing projects.
Introduction to .h and .cpp Files
As I said, generally in a C++ project we will have .h and .cpp files.
The .h is like the contract or promise: “I tell you what’s available to use.” The .cpp is the implementation: “I tell you how it works.”
Let’s look at them in a bit more detail 👇
Header files .h contain the definitions of the functions and classes that will be used in a program.
- Class Declarations: Contains the definition of classes, including data members and methods.
- Function Declarations: Defines the functions that are implemented in the
.cppfiles. - Macros and Constants: Declares constants, macros, and other global elements.
Implementation files, with the .cpp extension, contain the actual implementation of the code. This is where the definitions of the functions and methods declared in the .h files are written.
- Method Definitions: Implements the methods and functions declared in the
.hfiles. - Logic Code: Contains the code that performs the specific logic of the program.
Typical Project Structure
In a traditional C++ project, a convention is followed to separate declarations and definitions into different files.
Logically, everyone organizes their project as they want (especially large ones). But, in a simple example, a project would typically look like this.
proyecto/ src/ main.cpp mi_clase.cpp include/ mi_clase.h
That is, we would have the different elements separated into folders.
src/: We would put the implementation files (.cpp).include/: We would have the header files (.h).
File Inclusion
The #include directive is used to include the content of one file into another file.
#include "my_class.h"
In .cpp files, we will include the necessary header files to access class and function declarations.
Code Example
Let’s see it all together better in an example. Suppose we have a class called MyClass.
On one hand we would have the header file (my_class.h).
#ifndef MY_CLASS_H
#define MY_CLASS_H
class MyClass {
public:
MyClass(); // Constructor
void myMethod(); // Public method
private:
int myVariable; // Private variable
};
#endif // MY_CLASS_H
On the other hand we would have the implementation file my_class.cpp)
#include "my_class.h"
#include <iostream>
MyClass::MyClass() : myVariable(0) {
// Constructor: Initializes myVariable to 0
}
void MyClass::myMethod() {
std::cout << "My variable is: " << myVariable << std::endl;
}
Here we have the definition (the code) of the functions we had declared in the .h.
Finally, this is how we would use it in our main file main.cpp.
#include "my_class.h"
int main() {
MyClass object;
object.myMethod();
return 0;
}
Here we would include the my_class.h file. With that, main has access and can use the declarations (in this case of MiClase).
Later, the compiler will put everything together, adding the implementations from the .cpp file.
Include Guards in C++
An include guard is a mechanism used to ensure a header file is not included more than once in a project.
Basically, they are #ifndef, #define, #endif directives inserted into .h header files, which make the compiler include the code only once. So,
#ifndef MY_CLASS_H
#define MY_CLASS_H
// Header file content
#endif // MY_CLASS_H
Here,
#ifndef: Checks if the macro is not defined.#define: Defines the macro if it’s not already defined.#endif: Ends the include guard.
Without include guards, the same blocks of code could be included multiple times in a single source file, which would give us a compilation error.
The pragma once Directive
Instead of using traditional include guards, a more modern way is to use the #pragma once directive.
This directive is more concise and easier to read, and equally ensures that a file is included only once during compilation (but without needing to manually define macros).
#pragma once
class MyClass {
public:
void myMethod();
};
As we can see, #pragma once is much more convenient, as we don’t need to wrap all our code in guard directives. We simply put a single line at the beginning of the file.
To be able to use it, we need it to be supported by the compiler. It is compatible with most modern compilers (like GCC, Clang, and MSVC).
