C++20 introduced modules, a modern way to structure and manage code that replaces the traditional #include
system. Modules improve compilation speed, enhance encapsulation, and resolve longstanding issues like macro conflicts and double inclusion. This guide will help you understand the basics and practical uses for C++20 modules. This guide is part of our C++20 Masterclass, where we cover modules in detail and provide real-world examples to help you master their use in professional projects. This guide assumes you have a C++20 ready enviroment. If you don’t, you can follow our C++20 installation guide.
Modules organize C++ programs into logical units. They consist of module interfaces (public-facing parts) and module implementations (private parts). Unlike header files, modules avoid textual inclusion, parsing code only once per build.
Key Benefits of Modules:
C++20 does not enforce a file extension for modules, but common conventions have emerged:
For clarity and consistency, this post uses the .ixx extension.
Below is an example of a simple module with a single file:
//Module code stored in a file named math.ixx
module;
#include <iostream>
export module math;
//Import statements for other modules usually show up here
//What follows after here is the module purview
export int add(int a, int b) {
return a + b;
}
export class Point {
public:
int x, y;
Point(int x, int y) : x(x), y(y) {}
};
This file declares a module named math with an add function and a Point class. The export keyword makes these entities visible outside the module. The module name is specified in the export module math;
line. The module name doesn’t have to match the file name, but it’s a common practice for clarity. Our module file is made up of three parts:
Global Module Fragment: This part is optional but comes right after the module; line. It’s often used to include any necessary headers or other code that applies to the entire module. In this example, the global module fragment only includes the
Module Preamble: This follows the export module
Module Purview: This part contains the bulk of the module’s implementation, such as function and class definitions. It’s where the actual logic of the module lives, and it is typically not visible outside unless explicitly exported.
In the name of readability and maintainability, it’s a good practice to separate declarations from definitions. It is possible to do that in a single module file like this:
module;
export module math; // A module named math
export int add(int a, int b); // Declaration
int add(int a, int b) { // Definition
return a + b;
}
For larger projects, declarations and definitions can reside in different module files. In the example below, the declaration lives in the module interface file named math.ixx, while the definition is in the module implementation file named math.cppm:
Module Interface File (math.ixx):
module;
export module math;
export int add(int a, int b); // Declaration
Module Implementation File (math.cppm):
module;
module math;
int add(int a, int b) { // Definition
return a + b;
}
Notice that in the module implementation file, we use the module math;
directive to specify that this file is part of the math module. We can now use our math module in the main.cpp file as shown below:
import math;
#include <iostream>
int main() {
std::cout << "3 + 4 = " << add(3, 4) << '\n';
return 0;
}
The export import directive allows you to import a module and make it available to other modules that import the current module. This is useful when you want to re-export a module that you import. Here’s an example.
File: utility.ixx
export module utility;
export int multiply(int a, int b) {
return a * b;
}
File: math.ixx
export module math;
export import utility; // Re-export the utility module
export int square(int x) {
return multiply(x, x);
}
File: main.cpp
import math;
#include <iostream>
int main() {
//Use the multiply function from the utility module
std::cout << "3 * 4 = " << multiply(3, 4) << '\n';
return 0;
}
The export import
directive in the math module allows the utility module to be used in the main.cpp file without explicitly importing it. This makes it easier to manage dependencies and keep the codebase clean.
Submodules allow you to organize a module into smaller, more manageable parts. Suppose that we have a graphics module made up of the image and render submodules. Here’s how you can structure the code.
File: graphics_image.ixx
export module graphics.image;
export void load_image() {
// Image loading logic
}
File: graphics_render.ixx
export module graphics.render;
export void render_image() {
// Image rendering logic
}
File: graphics.ixx
export module graphics;
export import graphics.image;
export import graphics.render;
File: main.cpp
import graphics;
int main() {
load_image();
render_image();
return 0;
}
Please note that the directive such as export import graphics.image;
has nothing special in it. The only apparent difference with what we are used to is that it has a dot in it. The compiler doesn’t do anything different with the dot. graphics.image
is just a name, and it doesn’t have any special meaning to the compiler. But we can use it to organize our code in a way that makes sense to us. Looking at our code, one understands that image and render are live under the same roof.
The powerful thing here is that you can choose to use the graphics.image
module on its own without the graphics.render
module. This is a great way to manage dependencies and keep your codebase clean. The graphics module acts as a facade that exposes the image and render submodules to the outside world. You can use it if you want both image and render functionalities imported in one go.
C++20 modules represent a significant advancement in modern C++ development, offering a more efficient and maintainable approach to code organization. They help reduce compilation times and enhance encapsulation, making your projects more manageable and scalable. By following the steps outlined in this guide, you can begin incorporating modules into your work and reap the benefits they provide. For a deeper dive into C++20 modules, including advanced topics and best practices, you can join our C++20 Masterclass. There, we cover modules in detail and provide real-world examples to help you master their use in professional projects.
Fluid and dynamic user interfaces for desktop, mobile and embedded devices.
Learn and Master Qt, QML and C++ with Minimal Effort, Time and Cost
The discounts are available for limited time only!
Clear, up front courses on Qt. We have made it our business to provide the best online learning resources for Qt Development. We put in the required effort to make sure the code resources coming with the courses are up to date and use the latest tools to reduce the hustle for students as much as possible. We already have the basic courses on Qt C++ and QML out and there are more in the pipeline.