Skip to content

Modules

[Motivation for modules] – [Recommended module structure] – [Additional infos]

Motivation for modules

Problems with header files

  • Parsing overhead
    Header files are usually included by multiple client files (cpp). Each client file is a separate translation unit and needs to read and parse all headers it depends on. This includes the following steps:
    • the preprocessor reads the whole cpp file and all included header files and prepares a single file to be processed by the compiler (#include, #ifdef, #if statements and macros are used for text replacements)
    • the compiler parses again the file prepared by the preprocessor and usually builds an abstract syntax tree which is used for the next steps of compilation
  • Include order matters
    Macros or defines can be redefined in each header file. Therefore the definition which is available within a cpp file depends on the order in which the header files are included.
  • Avoid multiple include of the same header / #pragma once
    Often headers are not only included by a cpp file directly but also from other header files which also need access to some declaration defined elsewhere. Typically you get a nested include hierarchy. To avoid including of the same definition twice (violation of ODR = one definition rule) each header file must be protected with some guard (e.g. #pragma once) which will stop parsing if the header has already been parsed for the same translation unit.

Improvements by using modules

  • Single parsing and compilation => improvement of compile time
    A module has to be parsed only once. The result is stored as an abstract syntax tree (AST). Therefore importing a module does not cause a parsing/compilation effort but a performant usage of the prepared AST.
  • Modules can be imported in any order.
  • The code of a module doesn’t have to be separated in an interface and declaration part. The part visible to clients is defined by special export statements.
  • Module declaration file .ixx
    Nevertheless it is advisable for bigger modules to structure the code at least in a separate declaration file and one or more module implementation files (cpp).

Recommended module structure

Module interface unit

// MyService.ixx (module interface unit, module interface source)
module;

// if required for declarations below add some includes/imports here
#include <vector>

export module MyService;

export namespace MyService
{
    int DoSomething();
    int DoSomethingWithVector(const std::vector<int>& in_vec);
}

Recommendation: Use the same name for your module and the corresponding namespace.

Module implementation unit

// MyService.cpp (module implementation unit)

module MyService;

// if required for implementation add some includes/imports here (only visible inside module)
// #include <iostream> // at time of writing not available for MSVS Community 2022, Version 17.2.5

namespace MyService
{
    int DoSomething()
    {
        return 42;
    }

    int DoSomethingWithVector(const std::vector<int>& in_vec)
    {
        return static_cast<int>(in_vec.size());
    }
}

Client code

import MyService;

void TestModules()
{
    std::cout << "MyService::DoSomething() returned " << MyService::DoSomething() << "\n";

    const std::vector<int> myVec{ 1,2,3 };
    std::cout << "MyService::DoSomethingWithVector() returned "
              << MyService::DoSomethingWithVector(myVec) << "\n";
}

Output

MyService::DoSomething() returned 42
MyService::DoSomethingWithVector() returned 3

Additional infos

  • The compilation of module MyService will generate the following files:
    • MyService. ixx.ifc : meta data description of the module interface (AST)
    • MyService.obj : compiled implementation code
  • Bigger modules can be structured using
    • submodules
      “export import MyService.MySubService1” will allow client access to methods of module MySubService1 as if defined directly within MyService. The client can also use only submodule MyService.MySubservice1.
    • module partitions
      similar to submodules, but a partition MyServic:MySubservice1 cannot exist on its own and is not usable by clients
  • Templates can also be instantiated within client code when their definition is stored within a module. This is possible because all needed information for instantiation of the template is part of the AST.
  • Recommendation: replace all include statements with corresponding import statements. This will trigger the compiler to autogenerate corresponding synthesized modules which should improve the compile time. Some of your headers may fail with importing. But at least all headers of the standard library are guaranteed to be importable.