C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
91: Modules (C++20)
I've spent a lot of my career fighting with header files. You know the drill: you create a .h, you add the #ifndef guards, you declare the functions, then you jump over to the .cpp to actually write the logic. It's a ritual we've just accepted, but it's fundamentally inefficient. The compiler has to parse that header over and over again every time it's included in a different file. It's like reading the instruction manual from page one every single time you want to use a screwdriver.
The Header Headache
Let's start with something typical. I want to build a small physics helper for a game—something to handle basic 2D vector math. In the old world, I'd have a Vector2D.h and a Vector2D.cpp. If I forgot the include guards, I'd get those nightmare "redefinition" errors. If I changed one private member in the header, every single file that included it would need to be recompiled. It's a slog.
C++20 modules are supposed to fix this by treating a piece of code as a single unit that the compiler processes once. Let's see if we can actually make it happen.
Testing the Export Keyword
I'm going to create a file called Physics.cppm (the .cppm extension is common for module interfaces, though it varies by compiler). I'll start by declaring the module:
export module Physics;
export float calculateDistance(float x1, float y1, float x2, float y2) {
return __builtin_sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
Notice that export keyword. I'm telling the compiler, "Hey, let the outside world see this function." Now, let's try to use it in main.cpp using import instead of #include.
import Physics;
#include <iostream>
int main() {
std::cout << "Distance: " << calculateDistance(0, 0, 3, 4) << std::endl;
return 0;
}
This works. It's clean. No guards, no splitting declarations from definitions. But here is where it gets interesting: what happens if I have logic that the module needs, but I don't want the rest of the program to touch?
Hidden Logic and Module Boundaries
I realize my distance formula is a bit messy. I want a helper function to handle the squaring of numbers, but that's an internal detail. I don't want some other developer using square() throughout the codebase just because it exists in my physics module. Let's adjust the module:
export module Physics;
// No 'export' here!
float square(float n) {
return n * n;
}
export float calculateDistance(float x1, float y1, float x2, float y2) {
return __builtin_sqrt(square(x2 - x1) + square(y2 - y1));
}
Now, if I go back to main.cpp and try to call square(5.0f), the compiler throws a fit. It tells me square is not a member of the module Physics. This is a huge win. In the header file era, if you wanted something "private," you had to hide it in a separate .cpp file or put it in a detail namespace and pray that no one ignored the naming convention. With modules, the boundary is enforced by the language itself.
The Mixing Problem
I tried to import <iostream> instead of #include <iostream>, and depending on the compiler version, it either worked perfectly or crashed my build. This is the current "growing pain" of modules. While C++20 allows header units (importing traditional headers), the tooling is still catching up.
My rule of thumb right now: use import for your own modules and #include for the old-school standard library headers until your specific compiler toolchain tells you otherwise. It's a weird hybrid state, but it's the reality of transitioning a 40-year-old language to a new paradigm.
📋 Practical Task
Exercise: Building a Coordinate Transformation Module
Your task is to create a module that handles converting coordinates between a "World Space" and a "Screen Space."
- Create a module named
CoordinateSystem. - Inside the module, create a private helper function called
applyOffsetthat takes a value and an offset and returns the sum. This function should not be exported. - Create and export a function
worldToScreenthat takesworldX, worldYandoffsetX, offsetY. It should useapplyOffsetinternally to calculate the final screen coordinates and return them (you can use a simplestruct Point { float x, y; };for this). - In your
main.cpp, import theCoordinateSystemmodule and useworldToScreento transform a point. - Try to call
applyOffsetfrommain.cppand verify that the compiler prevents you from doing so.
There are no comments for now.