Functions are the smallest unit of behavior in C++. Interfaces are the contracts that define how parts of a system communicate. Modular design is the discipline that keeps your codebase clean, scalable, and understandable.
This chapter teaches you how modern C++ uses functions and interfaces to build modular systems — the foundation for everything from small libraries to full engines.
A good function answers three questions:
Modern C++ encourages functions that are:
Example of clear intent:
float Length(const Vec3& v);
Example of unclear intent:
float doStuff(Vec3* a, Vec3* b);
The second version hides ownership, purpose, and behavior.
Modern C++ uses function signatures to express ownership:
void Translate(Vec3 delta);
void SetColor(const Color& c);
void Normalize(Vec3& v);
void AttachChild(Node* child);
Vec3 Add(const Vec3& a, const Vec3& b);
Clear ownership leads to clear architecture.
A pure function has no side effects:
float Dot(const Vec3& a, const Vec3& b);
A mutating function changes state:
void Rotate(Transform& t, float angle);
Modern C++ encourages pure functions whenever possible because they:
Mutating functions are used when behavior is intentional and meaningful.
An interface is a promise:
In C++, interfaces are expressed through:
Example of a simple interface:
class Drawable {
public:
virtual void Draw() const = 0;
virtual ~Drawable() = default;
};
This defines a contract without specifying implementation.
Abstract classes define behavior without data.
class InputDevice
{
public:
virtual bool IsPressed(int key) const = 0;
virtual ~InputDevice() = default;
};
Derived classes implement the behavior:
class Keyboard : public InputDevice
{
public:
bool IsPressed(int key) const override;
};
Modern C++ uses polymorphism sparingly — only when dynamic behavior is required.
Static polymorphism avoids virtual calls:
template<typename T>
void Draw(const T& drawable)
{
drawable.Draw();
}
This is resolved at compile time and is often faster.
C++20 concepts make constraints explicit:
template<typename T>
concept DrawableConcept = requires(T t)
{
{ t.Draw() } -> std::same_as<void>;
};
Use static polymorphism when:
A good interface:
Example of a clean interface:
class Timer
{
public:
float DeltaTime() const;
void Update();
};
Example of a poor interface:
class Timer
{
public:
float* GetInternalPointer();
};
The second version leaks internal representation.
Headers define module boundaries.
A good header:
Example:
// Transform.h
#pragma once
#include "Vec3.h"
struct Transform
{
Vec3 position;
Vec3 rotation;
Vec3 scale;
};
This header is clean, minimal, and modular.
Modern C++ encourages:
This separation:
Example:
// Math.h
float Dot(const Vec3& a, const Vec3& b);
// Math.cpp
float Dot(const Vec3& a, const Vec3& b)
{
return a.x*b.x + a.y*b.y + a.z*b.z;
}
A module should:
Examples of good modules:
Each module has a clear purpose and clean boundaries.
In this chapter, you learned:
These principles prepare you for the architectural chapters that follow.