Language: EN

cpp-ficheros-h-y-cpp

Header and .cpp Files in C++

One of the peculiar things and hardest to understand when working with C++ is that code files are divided into two types of files.

On one hand we have:

  • Header Files (.h) that contain the declarations (what classes, functions, or constants exist)
  • Implementation Files (.cpp) that contain the definitions (how those classes and functions work)

This can be shocking (besides being a hassle), since most modern languages combine declarations and definitions in a single file.

However, when computers were much less powerful, this division helped to reduce compilation times ⌛, especially in large projects (nowadays it doesn’t make much sense anymore).

This is a direct inheritance from C, where this separation of code into headers (.h) and sources (.c) was already done.

Although they are not always strictly necessary, .h and .cpp files are still a daily occurrence in any C++ project (let’s face it, you will 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 mentioned, generally in a C++ project we will have .h and .cpp files.

The .h is like the contract or promise: “I tell you what is available to use.” The .cpp is the implementation: “I tell you how it works.”

Let’s take a closer look at them 👇

The header files .h (header) 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 .cpp files.
  • Macros and Constants: Declares constants, macros, and other global elements.

The 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 that have been declared in the .h files.
  • 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 wish (especially the large ones). But, in a simple example, typically a project would look like this.

/project
    |-- src/
        |-- main.cpp
        |-- my_class.cpp
    |-- include/
        |-- my_class.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 in another file.

#include "my_class.h"

In the .cpp files, we will include the necessary header files to access the class and function declarations.

Code Example

Let’s see everything together in an example. Suppose we have a class called MyClass.

On one side 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 side 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 that 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 file my_class.h. With that, the main has available the declarations *(in this case of MyClass).

Subsequently, the compiler will put everything together, adding the implementations from the .cpp file.

Safeguards in C++

A safeguard is a mechanism used to ensure that a header file is not included more than once in a project.

Basically, they are #ifndef, #define, #endif directives that are inserted in the .h header files, which make the compiler include the code only once. Thus,

#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 is not already defined.
  • #endif: Ends the inclusion guard.

Without safeguards, the same blocks of code could be included multiple times in a single source file, which would give us an error when compiling.

Pragma Once Directive

Instead of using traditional inclusion guards, a more modern way is to use a directive #pragma once.

This directive is more concise and easier to read, and also ensures that a file is included only once during compilation (but without the need to manually define macros).

#pragma once

class MyClass {
public:
    void myMethod();
};

As we can see, #pragma once is much more convenient, as there is no need to wrap all our code in safeguard directives. We simply put a single line at the beginning of the file.

To use it, we need to ensure that it is supported by the compiler. It is compatible with most modern compilers (like GCC, Clang, and MSVC).