C++模块支持已经到达VisualStudio!获取最新信息 Visual Studio预览 如果你想试试的话。 C++模块 可以帮助您划分代码,加快构建时间,并且它们可以无缝地与现有代码并排工作。
此预览仅支持MSBudio项目的IDE中的C++模块。虽然MSVC工具集由任何构建系统支持,但VisualStudio对CMake的IDE支持还不支持C++模块。我们会让你知道一旦它是!一如既往,请尝试一下,如果您有任何反馈,请告诉我们。
模块基础知识
C++模块允许您密切地控制那些对它们进行翻译的可用的内容。与头文件不同,它们不会泄漏宏定义或私有实现细节(不需要可笑的前缀)。另外,与头文件不同,它们只生成一次,然后可以在项目中多次使用,从而减少了生成开销。
C++ 20引入新的关键字来定义和消费模块,VisualStudio使用新的文件类型“.IXX”来定义模块的接口。详情请继续阅读。
Visual Studio模块入门
如果您在最新的预览版中创建了一个全新的项目,则无需执行任何操作。但是,在可以添加或使用现有项目中的模块之前,需要确保使用最新的C++语言标准。
要做到这一点,将C++语言标准设置为“预览/std:c++latest”. 如果您的解决方案中有多个项目,请记住对所有项目都这样做。
就这样!您可以使用VisualStudio使用C++模块。
创建模块
要将模块添加到项目中,需要创建模块接口。这些是普通的C++源文件,带有扩展名“.IXX”。它们可以包含标头、导入其他模块,还可以包含导出的模块定义。您可以向项目中添加任意数量的这些内容。
下面是它在解决方案资源管理器中的外观。在本例中 fib
和 printer
项目都定义C++模块。
注: 虽然这个例子显示了“.IXX”文件中的所有模块接口,但是任何C++源文件都可以被看作模块接口。为此,将源文件的“compileas”属性设置为“compileas Module”。“compileas”属性可以在任何源文件属性页的“Advanced”选项卡上找到。
导出模块
那么,模块接口实际上是什么?下面的示例定义了一个名为 DefaultPrinter
并导出单个结构:
module; //begins global module fragment #include <iostream> export module DefaultPrinter; export struct DefaultPrinter { void print_element(int e) { std::cout << e << " "; } void print_separator() { std::cout << ", "; } void print_eol() { std::cout << ''; } };
要稍微分解一下这个示例,您可以在第1、5和7行看到新的导出语法。第1行指定这是一个模块接口。第5行定义并导出模块本身,第7行导出结构。每个模块可以导出许多项,例如结构、类、函数和模板。
模块接口可以包括头和导入其他模块。在导入它们时,除非您显式导入它们,否则它们不会泄漏这些包含的头或模块的任何详细信息。这种隔离有助于避免命名冲突和泄漏实现细节。您还可以安全地定义宏并在模块接口中使用名称空间。它们不会像传统的头一样泄漏。
至 #include
在模块接口中,确保将它们放在 全局模块片段 之间 module;
和 export module mymodule;
.
本例将实现放在模块的接口中,但这是可选的。如果您在看到fibgen.ixx接口在fibgen.cpp中有相应的实现之前回顾解决方案资源管理器。
其界面如下所示:
export module FibGenerator; export fib gen_fib(int start, int &len);
有相应的实现:
module FibGenerator; fib gen_fib(int start, int &len) { //... }
在这里,接口定义了模块名和导出 gen_fib
. 相应的实现使用 module
关键字定义实现所属的模块,以便在构建时将所有内容自动组合成一个内聚单元。
消费模块
要使用模块,请使用新的 import
关键字。
module; #include <ranges> #include <concepts> import DefaultPrinter; struct DefaultFormatter { template<is_series S, is_printer T> void format(T t, S s) { while (!s.done()) { t.print_element(s.next()); t.print_separator(); } t.print_eol(); } };
所有从模块界面导出的项目都可以使用。此示例使用 DefaultPrinter
模块,在第5行导入它。
您的代码可以自动使用同一项目中的模块或任何引用的模块(使用 项目间参考 到静态库项目)。
使用其他模块中的模块
也可以从另一个模块接口导入模块。下面是一个扩展到 DefaultPrinter
以上模块:
module; #include <iostream> import DefaultPrinter; export module TabbedPrinter; export struct TabbedPrinter : DefaultPrinter { void print_separator() { std::cout << " "; } };
此示例导入 DefaultPrinter
并重写其 print_separator
功能。其他代码现在可以导入这个 TabbedPrinter
不必担心 DefaultPrinter
. visualstudio将确保一切都以正确的顺序构建。
外部模块
也可以引用磁盘上存在的模块,而不是属于解决方案中另一个项目的模块。但是,这里需要小心,因为模块是编译的二进制文件。您必须确保它们与您构建项目的方式兼容。
通过编辑“附加模块依赖项”属性,可以告诉Visual Studio在磁盘上查找模块:
智能感知和模块
你所知道和喜爱的所有智能感知功能也都可以与模块一起工作。像代码完成、参数帮助、查找所有引用、转到定义和声明、重命名等功能都可以按照您使用模块时所期望的方式跨解决方案工作。
在这里,你可以找到所有的参考资料和窥视定义与我们的合作 TabbedPrinter
上面的模块。例如,它可以显示 DefaultPrinter
从导出的结构 DefaultPrinter
模块并显示其定义:
您还可以从导入模块的任何地方访问或查看模块本身的定义:
请参阅实际操作中的模块
要查看所有这些功能,请查看我们的模块演示 CppCon 2020年 . 如果你感兴趣的话,还有很多其他最新的VisualStudio和C++ 20特性的演示。
收割台单位
头单元是一个标准的C++咒语,用来调用元数据(IFC文件)——用于行为良好的头文件,特别是标准库头——类似于为模块而生成的,目的是加快整个构建时间,如果明智的话。但是,与模块不同,头单元并不像模块那样真正提供隔离:宏定义和其他预处理器状态仍然泄漏给头单元的使用者。您可以通过 import "header.h";
或 import <header>;
语法。在visualstudio中,头单元的元数据由生成系统自动生成。头文件(及其include)中声明的所有项和合理的定义都可供使用者使用,就像 #include
文件。与模块消耗的情况一样,宏定义和其他在导入头单元的代码中活动的预处理器状态不会以任何方式影响导入的头单元。但是,与模块不同的是,当您导入头单元时,任何宏定义都可以在代码中使用。头单元主要是一种转换机制,而不是模块的替代品。如果您有机会考虑命名模块与头单元的比较,我们鼓励您在设计适当的模块时投入精力。我们将在未来的博客中深入讨论头单元,特别是它们在将现有代码库迁移到模块中的使用。
标题单元的完整IDE和工具集支持即将到来。您可以跟踪Microsoft STL的头单元支持的状态 在GitHub上 .
反馈
如果您对尝试用自己的代码尝试C++模块感兴趣,我敦促您获取最新的VisualStudio预览。如果您有任何问题或反馈,请试用并告知用户。如果您发现任何问题或有任何建议,联系我们的最佳方式是 报告问题 .