Modules: Modern Code Organization
C++20 modules replace the preprocessor-based #include system with a semantic import mechanism, offering faster compilation and better encapsulation.
Basic Module Syntax
Module Interface
// math.cppm (module interface file)
export module math;
export int add(int a, int b) {
return a + b;
}
export int subtract(int a, int b) {
return a - b;
}
Importing a Module
// main.cpp
import math;
int main() {
auto result = add(5, 3); // Uses exported add()
return 0;
}
Export Declarations
Exporting Functions
export module geometry;
export namespace geometry {
double circle_area(double radius);
double square_area(double side);
}
Exporting Classes
export module shapes;
export class Circle {
double radius_;
public:
Circle(double r) : radius_(r) {}
double area() const;
};
// Implementation can be in same file or separate
double Circle::area() const {
return 3.14159 * radius_ * radius_;
}
Exporting Templates
export module containers;
export template<typename T>
class Stack {
std::vector<T> data_;
public:
void push(const T& value) { data_.push_back(value); }
T pop();
};
Module Partitions
Split large modules into partitions:
// math-basics.cppm (partition interface)
export module math:basics;
export int add(int a, int b) {
return a + b;
}
// math-advanced.cppm (partition interface)
export module math:advanced;
export double sqrt(double x) {
// implementation
}
// math.cppm (primary module interface)
export module math;
export import :basics; // Re-export partition
export import :advanced;
Implementation Units
Separate interface from implementation:
// widget.cppm (module interface)
export module widget;
export class Widget {
public:
void process();
private:
void internal_helper();
};
// widget.cpp (module implementation)
module widget; // No 'export'
void Widget::process() {
internal_helper();
}
void Widget::internal_helper() {
// Implementation details
}
Module Linkage
Exported vs Non-Exported
export module utils;
// Exported: Visible to importers
export void public_function();
// Not exported: Module-internal only
void private_function();
namespace {
// Internal linkage
void internal_function();
}
Global Module Fragment
Import legacy headers:
module; // Global module fragment
#include <vector>
#include <string>
export module mymodule;
export class MyClass {
std::vector<std::string> data_; // Uses std types
};
Private Module Fragment
Hide implementation details completely:
export module widget;
export class Widget {
public:
void process();
};
module :private; // Private fragment
// Implementation hidden from importers
void Widget::process() {
// ...
}
// Helper functions not visible
void helper() {
// ...
}
Advantages Over Headers
1. Faster Compilation
// With headers: Reparse every #include in every TU
#include <vector> // Parsed in every .cpp file
// With modules: Parse once, import everywhere
import std; // Pre-compiled module
2. No Macro Pollution
// Header leaks macros
#define MAX(a,b) ((a)>(b)?(a):(b))
// Module doesn't leak implementation details
export module math;
// Macros don't leak to importers
3. Order Independence
// Headers: Order matters
#include "b.h" // Must come before a.h sometimes
#include "a.h"
// Modules: Order doesn't matter
import a;
import b;
4. Better Encapsulation
// Module interface
export module database;
export class Connection {
// Only public interface exported
};
// Private implementation not exposed
Importing Standard Library
import std; // Import entire standard library (C++23)
import std.core; // Core utilities
import std.io; // I/O facilities
import std.regex; // Regular expressions
Module vs Header Comparison
| Aspect | Headers | Modules |
|---|---|---|
| Parsing | Every TU | Once |
| Macros | Leak | Don’t leak |
| Order | Matters | Independent |
| ODR Violations | Possible | Prevented |
| Compile Time | Slow | Fast |
Migration Strategy
Gradual adoption:
// 1. Start with new code
export module new_feature;
// 2. Wrap existing headers
export module legacy_wrapper;
export {
#include "old_header.h"
}
// 3. Eventually refactor to pure modules
Best Practices
- One module per library: Don’t create too many small modules
- Use partitions for large modules
- Export minimal interface: Keep internals private
- Avoid global module fragment when possible
- Prefer module imports over header includes
Compilation Model
# Compile module interface (generates BMI - Binary Module Interface)
g++ -std=c++20 -c math.cppm -o math.o
# Compile code that imports module
g++ -std=c++20 -c main.cpp -o main.o
# Link
g++ math.o main.o -o program
Conceptual Check
What is the primary advantage of modules over headers?
Interactive Lab
Module Export
module math;
export int multiply(int a, int b) {
return a * b;
}