百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

c++20 module的理解(c++mod用法)

haoteby 2025-04-01 18:10 31 浏览

在c++20之前,在一个模块中(.cpp)想要获取别的模块的声明, 就需要使用#include去包含其他模块的头文件。 c++20引入了module关键字,使得c++拥有了类似于java和python的包管理机制,本文就来讲解一下module这个语法糖。

include头文件与module方式的对比

#include头文件有下面这些负面影响:

  • 低效:头文件的本职工作是提供前置声明,而提供前置声明的方式采用了文本拷贝,文本拷贝过程不带有语法分析,会一股脑将需要的、不需要的声明全部拷贝到源文件中。
  • 传递性:最底层的头文件中宏、变量等实体的可见性,可以通过中间头文件“透传”给最上层的头文件,这种透传会带来很多麻烦。
  • 降低编译速度:加入 a.h 被三个模块包含,则 a 会被展开三次、编译三次。
  • 顺序相关:程序的行为受头文件的包含顺影响,也受是否包含某一个头文件影响,在 C++ 中尤为严重(重载)。
  • 不确定性:同一个头文件在不同的源文件中可能表现出不同的行为,导致这些不同的原因,可能源自源文件(比如该源文件包含的其他头文件、该源文件中定义的宏等),也可能源自编译选项。

而module模块机制则有以下一些优势:

  • 无需重复编译:一个模块的所有接口文件、实现文件,作为一个翻译单元,一次编译后生成 pcm,之后遇到 Import 该模块的代码,编译器会从 pcm 中寻找函数声明等信息,该特性会极大加快 C++ 代码的编译速度。
  • 隔离性更好:模块内 Import 的内容,不会泄漏到模块外部,除非显式使用 export Import 声明。
  • 顺序无关:Import 多个模块,无需关心这些模块间的顺序。
  • 减少冗余与不一致:小的模块可以直接在单个 cppm 文件中完成实体的导出、定义,但大的模块依然会把声明、实现拆分到不同文件。
  • 子模块、Module Partition 等机制让大模块、超大模块的组织方式更加灵活。
  • 全局模块段、Module Map 制使得 Module 与老旧的头文件交互成为可能。

c++20 module 的 Helloworld

下面的例子是所有程序员都爱写的helloworld。

//g++ -std=c++20 -fmodules-ts -xc++-system-header iostream
//g++ main.cpp -o main -std=c++20 -fmodules-ts
import ;

int main()
{
    std::cout << "Hello, World" << std::endl;
}

但是想跑通这个helloworld并不简单,系统库iostream的module并不会自动生成,而需要我们使用生成。

可以使用下面的命令生成iostream的module:

g++ -std=c++20 -fmodules-ts -xc++-system-header iostream

这个操作会在当前目录下生成一个gcm.cache目录,其目录结构如下所示:

$ tree gcm.cache/
gcm.cache/
└── usr
    └── include
        └── c++
            └── 11
                └── iostream.gcm

4 directories, 1 file

其次,在编译main.cpp 时需要添加-fmodules-ts的flag,即使用下面的编译语句:

g++ main.cpp -o main -std=c++20 -fmodules-ts

经过这样的操作之后,可以成功的编译,并打印Hello, World。

c++20 module管理

为了支持module, c++20 引入了三个关键字export/import/module。下面一一解读。

export关键字

export关键字用于声明一个module名和标记内容的导出性。

export(optional) module module-name module-partition (optional) attr (optional) ;   (1) 
export declaration  (2) 
export { declaration-seq (optional) } (3)

语句1声明了一个模块的名字,标记当前是一个Module单元。

语句2和语句3声明内容是可以导出的,即外部可以见的。

例如下面的例子:

export module A; // (1)declares the primary module interface unit for named module 'A' 

// hello() will be visible by translations units importing 'A'
export char const* hello() { return "hello"; } (2)

// world() will NOT be visible.
char const* world() { return "world"; } (3)

// Both one() and zero() will be visible.
export  //(4)
{
    int one()  { return 1; }
    int zero() { return 0; }
}

// Exporting namespaces also works: hi::english() and hi::french() will be visible.
export namespace hi //(5)
{
    char const* english() { return "Hi!"; }
    char const* french()  { return "Salut!"; }
}

语句1声明了一个模块的名字,关于module前面加不加export的区别,将在module关键字中讲解。

语句2声明hello函数是可以导出的。

语句3没有export,代表其是不可以导出的。

语句4同时导出了两个函数。

语句5导出了整个namespace。

import关键字

import关键字用于导入一个module。

export(optional) import module-name attr (optional) ;

