Modules (C++20)
Modules are C++20's replacement for the textual #include model. Instead of the preprocessor
pasting header text into every translation unit, a module is compiled once into a binary module
interface (BMI) that consumers import. This fixes the header model's three chronic problems: slow
builds, macro leakage, and fragile include order.
This page assumes the header model it replaces โ see Headers and Includes and Include Guards / pragma once. Modules make include guards obsolete and macros no longer leak across the boundary.
The problem modules solveโ
With #include, every translation unit re-reads and re-parses the full text of every header it
pulls in (transitively). A common header parsed across 200 source files is parsed 200 times.
A module in two filesโ
Interface unit โ declares what the module exports:
// math.ixx (or .cppm โ extension is toolchain-specific)
export module math; // declares the module
export int add(int a, int b) { // 'export' = visible to importers
return a + b;
}
int helper() { return 42; } // no 'export' = private to the module
Consumer โ imports it by name, no path, no guard:
import math; // not #include "math.h"
int main() { return add(2, 3); } // helper() is invisible here
What export controlsโ
Only export-ed names are visible to importers; everything else is module-local โ true
encapsulation the header model never had.
export module widget;
export class Widget { /* ... */ }; // part of the public interface
export void configure(Widget&);
namespace detail { // entire namespace stays internal
int compute();
}
Larger modules split into partitions (export module widget:parts;) and separate
implementation units (module widget; with no export), keeping the interface small while the
definitions live elsewhere โ analogous to the header/source split, but without re-parsing.
import std; (C++23)โ
C++23 ships the entire standard library as a module. One import replaces dozens of headers and compiles dramatically faster:
import std; // the whole standard library
int main() {
std::vector<int> v{1, 2, 3};
std::println("{}", v.size());
}
Why it's better โ and the catchโ
- Faster builds โ an interface is compiled once, not re-parsed per includer.
- Isolation โ macros and private declarations do not leak across
import. - No include guards / ordering โ import order is irrelevant; no ODR landmines from header soup.
Modules need coordinated compiler and build-system support, because a module must be built before anything that imports it (a dependency the build tool has to discover). Support in GCC, Clang, MSVC, CMake, and Ninja is solid but still maturing. BMIs are not portable โ compiler/version specific โ so they are a build artifact, never something you ship or check in.
Summaryโ
- A module is compiled once into a BMI and
imported โ replacing textual#include. exportdefines the public interface; everything else is module-local (real encapsulation).- Macros and private names don't cross an
import; include guards become unnecessary. import std;(C++23) pulls in the whole standard library, fast.- The model is sound but depends on build-system support; BMIs are non-portable build artifacts.
Relatedโ
- Headers and Includes ยท Include Guards / pragma once โ the model modules replace
- Preprocessing ยท Translation Units ยท Compilation Pipeline
- C++ Versions โ what landed in C++20/23