内容简介:Modules are one of the bigest changes in C++20 but the compilers’ support for them is a work in progress. The Visual C++ compiler has experimental support for modules that can be enabled by using theA typical hello world application in C++ looks like this:
Modules are one of the bigest changes in C++20 but the compilers’ support for them is a work in progress. The Visual C++ compiler has experimental support for modules that can be enabled by using the /experimental:module and /std:c++latest switches. In this post, I will walk through the core of the functionality available in Visual Studio 2019 16.5.
A first example
A typical hello world application in C++ looks like this:
#include <iostream> int main() { std::cout << "Hello, World!\n"; }
How do we transform this code so that it uses modules? Just replace the #include preprocessor directive with an import directive.
import std.core; int main() { std::cout << "Hello, World!\n"; }
The std.core module provides most of the content of the C++ Standard Library. The library is modularized as follows:
- std.regex : the content of header <regex>
- std.filesystem : the content of header <filesystem>
- std.memory : the content of header <memory>
- std.threading : the contents of headers <atomic> , <condition_variable> , <future> , <mutex> , <shared_mutex> , <thread>
- std.core the rest of the C++ Standard Library
To be able to use the modularized version of the Standard Library you must install the component called C++ Modules for v142 build tools shown in the following image:
When importing the Standard Library you should build with the /MD and /EHsc options.
To build the code snippet above, open a Developer Command Prompt from Visual Studio, and execute the following command:
cl /std:c++latest /EHsc /experimental:module /MD main.cpp
Now, if you run main.exe , you get the expected result:
Writing a module
Instead of just printing a greeting text in main() , we could get that text from a function. In the following example, this function called get_greeting_text() is exported from a module called greetings . This module is defined in an module interface unit called greetings.ixx .
The .ixx extension is required by the VC++ compiler for module interface units.
export module greetings; import std.core; export std::string get_greeting_text() { return "Hello, World!\n"; }
The main.cpp file needs to change slightly to import the greetings module and invoke the get_greeting_text() function.
import std.core; import greetings; int main() { std::cout << get_greeting_text() << '\n'; }
Now, you need to build both greetings.ixx and main.cpp . The following commands must be executed:
cl /std:c++latest /EHsc /experimental:module /MD /c greetings.ixx cl /std:c++latest /EHsc /experimental:module /MD main.cpp greetings.obj
Let’s add more to the greetings module. In the following snippet, greeter is a class with an overloaded call operator that, when invoked, returns a random greeting text.
export module greetings; import std.core; export std::string get_greeting_text() { return "Hello, World!"; } export struct greeter { constexpr static const char* hellos[] {"Hello", "Hi", "Hey"}; std::string operator()() { return hellos[rand() % 3] + std::string{", World!"}; } };
In main.cpp we will have the following:
import std.core; import greetings; int main() { std::cout << get_greeting_text() << '\n'; std::cout << greeter()() << '\n'; }
The commands for compiling this code remain the same. However, each time we execute the program now a different text will be printed to the console.
Composing a module from partitions
Modules can be split into partitions. Partitions help to organize the code of a module, especially if the module is large. Partitions are not exported as stand-alone units but as parts of a module interface unit.
To exemplify module partitions, let’s split the code of the greetings modules into two partitions: one that contains the free functions, called greetings-func and one that contains the classes, called greetings-types . These are also available in files with the extension .ixx. Here is how the look:
The content of greetings-func.ixx is:
export module greetings:func; export const char* get_greeting_text() { return "Hello, World!"; }
The content of greetings-types.ixx is:
module; #include <cstdlib> export module greetings:types; import std.core; export struct greeter { constexpr static const char* hellos[] {"Hello", "Hi", "Hey"}; std::string operator()() { return hellos[rand() % 3] + std::string{", World!"}; } };
The syntax for exporting a module partitions is export module <module-name>:<partition-name> . The rest is no different than regular module interface units.
These two partitions are then imported and re-exported from the module interface unit, greetings.ixx as follows:
export module greetings; export import :func; export import :types;
The syntax for exporting a partition is export import :<partition-name> . Of course, apart from these directives, the module interface unit may contain any other exports.
The content of main.cpp does not change. However, we need to change the commands we use to build the code, as follows:
cl /std:c++latest /EHsc /experimental:module /MD /c greetings-func.ixx cl /std:c++latest /EHsc /experimental:module /MD /c greetings-types.ixx cl /std:c++latest /EHsc /experimental:module /MD /c greetings.ixx cl /std:c++latest /EHsc /experimental:module /MD main.cpp greetings-func.obj greetings-types.obj
Building this way is possible because we leveraged a naming scheme supported by the VC++ compiler for module partition units. That is <module-name>-<partition-name>.ixx . If you do not follow this scheme, then you need to use the /module:reference switch to specify the module partition interfaces.
Internal partitions
A partition does not have to be an interface unit. It could contain code that is not supposed to be exported from the module. Such a partition is called an internal partition and must be put in a file with the extension .cpp .
To see how these work, let’s modify the previous example where we used the rand() function in the greeter class. We will remove the details of generating a new integer to another function called next_rand() available in an internal partition called greetings:details . This function is not exported from the greetings module. The content of greetings-details.cpp is shown in the following snippet:
module; #include <cstdlib> module greetings:details; int next_rand() { return rand(); }
We need to modify the code in the greetings:types partition as follows (notice the import :details directive):
export module greetings:types; import std.core; import :details; export struct greeter { constexpr static const char* hellos[] {"Hello", "Hi", "Hey"}; std::string operator()() { return hellos[next_rand() % 3] + std::string{", World!"}; } };
Nothing else needs to change except for the build commands. We have a new file to build, greetings-details.cpp and it requires a new compiler switch, /module:internalPartition to indicate that the file that is compiled is an internal partion of a module.
cl /std:c++latest /EHsc /experimental:module /MD /c greetings-func.ixx cl /std:c++latest /EHsc /experimental:module /module:internalPartition /MD /c greetings-details.cpp cl /std:c++latest /EHsc /experimental:module /MD /c greetings-types.ixx cl /std:c++latest /EHsc /experimental:module /MD /c greetings.ixx cl /std:c++latest /EHsc /experimental:module /MD main.cpp greetings-func.obj greetings-types.obj greetings-details.obj
Now, we can change the implementation details of the next_rand() function without affecting the module interface.
module greetings:details; import std.core; int next_rand() { static std::random_device rd{}; static std::mt19937 eng{rd()}; static std::uniform_int_distribution<> uid {0, 1000}; return uid(eng); }
To build the program we only need to run the following commands:
cl /std:c++latest /EHsc /experimental:module /module:internalPartition /MD /c greetings-details.cpp cl /std:c++latest /EHsc /experimental:module /MD main.cpp greetings-func.obj greetings-types.obj greetings-details.obj
Importing header units
What if the get_greeting_text() was already available in a header file, which perhaps you cannot modularize, maybe because you do not own the code? Modules support the importing of a special translation unit called header unit .
Suppose the header, called greetings.h looks like this:
#pragma once inline const char* get_greeting_text() { return "Hello, World!"; }
We can import this using the same import directive, as shown in the below snippet:
import std.core; import "greetings.h"; int main() { std::cout << get_greeting_text() << '\n'; }
To build the program, this time, the build commands must be the following:
cl /std:c++latest /EHsc /experimental:module /MD /module:exportHeader greetings.h /Fogreetings.h.obj cl /std:c++latest /EHsc /experimental:module /MD /module:reference greetings.h:greetings.h.ifc main.cpp greetings.h.obj
There are several compiler switches used here:
- /module:exportHeader specifies that a header will be exported as a header unit. It requires the path to the header.
- /Fo that specifies the name of a object file. Without this, the compiler only generates and .ifc file.
- /module:reference that has an argument of the form <path-to-header>:<path-to-ifc> .
The .ifc file is a binary file generated by the compiler when exporting a module interface. It contains metadata about the module interface and is modeled based on the Internal Program Representation (IPR) for C++, developed by Gabriel Dos Reis and Bjarne Stroustrup. The IFC is the binary module interface (BMI), which is the term found in documentation.
Exporting templates
Templates can also be exported from a module. Let’s look at an example. The following module is available in a file called foo.ixx :
export module foo; export template <typename T> struct foo { T value; foo(T const v):value(v){} }; export template <typename T> foo<T> make_foo(T const value) { return foo(value); }
In this snippet, the module foo contains a class template also called foo and function template called make_foo() that creates an instance of foo . Notice that the keyword export is preceding the keyword template . This module can be imported and its exports used in main.cpp as follows:
import std.core; import foo; int main() { auto fi = make_foo(42); std::cout << fi.value << '\n'; auto fs = make_foo(std::string("modules")); std::cout << fs.value << '\n'; }
To build this program you must use the following build commands:
cl /std:c++latest /EHsc /experimental:module /MD /c foo.ixx cl /std:c++latest /EHsc /experimental:module /MD main.cpp foo.obj
If you run this, it will print 42 and modules to the console.
See also
To learn more about modules in Visual C++ you can read the following:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
硅谷产品:36讲直通世界级产品经理
曲晓音 / 电子工业出版社 / 2018-10 / 59
《硅谷产品:36讲直通世界级产品经理》是Facebook资深产品经理曲晓音撰写的产品实战教程,立足于作者在Facebook、Instagram、Microsoft、Atlassian等硅谷科技公司的工作经验,分享硅谷先进的产品思维和方法论,用实际案例带领读者了解硅谷产品经理的所想所做。 《硅谷产品:36讲直通世界级产品经理》适合产品经理从业者、想要提升产品理解能力的技术运营人员、刚刚入行或者......一起来看看 《硅谷产品:36讲直通世界级产品经理》 这本书的介绍吧!