This is still the header! Main site

On C++ headers, part 2: including all the things

2024/10/06

Header files should contain class definitions, while the actual methods should go into cpp files. Right?

If you're coming from Java-land, one class per hpp-cpp pair sounds reasonable. Meanwhile, including header files on top of yours is supposed to correspond to all the import statements you have.

This sometimes makes sense. After all, if you're using another class in your own, you need to know exactly how many bytes to allocate to it. Just to continue with the example from the previous post:


// water_kettle.hpp
#pragma once
#include "lid.hpp"
#include "heater_coil.hpp"

class WaterKettle {
public:
  // (...)
private:
  Lid lid;
  HeaterCoil coil;
};
          

To make our kettle, we need exact descriptions of what its lid and heater coil contains. They might have their own fields, which should be included in our class. As in...


sizeof(WaterKettle) == sizeof(Lid) + sizeof(HeaterCoil) + (... some other fields)

Nevertheless, applying the rule of "just include everything you are using" in your header often leads to suboptimal consequences. Especially if you consider private methods that, due to the way C++ works, also need to be declared in the header, despite them supposedly only being a description of the interface of this class.


// water_kettle.hpp
#pragma once
#include "qed/everything.hpp"
#include "classical_physics.hpp"  // needed for Temperature

class WaterKettle {
public:
  // (...)
private:
  void RunActualPhysics(QuantumElectroDynamicsProcessor& proc);
  size_t CountSteamBubbles(Water* water, Temperature temp, HeaterCoil::Shape shape);
};
          

We could remove some of these includes just by using forward declarations as described in part 1. On the other hand, sometimes we want to legitimately pass around value objects or use templates in our private methods, so we have to include the relevant header.

As a consequence, everyone who wants to use our water kettle class will need to mandatorily learn everything about quantum electrodynamics, despite them only wanting to heat some water. This is pretty sad from a compilation time perspective.

Worse yet, imagine each of your header files including some other ones from your own project...


// water_kettle.hpp
#pragma once
#include "wire.hpp"

class WaterKettle {
public:
   void AttachCord(Wire<Network::US_110v> cord);
(...)
}
        

... with those header files including some more...


// wire.hpp
#pragma once
#include "electrical_network.hpp"
#include "transformer_station.hpp"
#include "building_wall.hpp"

template <class TVoltage>
class Wire {
public:

   Station GetSupplyingTransformerStation();
(...)
}
        

... and continuing even more, ending up somewhere like...


// nuclear_power_plant.hpp
#pragma once
#include "periodic_table.hpp"
#include "nrc/fuel_handling_procedures/process.hpp"

class NuclearPowerPlant {
public:
   void Refill(FuelSupply<Elements::U::U238> fuel, const NuclearRegulatoryCommissionRules& regulations);
(...)
}
        

Yet again, whoever wants to use a water kettle now has to go through every single rule regarding the handling of nuclear waste in the United States. Among other things.

Also, if the graph of your includes is a dense one, you likely will need to recompile basically all your files after changing any one of them. Not especially good for compilation times, either.

There are solutions to this. Solutions whose discussion is hereby postponed to the next episode of this series. Pointing out that they exist might help ameliorating the sadness resulting from all of the above though.