如果导入的模块仅仅希望在当前编译单元可见,则不要加上export, 否则需要加上export。

在下面的例子,在A.cpp中,声明了module A,在moduleA中,hello函数是可以导出的。

在B.cpp文件中,声明了module B,在module B中,导入了module A,并使得moduleA中的内容对外可见,也声明world函数是可以导出的。

在main.cpp中,import了B模块,因为B模块中的world是可以导出的,同时由于B模块引入的A模块时使用了export,因此main方法可以调用hello和world方法。

/////// A.cpp (primary module interface unit of 'A')
export module A;

export char const* hello() { return "hello"; }

/////// B.cpp (primary module interface unit of 'B')
export module B;

export import A;//A is visible for other compile unit

export char const* world() { return "world"; }

/////// main.cpp (not a module unit)
#include 
import B;

int main()
{
    std::cout << hello() << ' ' << world() << '\n';
}

module关键字

module用于声明一个模块,其前方也可以带上export。下面将具体讲解module的用法。

module可以用于声明一个模块

export module代表纯接口或者是接口和实现在一起, 单独只有module代表纯实现。可以通过下面的例子去理解二者的区别:

  • module接口和实现单元在一起:
//Hello.cpp
export module Hello;
export const char* hello(){
    return "hello";
}
//main.cpp
//g++ -fmodules-ts -std=c++20 Hello.cpp  main.cpp
import Hello;
int main(){
    hello();
}
  • module接口声明单元和接口实现单元分开:
//Hello.cpp
export module Hello;
export const char* hello();

注意Hello_Impl.cpp中的hello是不能添加export的,export只出现在有export module的接口声明单元中,而下面的是接口实现单元。

//Hello_Impl.cpp
module Hello;
const char* hello(){
    return "hello";
}
//g++ -fmodules-ts -std=c++20 Hello.cpp Hello_Impl.cpp  main.cpp
import Hello;
int main(){
    hello();
}

module可以用于声明全局模块片段(global module fragement)

module;语句之后可以跟一些预处理指令,例如#include#define等。

其存在的原因可以通过下面的例子说明:

对于第一种采用#include方式的头文件包括,尽管_UNICODE宏可以改变头文件windows.h中的条件编译,但该头文中的所有的可导出符号(exportable symbol)都会附加到相应导入模块(importing module)空间(既具有模块链接(module linkage))。

而对于第二种采用import指令的头文件单元导入方式,_UNICODE宏不能影响头文件windows.h的条件编译。

// legency include preprocessor directive
#define _UNICODE
#include 
// `header-unit import` preprocessor directive
#define _UNICODE
import ;

下面是一个完整的例子,module;export module A之间的内容就是global module fragement。

/////// A.cpp (primary module interface unit of 'A')
module;

// Defining _POSIX_C_SOURCE adds functions to standard headers,
// according to the POSIX standard.
#define _POSIX_C_SOURCE 200809L
#include 

export module A;

import ;

// Only for demonstration (bad source of randomness).
// Use C++  instead.
export double weak_random()
{
    std::timespec ts;
    std::timespec_get(&ts, TIME_UTC); // from 

    // Provided in  according to the POSIX standard.
    srand48(ts.tv_nsec);

    // drand48() returns a random number between 0 and 1.
    return drand48();
}

/////// main.cpp (not a module unit)
import ;
import A;

int main()
{
    std::cout << "Random value between 0 and 1: " << weak_random() << '\n';
}

module 分区

module可以定义分区,例如定义一个module A, 再定义一个module A:Bmodule A:CA:CA:B同隶属于module A

///////  A.cpp   
export module A;     // primary module interface unit

export import :B;    // Hello() is visible when importing 'A'.
import :C;           // WorldImpl() is now visible only for 'A.cpp'.
// export import :C; // ERROR: Cannot export a module implementation unit.

// World() is visible by any translation unit importing 'A'.
export char const* World()
{
    return WorldImpl();
}
/////// A-B.cpp 
export module A:B; // partition module interface unit

// Hello() is visible by any translation unit importing 'A'.
export char const* Hello() { return "Hello"; }
/////// A-C.cpp 
module A:C; // partition module implementation unit

// WorldImpl() is visible by any module unit of 'A' importing ':C'.
char const* WorldImpl() { return "World"; }
/////// main.cpp 
// g++ -fmodules-ts -std=c++20 A-B.cpp A-C.cpp A.cpp main.cpp
import A;
import ;

int main()
{
    std::cout << Hello() << ' ' << World() << '\n';
    // WorldImpl(); // ERROR: WorldImpl() is not visible.
}

