【C++20】vs2019使用modules的实际操作

        

目录

一、什么是modules

二、vs版本要求

三、项目配置

四、标准库模块引入方式

五、兼容旧式写法

六、导出类与变量

七、文件后缀

八、实现与主接口分开

九、模块命名与分区

十、引入模块

十一、模块分区       

十一、模块私有部分

十二、结语


        大家好,我是略游。今天讲一讲我在vs上操作C++20新标准模块(modules)的经验。此文章具有时效性,只代表当前实验结果,也可作为一个基本写法入门,将来的编译器一定会完善得更好。

一、什么是modules

        modules是C++20标准的源码组织方式,以替代#include等传统的源码组织方式。与它相关的基本知识网上有很多文章,废话不多说直接实际操作。

二、vs版本要求

        官方文档说16.10版本以上,更新最新版本即可。使用vs2022也可,不过需要注意的是需要安装以下工具:

【C++20】vs2019使用modules的实际操作_第1张图片

        虽然在随后的实验中,我没有成功使用上标准库的模块接口,但这个工具是否必须安装,我暂时懒得测试了,在这里获取工具:

三、项目配置

        首先设置C++语言标准为/std:c++ latest,并且启用标准库模块

【C++20】vs2019使用modules的实际操作_第2张图片

         在定义使用最新特性的命令行/await

【C++20】vs2019使用modules的实际操作_第3张图片

         在链接器中关闭使用增量链接,同时使用程序数据库,这二者是配套的,如果不关闭增量链接,会与模块编译有冲突,后者不能正常识别修改。

【C++20】vs2019使用modules的实际操作_第4张图片

四、标准库模块引入方式

        按照微软文档的说法,如下即可导入标准库模块:

import std.core;
import std.filesystem;
//...

         但实际操作中很困难,会提示一些编译选项冲突。除了/EHsc和/MD选项,还有一些其他的定义冲突,例如_DEBUG宏。

        文档链接如下:

Overview of modules in C++ | Microsoft Docs

【C++20】vs2019使用modules的实际操作_第5张图片

五、兼容旧式写法

        要以#include包含旧式头文件,可以如下写:

module;
#include "Head.h"
export module Test;

        这里包含了一个头文件Head.h,然后导出了一个模块叫Test。但是此处有一些局限性,Head.h不能是预编译头,预编译头是msvc的一种加快编译速度的方式,但是这里是不能兼容的。

        在Head.h里面我包含了一大串头文件,这样我们也能使用上标准库。当前这样的写法,缺点是每次修改都需要重新编译所包含的头文件内容。预编译头可以解决这个问题,等完全使用上模块后,也能解决这个问题。现在只是兼容的写法。

//Head.h
#pragma once
#include 		//!< 数组
#include 			//!< 链表
//more...

         值得一提的是,我在包含windows头文件时,遇到一些报错无法解决,最后只好不直接包含windows头文件了,暂时使用传统方法隐藏起来。

        语法很简明,在“module;”和“export module Test;”之间就是旧式的包含头文件方式。

六、导出类与变量

        前面加export即可导出类与全局变量,如下:

module;
#include "Head.h"
export module Test;

export class Test
{
    //...
};

export Test g_test;

         这里就不用再像以往一样,害怕变量重定义,不能直接放置到头文件。在以前的写法就是这样的:

//--------------Test.h
#include "Head.h"

class Test
{
    //...
};

extern Test g_test;

//--------------Test.cpp
Test g_test;

        另外constexpr常量并不能直接导出,因为它是不变量,在编译期就会替换为数值,并不会真实存在这个变量,只能通过命名空间间接导出。

//-----------failed
export constexpr size_t DEFINE_NUM_GOODS_00 = 16 * 22;

//-----------ok
export
namespace Define
{
	constexpr size_t NUM_GOODS_00 = 16 * 22;
}

七、文件后缀

        C++标准没有规定文件的后缀名必须是什么,但是到目前为止,msvc必须使用.ixx后缀名,否则编译器会报错。

        同时还应该设置文件项类型为C/C++编译器 ,如下图所示:

