首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Visual Studio C+模块快速入门

是时候入门了

Visual Studio现在已经支持C++模块了。C++模块可以对代码进行合理的划分,加速工程构建过程并且可以和现有的代码进行无缝,并行地协调工作。

此次的预览版本仅支持在IDE中在基于MSBuild工程中使用C++模块。与此同时,MSVC工具集可以被任何类型的构建系统支持,所以基于CMake的工程在Visual Studio IDE中还尚未得到支持。我们会在此项特性得到完全支持后再告诉大家。

C++模块基本概念

C++模块可以对客户代码调用的编译单元进行深度的控制。和头文件不一样,C++模块不会对客户代码公开宏定义或者私有实现细节(不再需要一些特殊宏预定义了)。

另外一个不同的地方是,只需要编译C++模块一次,就可以在整个工程中重复使用,减少不必要的重复编译过程。

为了方便模块的开发者和调用者,C++20引入了新的关键字。Visual Studio设计了一种新的文件类型”.ixx”来定义模块的接口,下面是更多的细节。

在Visual Studio中使用模块

如果你在最新的Visual Studio预览版中创建了一个全新的工程,则你不需要任何额外的 设置。但是,如果你的项目是一个已经存在的工程,则你需要确保在Visual Studio中开启了最新的C++语言标准。

以下是设置Visual Studio的C++语言标准的步骤。如果你的解决方案中有多个工程,则你需要对所有的工程都做如下相同的设置。

创建一个C++模块

为了向现有工程中添加一个模块,你首先需要创建一个模块接口。模块接口是一组以”.ixx”后缀结尾的C++源文件。在这些源文件中可以包含头文件,导入其他C++模块,以及包含从这个模块中导出的定义。你可以在一个工程中添加无限制个数的模块。

执行上述步骤后,你可以观察到解决方案视图中的模块工程。在下面这个例子中,我们创建了两个C++模块工程,它们分别是:fib和printer。

请注意:这个例子中,解决方案视图中显示了所有的”.ixx”的源文件的模块接口,实际上,任何一个C++源文件都可以被视作一个模块接口。可以通过在源文件的”Compile As”属性中设置”Compile As Module”选项来实现,你可以在源文件的高级属性设置窗口中找到这个编译开关。

导出C++模块

那么,接下来呢?

下面的例子代码定义了一个简单的模块调用了DefaultPrinter,并导出了一个简单的结构体定义。

在上面的代码中,你可以在第1,5和第7行看到新的导出语法。

第1行指定了这是一个模块接口。

第5行定义和导出了模块,第7行则导出了一个结构体。

每个C++模块可以导出任意个定义,包括结构体,类,函数以及模块等。

模块接口可以包括头文件并导入其他模块。导入它们时,除非你明确导入它们,否则它们不会从这些包含的头文件或模块中泄漏任何细节信息。这种隔离可以帮助避免命名冲突和泄漏实现细节。你可以安全地定义宏并在模块接口中使用名称空间。它们不会像传统的头文件那样泄漏实现细节。

要在模块接口中#include头文件,请确保将它们放在模块之间的全局模块片段中,并使用语句: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)

{

//…

}

在上面的例子中,我们定义了模块的名称和导出名称,对应的实现代码使用了module关键字来定义模块的实现,所有这些语法一起发生奇妙的化学反应,使之在编译时自动的组成一个高度内聚的C++模块。

使用C++模块

为了使用一个现有的C++模块,我们可以使用关键字import,如下图所示:

所有从模块中被导出的定义都可以被导入。这个例子中,我们使用了DefaultPrinter模块,并在第5行导入了这个模块。

如果是在同一工程中,你可以自动的使用的定义的模块或者任何引用。

在其他模块中使用模块

你也可以从一个模块中导入另一个模块。下面是一个例子:

上面的这个例子中,我们导入了DefaultPrinter模块并重写了print_separator函数。

其他代码可以导入这个TabbedPrinter而不用担心DefaultPrinter的实现细节,VisualStudio会自动按照正确的方式来构建它们。

外部模块

可以引用磁盘上存在的另一个模块,而不要求此模块位于解决方案中的工程中。

但是这种场景需要小心,因为模块被编译成了二进制文件。你必须确保模块和调用者之间的二进制兼容问题。

你可以通过如下的方式告诉Visual Studio来引用外部模块。

IntelliSense和模块

所有IntelliSense功能都可以无缝地在模块上使用。类似于代码自动完成,参数信息帮助,查找所有引用,转到定义和声明,重命名,以及所有其他的IntelliSense特性,它们都可以和模块一些协同工作。

在下图中,你可以看到我们在模块TabbedPrinter模块上,使用了查找所有引用和预览定义功能。具体来说,它显示了所有的DefaultPrinter结构体的引用信息并显示了定义。

你也可以转到模块的定义,如下图所示:

关于头文件单元(Header Units)

头文件单元是一种标准的C++指令,可调用行为良好的头文件(尤其是标准库头文件)的元数据(IFC文件)的生成,类似于为模块生成的目标,旨在加快整体构建时间。

但是,与模块不同,头文件单元并没有像模块那样真正提供隔离:宏定义和其他预处理器状态仍然泄漏给头文件单元的使用者。

您可以通过导入”header.h”使用头文件或导入句法。在Visual Studio中,头文件单元的元数据由生成系统自动生成。头文件(及其包含文件)中所有已声明的项目和合理的定义,以及#include文件,都可供调用者使用。

像在使用模块时一样,在导入头文件单元的代码中激活的宏定义和其他预处理器状态不会以任何方式影响所导入的头文件单元。但是,与模块不同,导入标头单元时,任何宏定义都可以在代码中使用。头文件单元主要是一种过渡机制,不能替代模块。

如果你有机会考虑命名模块与头文件单元,我们建议投入精力设计适当的模块。

我们将在以后的博客中深入介绍头文件单元,尤其是在将现有代码库迁移到模块的用法中。

最后

Microsoft Visual C++团队的博客是我非常喜欢的博客之一,里面有很多关于Visual C++的知识和最新开发进展。大浪淘沙,如果你对Visual C++这门古老的技术还是那么感兴趣,则可以经常去他们那(或者我这)逛逛。

本文来自:《A Tour of C++ Modules in Visual Studio》

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20201105A06R5700?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券