module : private

从gcc的官方说明中得知,该点还没有被实现,
https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Modules.html。

Private Module Fragment The Private Module Fragment is recognized, but an error is emitted.

总结

  • c++20中开始支持module机制,新增加了module/import/export三个关键字,类似于java和python语言的包管理机制,旨在取缔头文件包含方式。目前主流的编译器并没有完全支持module中的所有内容,对于新项目而言可以尝试使用,老项目想要使用将带来一些额外的工作量。

相关推荐

DIY桌面激光雕刻机#是时候展现真正的技术了

激光雕刻机。这期视频我们来看一下我是如何DIY一台桌面激光雕刻机。前几天在水池子边上发现了一台旧电脑,我看这电脑上还有一些东西可以利用到,比如光驱上面拆出了步进电机和滑轨。所以本期视频我将用这些废品去...

100000块多米诺骨牌拼成超级马里奥,这款机器人1天就完成了

智东西(公众号:zhidxcom)编译|王健恩编辑|高歌智东西7月30日消息,美国工程师兼YouTube博主MarkRober创造出了一个可以自动摆放多米诺骨牌的机器人。这个机器人被命名为D...

这个3D打印机器人可以在30秒内打开密码锁

密码锁看似很安全?也许曾经是,但现在你可要当心了!这是因为一台3D打印制造的机器人就可以在半分钟内打开你的密码锁。上周四,知名黑客萨米·卡姆卡尔(SamyKamkar)在自己的网站上公布了一个称之为...

密码锁也不安全 这款机器人30秒即可自动打开

大学生和体育爱好者们要注意了,千万不要再把贵重物品存放在公共储物柜里。因为现在已经出现了一种3D打印的机器人,据说世界上各大锁商推出的大部分密码锁,它都能够在30秒之内打开。著名黑客山米·卡姆卡(Sa...

硬件单片机模拟器,再也不用买开发板了...

#头条创作挑战赛#记得2006年在凌阳科技(sunplus)工作的时候,凌阳科技开发了自己的编译器/集成开发环境(unspIDE),那个IDE除了有keil那样的编辑器、编译器、链接器、调试器、下载...

3D打印机分哪几部分构成?(3d打印机结构组成及系统分析)

3D打印机的构成根据技术类型(如FDM、SLA、SLS等)有所不同,但以最常见的FDM(熔融沉积成型)3D打印机为例,其核心组成部分可分为以下模块:1.机械结构框架提供整体支撑和稳定性,常见材质为金...

初学者学伺服都需要什么?石家庄诺仕通

#初学者学伺服都需要什么?#对于初学者学习伺服系统,需要从...

arduino(arduino是单片机吗)

arduino学习笔记arduino学习笔记1-什么是arduino?...

自制写字机,你需要的全套资料都在这里

小编之前发过《用废旧光驱制作迷你绘图仪》,很多读者都成功制作了自己的绘图仪。但是该方法的缺点是gcode要在inkscape软件中生成,然后通过grbl-controller这个没有界面的程序发送画图...

自己动手DIY3D打印机 瞬间效果出现桌面时,大家都惊呆了!

3D打印机,对数码产品比较了解的朋友都知道,但是真正玩过的童鞋可能就不多了。其实3D打印机离我们并不远,随着3D打印技术越来越成熟,3D打印机的学习资料也越来越多,这让自己动手做一台桌面3D打印机也成...

机器人仅用24小时将十万块多米诺骨牌拼出马里奥,创下世界纪录

十万块多米诺骨牌倒下是个啥场面?等等,十万块?那得搭多久啊?...

如何制作一个机器人?(制作机器人的方法)

1.简单机械机器人(例如自动小车)2.智能机器人(带有人工智能或计算机视觉)3.工业机器人(用于生产自动化)4.人形机器人(类人结构,可以行走、对话)...

CrowPi2树莓派4学习套件评测第1部分–开箱和首次启动

文章来源:CNXSoftware中文站2020年6月,我曾写过一篇关于深圳易科诺...

基于 Arduino UNO 的蓝牙汽车(arduino智能小车蓝牙控制app)

HC-05蓝牙模块HC-05是一款易于使用的蓝牙SPP模块,针对流畅的串行无线通信配置进行了优化。串口蓝牙模块是完全合格的蓝牙V2.0+EDR(增强数据速率)3Mbps调制,具有总2.4...

电机驱动设计方案带你初识机电一体化

在直流电机驱动电路的设计中,主要考虑以下几点:功能:电机是单向还是双向转动?需不需要调速?...