Contents
  1. Why Separate Header and Source Files
  2. Inline Functions
  3. constexpr
  4. Structs vs Classes
  5. Inheritance
  6. Operator and Function Overloading
← All posts

C++ Header Files, Inline Functions, and Inheritance

Separating C++ code into header and source files improves encapsulation, readability, and compilation performance. Inline functions, constexpr, and inheritance patterns each have specific trade-offs worth understanding.

Why Separate Header and Source Files

In C++, separating code into .h (header) and .cpp (source) files serves several purposes:

  • Encapsulation: the header exposes the public interface; the implementation is hidden in the .cpp.
  • Readability: during development, when a file grows large it becomes easier to understand the header without reading the whole implementation.
  • Compilation performance: the compiler already knows about all functions from the header. If the header does not change, the compiler skips extra compilation steps. This is the main performance benefit.
  • Modularity: other languages handle this differently. Java uses packages. Python uses modules. JavaScript uses modules or packages. C++ uses the header/source split as its primary modularity mechanism.

The static keyword on a function or variable gives it internal linkage, meaning it is only visible within its translation unit. This is one way to avoid naming conflicts across files.

Inline Functions

The inline keyword suggests to the compiler that it should replace a function call with the function’s body directly at the call site. This eliminates function call overhead (stack frame setup, jump, return).

inline int square(int x) {
    return x * x;
}

Rules:

  • The function implementation should be 10 lines or smaller for inline to be effective.
  • The compiler may ignore the inline hint. Modern compilers make their own inlining decisions regardless.
  • Inline functions must be defined in header files because the definition must be visible at every call site.

constexpr

constexpr declares that a value or function can be evaluated at compile time. This enables optimisations and allows use in contexts that require compile-time constants (array sizes, template parameters).

constexpr int factorial(int n) {
    return n <= 1 ? 1 : n * factorial(n - 1);
}

constexpr will make your code run faster by moving computation from runtime to compile time. Templates are particularly powerful in the context of constexpr.

Structs vs Classes

In C++, struct and class are nearly identical. The only difference is default access: struct members are public by default, class members are private by default.

Other constructs to know:

  • struct vs pair/tuple: use struct when fields have meaningful names.
  • Thread-local storage: thread_local keyword.
  • constexpr (above).
  • Internal linkages via static.
  • Type casting: static_cast, dynamic_cast, reinterpret_cast, const_cast.
  • explicit vs implicit constructors.
  • Copy and move constructors.
  • Compile-time vs run-time vs link-time distinctions.

Inheritance

C++ supports two broad types of inheritance:

  • Interface inheritance: inherit the interface (pure virtual functions) without implementation.
  • Implementation inheritance: inherit both the interface and the implementation.
class Animal {
public:
    virtual void speak() = 0;  // pure virtual: interface inheritance
};

class Dog : public Animal {
public:
    void speak() override { /* implementation */ }
};

The virtual keyword enables runtime dispatch (polymorphism). Without it, the base class method is called regardless of the derived type.

Diamond inheritance problem: if two base classes share a common ancestor, and a derived class inherits from both, the common ancestor’s members appear twice. This leads to ambiguity and bugs. Use virtual inheritance to resolve this, or prefer composition.

Composition over inheritance: when the relationship is “has-a” rather than “is-a”, prefer composition. Composition is more flexible and avoids the diamond problem entirely.

Use the final keyword on a class to prevent further inheritance:

class Leaf final : public Base { };

Operator and Function Overloading

C++ allows defining custom behaviour for operators (+, ==, <<, etc.) and multiple functions with the same name but different parameter types (function overloading).

class Vector2D {
public:
    Vector2D operator+(const Vector2D& other) const;
};

Lambda functions and friend functions are related tools: lambdas for inline callable objects, friend functions for granting external access to private members.

← All posts