【C++20】vs2019使用modules的实际操作_第6张图片

         为了区分主接口文件,我们可以让实现文件后缀为.cpp,但仍然注意属性页里的项类型也要是C/C++编译器。

        直接右键添加一个模块文件,这样也是正确的:

【C++20】vs2019使用modules的实际操作_第7张图片

八、实现与主接口分开

        我们可以直接在主接口文件实现Test::Init成员函数的定义,但也可以分开文件存放。例如以下文件Test.cpp,在兼容旧写法的同时,还需表明自己是Test模块的一部分。

module;
#include "Head.h"
module Test;

void Test::Init()
{
}

        在以前的写法,将函数定义在头文件的严重缺点就是,修改此函数就会导致整个文件被修改,然后所有包含此文件的源码都需要重新编译。而模板函数必须放在头文件,不但编译速度极慢,而且无法规避这个问题。而使用模块后便可以改进这个缺点,同时我们还能保持文件分开以保证代码的整洁性。

九、模块命名与分区

        在一些示例中我们可以看到用点来分隔名字,比如export module Test.A,但实际上Test.A就是一个模块的名字,它是一个整体,并不代表Test模块的A分区(点在这里没有任何意义,只是为了好看)。正规的分区写法如下:

//Test.A是一个独立的模块
export module Test.A;

//A是Test的分区
export module Test:A;

        另外微软推荐文件名命名与模块名相似,假设一个模块叫Test.Point:Draw,那么它的文件名应该是Test.Point-Draw.ixx,实现文件可以叫Test.Point-Draw.cpp。即用-来代替:。

十、引入模块

        很简单,如下写即可:

import Test;

        如果想要“转发”模块,在自己引入X的时候,同时引入自己的地方也会引入X,如下所示:

export module Test;

export import Test.Image;
export import Test.Sprite;
export import Test.Tex;
export import Test.Text;
export import Test.UI;

         当另一个文件引入Test时,就不需要再引入Test.Image等了,只需import Test就会import Test.Image等。注意这里的.只是名字的一部分,而不是模块分区,只是为了好看。

        如果只是Test自己使用,去掉export则不会转发:

export module Test;

import Test.Image;
import Test.Sprite;
import Test.Tex;
import Test.Text;
import Test.UI;

十一、模块分区       

        前面用“.”来分隔名字只是一种权宜手段,它们本质上是单独的模块,其余文件不需要通过Test来导入Test.Image,可以直接导入Test.Image。而分区模块如果主接口没有导出,则其他文件是使用不了的。

        假设有个Test:Struct分区,在Main.cpp想要导入。那么这么写是无效的:

import Test:Struct; //error

         而用.的话,就可以:

import Test.Image; //ok

         所以用分区的区别在于此,并且从定义上来说Test:Image是真正属于Test的。

        要定义一个模块分区,如下:

export module Test:Struct;

export
struct A
{
    //...
};

        在上面的代码,在Test:Struct分区导出了一个类A。在Test主接口文件,同样可以选择import或者export import。这决定了Test:Struct是否对外界可见。

//导出模块Test
export module Test;

//导入Define
import Define;

//导入Test.Image并且使导入Test的自动导入Test.Image
export import Test.Image;

//导入Test.Ui供自己使用
import Test.UI;

//导入分区,但是不导出,也可以前面加export导出
import :Struct;

        注意import模块分区时,则必须省略冒号前面的模块名, 想必编译器也是由此判断的。因为不属于Test的模块会import Test:Struct,然而这是不允许的。而属于Test的模块,是知道自己在Test的,所以可以省略“:”前的Test。

十一、模块私有部分

        在声明module :private之后的内容只对自己文件可见,当然这是对于实现文件模块分区来说的。因为一个类或变量是否导出,取决于前面是否有export关键字。

module :private;

十二、结语

        如果你觉得此文章有帮到你,可以点击收藏,然后点击关注,这可以极大的支持我发更多的文章。

        你还可以加我的QQ群讨论:游戏编程星云阁 170100866

 

你可能感兴趣的:(C/C++,modules,c++,模块)