Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

14 Nov 2020

MSVC中的C++ Module

C++ Module区别于头文件的地方在于它可以对导入和到处的符号集合进行更好的控制。Visual Studio 16.8是刚发布的版本,在这个版本中,MSVC对C++ Module支持说是Feature Complete了。但是还是有一些小bug(例如 what is is plan for Modules support with MSVC in Visual Studio 2019 version 16.8 #759 ),只能先尝尝鲜。

A Tour of C++ Modules in Visual Studio

引入了一种新的以”.ixx"结尾的文件类型作为C++ Module的接口文件。为了支持”.ixx”,必须打开/std:c++latest选项。

非”.ixx"结尾的文件也可以被认为是模块接口文件,但是需要编辑“Compile As”属性,告诉MSVC这个情况。

模块接口文件可以包含其他头文件,导入其他模块,导出相关模块。包含的头文件需要放置在global module fragment,如下所示:

module; //begins global module fragment

#include <iostream>


// ends global module fragment
export module DefaultPrinter;

可以在ixx中放置定义。但这不是必须的,也可以把定义挪到cpp文件中:

ixx:

export module FibGenerator;
export fib gen_fib(int start, int &len);

cpp:

module FibGenerator;

fib gen_fib(int start, int &len)
{
	//...
}

如上所示,cpp中需要通过module关键字指定其所属的module。

对其他模块的录用,需要使用import关键字。

比如:

module;
#include <ranges>

#include <concepts>


import DefaultPrinter;

使用import可以导入目标模块所导出的所有符号。

在MSVC中,可以跨项目导入模块,只需引用目标项目,参考https://docs.microsoft.com/en-us/visualstudio/ide/managing-references-in-a-project

中间商模块,导入其他模块,稍微加工下,再导出一个新的模块:

module;
#include <iostream>

import DefaultPrinter;

export module TabbedPrinter;

export struct TabbedPrinter : DefaultPrinter
{
    void print_separator()
    {
        std::cout << "\t";
    }
};

Visual Studio甚至允许引用项目之外已经编译好的模块。但需要在配置中指定Additional Module Dependencies。

有了模块系统之后,传统的头文件也可以import,比如你可以import "header.h"或者import <header>。这些导入的头文件可以被预编译,然后其所有的符号都会包含到目标编译单元之中。

- Standard C++20 Modules support with MSVC in Visual Studio 2019 version 16.8

从Visual Studio 16.8开始,/std:c++latest 选项蕴含了C++ Modules功能,而不需要额外使用/experimental:module选项。/std:c++latest 也意味着启用/permissive-。

Note: enabling /permissive also disables the use of Modules.

MSVC自带的std.*模块未标准化,只能在启用/experimental:module选项的时候才可使用。

可以使用 module :private; 指定模块的私有部分。

模块的链接需要编译器和链接器协同工作。

MSVC采用的是Strong Ownership模型:

he strong ownership model brings certainty and avoids clashes of linkage names by empowering the linker to attach exported entities to their owning Modules.

参考“A Module System for C++”

通过在C/C++ -> All Options -> Additional Options 中添加 /translateInclude,可以把所有头文件编译成header units

在MSVC中,可以在C/C++ -> Advanced -> Compile As中执行目标文件的编译行为,有以下几种选项:

  • Compile as C Code (/TC)
  • Compile as C++ Code (/TP)
  • Compile as C++ Module Code (/interface)
  • Compile as C++ Module Internal Partition (/internalPartition)
  • Compile as C++ Header Unit (/exportHeader)

如果要把一个.h文件编译为Header Unit,还必须将其Item Type从C/C++ header改为C/C++ Compiler。

一个项目可以通过设置VC++ Directories -> Public Include Directories来设置对其他项目可见的目录。

编译选项大更新:

  • /module:interface /interface
  • /module:internalPartition /internalPartition
  • /module:reference /reference
  • /module:search /ifcSearchDir
  • /module:stdIfcDir /stdIfcDir
  • /module:output /ifcOutput
  • /module:ifcOnly /ifcOnly
  • /module:exportHeader /exportHeader
  • /module:showResolvedHeader /showResolvedHeader
  • /module:validateChecksum[-] /validateIfcChecksum[-]

其他参考

(完)

2020-11-21 更新

Visual Studio目前使用.ixx作为模块接口单元的文件名后缀。这个并不是所有工具都能识别。Visual Assist就是一个例子。可以参考Visual Assist的文档Allow C/C++ files with a non-standard extension来增加对.ixx的支持。但这需要修改注册表。对于Visual Studio 2019的情况,需要修改注册表路径Computer\HKEY_CURRENT_USER\SOFTWARE\Whole Tomato\Visual Assist X\VANet16下的ExtHeader或者ExtSource。但是不知为何,注册表修改后,在某个时候会被还原成初始值,导致修改无法生效。

考虑到默认的ExtHeader的值有.h;.hh;.hpp;.hxx;.ipp;.tlh;.inl;.p;.rh;.dh;.ih;.ph;.hm;;默认的ExtSource的值有.c;.cpp;.cc;.cxx;.tli;。可以从中选择出一些后缀供模块使用。比如可以选择.inl作为模块接口单元的后缀,以及.ipp作为模块分片的后缀。这么做的好处是不用动Visual Assist的注册表,只需要在Visual Studio 2019项目中为每个.inl或者.ipp结尾的文件设置相应的编译选项。相关设置在前面正文中已有介绍。另外在其他工具中(比如clang format),也要添加相应支持 。

(更新完)

comments powered by Disqus