VS 2015 Update 1中C++Modules

[原文发表地址] https://blogs.msdn.com/b/vcblog/archive/2015/12/03/c-modules-in-vs-2015-update-1.aspx

[原文发表时间] 2015年12月3日 上午10:21

【这篇文章的作者是Gabriel Dos Reis和Andrew Pardoe】

VC++团队兴奋地预览着VS 2015 Update 1中的新功能: 第一次实验性的实现 C++模块系统,并提出了C++17。在2015年秋天夏威夷科纳的会议上, C++标准改进工作组通过了C++17技术规范的提案。技术规范草案的措辞是由C++标准核心工作组审查的。

Modules经常被作为一种提高编译速度的形式来讨论。但他们所提供的要比提高构建性多的多!C++Modules可以帮助你改进你的代码模块。事实上,模块化是提高编译和生成吞吐量的有效途径。Modules允许你在难以调试的预处理器状态下分离出你的代码。并且他们能使开发工具更容易的来处理你的代码。

Modules允许直接在你的代码中表达你的组件提供描述它所需要的功能的符号依赖,和这个依赖的边界。Modules通过头文件减少了复杂的预处理器voodoo去指定接口的需要。如果你没有其他选择,但不得不使用宏接口,条件编译和生成代码来工作,那这里的预处理器会是你的选择,并且和模块系统一起工作。预处理器是一个强大的工具,它虽然没有结构但它可以做C++Modules不能做的一些事情。预处理器的工作是生成语法分析器需要的预处理符号。其40年来不断创新,可以用来生成各种类型的文本,包括HTML源文件。

如果你想知道更多关于它的原则和其隐含的C++Modules设计选择的基本原理,那么现在停下来并建议你读读:C++模块系统。在2015年的CppCon上Gabriel Dos Reis也大谈C++Modules。这个视频在CppCon YouTube的频道上播放;谈话的幻灯片还可以在IsoCpp GitHub上观看。但是如果你想深入了解此功能提供了什么,请继续阅读!

在Visual C++2015 update1中的实现只是我们在持续不断努力工作的一个预览,所以我们渴望听到你的反馈意见。这是一个让你对C++主要功能产生影响的机会。我们想建立一个适合所有的开发人员与所有的编译器的C++模块系统,所以如果你有任何的反馈意见请告知我们。你可以通过modules@microsoft.com和我们联系。

在Visual C++ 2015update1中的Modules支持

首先,你应该知道模块支持完全由开关控制: /experimental:module。如果不使用此开关,则模块功能根本不会影响你的代码。另外,请注意,对模块的支持现在仅限于命令行工具。大量的IDE功能应该可以使用,但是完整的IDE集成还没有出现。此外,这次预览版本的重点是模块化。随着我们在C++语言功能支持上的提高,我们将在模块支持中优化构建吞吐量方面提高我们的注意力;如果开始没有模块化,它将太简单而不能复制一个特定的构建逻辑,但并没有对基本的问题产生重大的影响。

Modules生成

直截了当的创建并使用一个模块:开发人员仅仅声明一个源文件去包含一个模块的定义,可以写一个module M。然后她通过用export关键字包含每个模块和这些模块每个单独的声明,宣布源文件的那些部分也是模块接口的一部分。

任何顶级的声明都可以被导出,或者任何顶级声明的序列都应该被包含在括号之中。Modules不定义新的命名空间或以任何方式更改名称的查找规则。他们只允许开发人员指定并发布源代码文件的接口。但是,这里真的没有你需要学习的新名称查找规则。

在此示例中,函数f(int)和g(double,int)被当作module M的接口的一部分导出。

// file: foo.ixx
module M;
export int f(intx)
{
return 2 + x;
}
export double g(doubley, int z)
{
returny * z;
}

命令行去编译的模块接口仅仅是 cl /c /experimental:module foo.ixx。特别是扩展的“ixx”。它告诉编译器的源代码文件的内容应该是一个模块接口的源代码。如果你想要在你的接口文件上使用另一个扩展你还必须提供开关 /module:interface。(注意这些开关在将来可能会更改!)

当你编译一个模块接口文件时,你会像往常一样得到一个OBJ文件。编译器还会产生一个扩展名为”.ifc”的文件(也叫IFC文件),它包含元数据的模块接口描述。这是唯一 一次模块支持产生除了传统的编译器会做的内容之外的其它东西。ICF文件的二进制格式代码将会是开放代码;它是仿照在十年前由Gabriel Dos Reis 和 Bjarne Stroustrup仿效内部程序展现完成的。IPR的原始实现代码是开放的,可以在GitHub IRP上看到。

Modules使用

若要使用该模块,开发人员需要写import M;在源文件的上面,然后使得f(int)和g(double, int)的声明在源文件中可见。编译器的开关 /module:reference 是用来指定一个已编译的二进制模块接口文件

