C++ 20就在拐角处。伴随着新标准而来的是备受期待的模块特性!编译器团队 最初宣布 我们早在2017年就在开发TS模块,从那时起,我们就一直在努力改进该功能,并围绕该功能改进编译器的一致性。我们终于觉得是时候分享我们在模块一致性方面取得的一些进展了。
有什么新鲜事吗?
- 收割台单位 是一种新型的翻译单元,类似于便携式PCHs。
- 上下文敏感
module
和import
关键词 在代码中将这些术语用作标识符时,为用户提供了更大的灵活性。 - 全局模块片段 是在构成模块接口时将非模块代码与模块接口代码分离的一种方法。
- 模块分区 是一种模块接口,组成一个较大的模块接口。
- 智能感知 Visual Studio 2019版本16.6预览版2的状态。
收割台装置支架
在C++ 20中 [模块导入]/5 描述导入新的翻译单位类型,即标题单位。此类导入的语义在[module.import]/5中作了进一步阐述,其中一个更重要的信息是,在导入的头中定义的宏也会被导入: myheader.h
#pragma once #include <cstdio> #define THE_ANSWER 42 #define STRINGIFY(a) #a #define GLUE(a, b) a ## b
main.cpp
import "myheader.h"; int f() { return THE_ANSWER; } int main() { const char* GLUE(hello_,world) = STRINGIFY(Hello world); std::printf("%s", hello_world); }
可以使用新的 /module:exportHeader
开关: $ cl /std:c++latest /W4 /experimental:module /module:exportHeader myheader.h /Fomyheader.h.obj$ cl /std:c++latest /W4 /experimental:module /module:reference myheader.h:myheader.h.ifc main.cpp myheader.h.obj
注意使用 /module:exportHeader
,此选项的参数是指向某个头文件的路径(相对或绝对)。输出 /module:exportHeader
是我们的.ifc格式。同时,在进口方面 /module:reference
有一个新的参数形式 <path-to-header>:<path-to-ifc>
以及 /module:reference
可以是相对的,也可以是绝对的。同样重要的是要指出 /Fo
切换编译器将 不 自动生成一个对象文件,编译器将只生成.ifc。
另一个 /module:exportHeader
是供用户(或构建系统)向它提供一个文本参数,它表示编译器将看到的某个头名称。一个简单的例子是: $ cl /std:c++latest /EHsc /experimental:module /module:exportHeader "<vector>" /module:showResolvedHeader<vector>Note: resolved <vector> to 'C:<path-to-vector>incvector'
这种用法 /module:exportHeader
使编译器能够使用标头搜索机制构建标头单元,就像参数是在源代码中编写的一样。这个功能还附带了一个助手开关, /module:showResolvedHeader
,以发出通过查找找到的头文件的绝对路径。
读者注意:有一个已知的限制 /module:exportHeader
以及它与 /experimental:preprocessor
这两个开关目前不兼容,将在将来的版本中解决。
上下文敏感 module
和 import
关键词
在两个模块中 module
和 import
作为关键字处理。此后,人们意识到,这两个术语通常被用作用户代码的标识符,因此许多建议被接受到C++ 20中,这就给何时添加更多的限制。 module
和 import
是关键字。其中一个建议是 第1703R1页 这就增加了对上下文的敏感度 import
标识符。另一个尚未被接受的建议是 P1857R1页 . P1857R1有趣的是,它是定义时间最严格的文件 module
和 import
是关键字或标识符。
从16.5开始,MSVC将实现P1703R1和P1857R1。实施这两份文件中概述的规则的结果是:
#define MODULE module #define IMPORT import export MODULE m; IMPORT :partition; IMPORT <vector>;
不再有效,编译器将处理两者的宏扩展 MODULE
和 IMPORT
作为标识符,而不是关键字。对于更多类似的情况,请参阅论文,特别是P1857R1提供了一些有用的比较表,描述了受变化影响的场景。
全局模块片段
由于模块合并到C++ 20中,又引入了另一个新概念,称为全局模块片段。全局模块片段仅用于组成模块接口,该区域的语义借用了模块TS中描述的关于连接到全局模块的实体的语义。全局模块片段的目的是作为用户放置预处理器指令的空间,如 #include
这样模块接口就可以编译,但是全局模块片段中的代码不属于模块接口,也不直接由模块接口导出。举个简单的例子:
module; #include <string> #include <vector> export module m; export std::vector<std::string> f();
在这个代码示例中,用户希望同时使用这两种方法 vector
和 string
但不想导出它们,它们只是希望导出的功能的实现细节, f
. 全局模块片段尤其是 module;
和 export module m;
. 在这个区域,唯一可以编写的代码是预处理器指令; #if
和 #define
这是公平的游戏。需要注意的是,如果翻译单元的前两个标记 不 module;
接口单元被视为全局模块片段不存在,并且此行为通过 [cpp.global.frag]/1 .
模块分区
模块划分为用户提供了一种组成模块接口单元和组织模块代码的新方法。在其核心,模块分区是一个更大的模块接口单元的一部分,不能独立作为一个接口导入到模块单元之外。下面是一个使用分区的简单模块接口的快速示例: m-part.ixx
export module m:part; export struct S { };
m.ixx
export module m; export import :part; export S f() { return { }; }
main.cpp
import m; // 'm' is also composed of partition ':part'. int main() { f(); }
要编译示例: cl /experimental:module /std:c++latest /c m-part.ixxcl /experimental:module /std:c++latest /c m.ixxcl /experimental:module /std:c++latest main.cpp m.obj
注意,我们没有显式地添加 /module:reference
对于编译器的任何调用,这是因为我们为模块分区引入了一个命名方案,它简化了功能的使用,就像我们对普通模块接口单元的使用一样,在普通模块接口单元中,文件名直接表示模块名。模块分区使用的模式是 <primary-module-name>-<module-partition-name>
. 如果您的模块分区遵循这种模式,编译器可以自动找到分区的接口单元。当然,如果您真的想在命令行上指定模块接口,只需添加适当的 module:reference
论据。
该标准通常将分区称为接口单元 [模块单元]/3 但是,有一个例外,我们称之为“内部”分区。这些内部分区不是接口,仅用于促进模块单元的实现细节。导出内部分区显然是格式错误的(参见[module.unit]第4节中的翻译单元3)。MSVC通过一个新的交换机来实现内部分区的创建 /module:internalPartition
. 使用内部分区的示例:
m-internals.cpp
注意.cpp扩展名
module m:internals; void g() { } // No declaration can have 'export' in an internal partition.
m.ixx
export module m; import :internals; // Cannot export this partition. export void f() { g(); }
要编译此接口: cl /experimental:module /std:c++latest /module:internalPartition /c m-internals.cppcl /experimental:module /std:c++latest /c m.ixx
如前所述 :internals
分区只能用于实现部分模块接口 m
也不能直接贡献。
智能感知
(自Visual Studio 2019版本16.6预览版2起的状态) 热心的读者可能已经注意到了对消费模块的智能感知的初步理解。虽然在IDE中,模块的生产和消费还远远不够,我们打算提供,当我们走向C++ 20的一致性时,它显示了我们正在构建的初始能力。一旦翻译单元使用具有 import
使用的属性页进行配置 /std:c++latest
, /experimental:module
,以及任何必要的模块查找路径选项,并且生成导入的模块时,IntelliSense处理应拾取相关的.ifc文件。在程序中键入名称空间、自由函数及其参数后,现有的支持将从导入的模块中识别它们。但是,名称将不会在自动完成/成员列表中提供,并且处理可能会在其他语言构造(如类或模板)上失败。请继续关注我们在未来版本中扩展的支持!
结束语
C++ 20带来了许多新概念(字面上和比喻上)到C++,模块是我们将来如何编写代码的最大贡献者之一。这些MSVC一致性更改将帮助用户方便地过渡到思考我们如何组织和推理api的接口。正如我们所有的预览功能一样,模块的开关和编译器行为一旦我们准备好声明工具集C++ 20完成,就可能发生变化。
我们敦促您走出去,尝试使用MSVC与模块。16.5现在可以通过 Visual Studio 2019下载 呼叫!
一如既往,我们欢迎您的反馈。欢迎通过电子邮件发送任何评论 visualcpp@microsoft.com 或通过 推特@visualc . 另外,请随时在Twitter上关注我 @星际克隆 .
如果您在VS 2019中遇到MSVC的其他问题,请通过 报告问题 选项,从安装程序或VisualStudioIDE本身。如需建议或错误报告,请通过 开发命令。