Header vs Source
In general only declarations should be put into the header files (.h
files),
the definitions should all go into source files (.cpp
files).
This is to obey the ODR.
We want each translation unit to be aware of everything declared but not keep a
separate copy of the object in memory. Then, they link to the same object.
There are a few exceptions.
Inline functions
They are not compiled as separate functions at compile time. They are just copied and pasted into the functions calling them. So, they have no declaration or definition. It is OK to have them in the header files.
// MyMath.h
#pragma once
inline int add(int a, int b) { // Defined directly in the header
return a + b;
}
Some trivial member functions would also be implicitly converted to inline functions.
// Person.h
#pragma once
#include <string>
class Person {
private:
std::string name;
public:
Person(const std::string& n) : name(n) {}
const std::string& getName() const { // Small function defined in header (implicitly inline)
return name;
}
};
Class/Templates
classes and templates are type definitions. They are typically kept in the headers. They have no linkage.
Classes are typically defined in header files because at compile time, the compiler needs to know the member functions and variables to compile their usages in code.
#pragma once // Prevents multiple inclusions of the same header
class MyClass {
public:
int value; // Member variable declared
MyClass(int val); // Constructor declaration
void printValue(); // Member function declaration
void inlineFunc() { // Inline function defined directly in header
// Body here
value++;
}
};
So, if you use MyClass my_class; my_class.printValue();
, the compiler would
have to know the printValue()
function. So, the header file has to have the
definition instead of just a declaration of the class name.
For templates, the compiler needs the entire definition of the class or the function to generate the code for that type when instantiated.
See template for more details on how a template is compiled.
// MyContainer.h
#pragma once
template <typename T>
class MyContainer {
public:
T value;
MyContainer(T val) : value(val) {}
void printValue() { // Template member function defined in header
// Implementation detail needs to be visible
std::cout << "Value: " << value << std::endl;
}
};
template <typename T>
T getMax(T a, T b) { // Template function defined in header
return (a > b) ? a : b;
}
Constants/Enum
const
variables with external linkage. If you have a const global variable
that you want to be visible across multiple translation units, it's typically
declared in a header. However, if it's not constexpr and not an integral type,
it still needs a single definition in a .cpp file. But for simple integral
consts, they can often be defined in the header.
// Config.h
#pragma once
class Config {
public:
static const int MAX_USERS = 100; // Can be defined in header
enum Status { ACTIVE, INACTIVE };
static const Status DEFAULT_STATUS = ACTIVE; // Can be defined in header
};
constexpr
constexpr
functions are functions that can be evaluated at compile time if
their arguments are known at compile time.
// Constants.h
#pragma once
constexpr int multiply(int x, int y) { // Defined in header for compile-time evaluation
return x * y;
}