// file: bar.cpp
import M;
int main()
{
f(5);
g(0.0, 1);
return 0;
}

用命令行cl /experimental:module /module:reference M.ifc bar.cpp foo.obj 编译bar.cpp 。因为导入了语句,所以f(int) 和g(double,int) 的函数定义在bar.cpp文件中是可用的。如果有太多的引用文件,或者如果你把所有的IFC文件放在一个给定的目录中,你可以用一个带有文件目录名作为其参数的编译器选项 /module:search 来代替它。

概述

在文件层面,包含模块定义的源文件foo.ixx。(在我们的示例M中,模块定义的名字不需要匹配文件的名字,foo)。编译foo.ixx创建的M.ifc是一个二进制表示形式的接口文件,此外foo.obj是标准的Windows对象文件。

当使用模块(与/module:reference 开关),编译器读取M.ifc,为了使导出的顶级接口声明在目前被编译的源文件中可用,连接器还是使用foo.obj。

这三个关键字和两个开关足以让你试用C++Modules。那儿还有更多的开关可用于迁移的方案,但是他们并不值得依靠,因为他们很可能随着设计的发展而变化。

便利功能

重要的是C++Modules逐渐在你的源代码中被采纳。我们已经建立了少量的便利功能,以帮助迁移方案。所有的这些功能正在由Microsoft内部团队测试。他们可能会基于内部和外部开发者的反馈有所改变。我们还创建了一些工具来帮助操作模块接口文件,这些我们将在另一篇博客文章中讨论。

使用现有的Legacy头文件作为Modules接口

假如你现在有在宏和预处理器状态方面表现良好的源文件(在特定的头文件中)。你想使头文件就像一个模块接口一样可用。我们还创建了一个便利到编译器,它允许你假装所有用外部链接的顶层声明在C++源代码文件中都被标记为已导出。你使用开关 /module:name/module:export 可以自动从头创建一个编译好的模块接口(IFC)。 /module:name 指定参数的模块接口(.IFC)名称和 /module:name 指定使用的头文件来创建接口。请注意,由于我们编辑器中文件处理的限制,你目前已将你的头文件包含在一个.cpp文件中(或者重命名你的头文件)。

// file: foo.cpp
int f(intx)
{
return 2 + x;
}
double g(doubley, int z)
{
returny * z;
}

命令行cl /c /experimental:module /module:name mymodule /module:export foo.cpp产生mymodule.ifc,f(int)和g(double,int)带有定义的接口文件。

Module搜索路径

/module:search 指示编译器应该通过 /module:reference文件的目录来寻找引用。例如,在bar.cpp上面 ( 在使用模块中 ) 的编译命令行本来可以被写成cl /experimental:module /module:search . bar.cpp foo.obj 以便在当前的目录下搜索IFC文件。

保留宏

最后,如果你在旧的源文件中定义了对于使用者来说必不可少的宏,你可以让编译器生成封装的头文件,该文件包含一个导入声明和其后这些宏的预处理器定义。当编译器完成定义模块的源文件编译,编译器开关 /module:exportActiveMacros 就会导出所有宏的定义。如果你想选择你可以使用 /module:exportMacro来代替,在封装文件中指定你想要定义的宏。通过带有参数文件名的开关 /module:wrapper 来指定封装头文件的名称。

// file: baz.h
#ifndef BAR_INCLUDED
#definenumber 6
int f(intx)
{
return 2 + x;
}
double g(doubley, int z)
{
returny * z;
}
#endif// BAR_INCLUDED

// file: baz.cpp
#include“baz.h

不出所料,用cl /c /experimental:module /module:export /module:name mymodule baz.cpp /module:wrapper baz-wrapper.h /module:exportActiveMacros编译上面的源文件将会产生mymodule.ifc,但会产生另外一个头文件,baz-wrapper.h,包含以下代码:

 

#ifndef mymodule_WRAPPER_INCLUDED
#definemymodule_WRAPPER_INCLUDED
import mymodule;
#definenumber 6
#endif// mymodule_WRAPPER_INCLUDED

你现在可以包含baz-wrapper.h文件,而不是baz.h,即使baz.h起初不是用作模块,你仍然可以得到其模块化的好处。这种方法将保留你预处理器的状态,使你有机会清理任何你当时没有意识到的活跃的宏。令人遗憾的是,通过rogue预处理器状态排序是太常见的经历。

工作愉快!

即使这是初期,但VS 2015 update 1中最令人兴奋的功能还是C++Module。还将有更多的好功能会产生——显然,我们缺乏一些基本功能,例如集成VS IDE和构建系统——但我们想要早点把事情做完,为了我们开发者群体可以有机会在工作方式上得到大的改善。请试试C++Module并让我们在modules@microsoft.com知道你的想法。