C++20四大之一:module特性详解

前言

C++20最大的特性是什么?
——最大的特性是迄今为止没有哪一款编译器完全实现了所有特性。

在这里插入图片描述
C++20标准早已封版,各大编译器也都已支持了C++20中的多数特性,但迄今为止(2021.7),尚未有哪一款编译器完整支持C++20中的所有特性。有人认为C++20是C++11以来最大的一次改动,甚至比C++11还要大。
本文仅介绍C++20四大特性当中的module部分。全文分为三章:第一章探究C++编译连接模型的由来以及利弊、第二章介绍C++20 module机制的使用姿势、第三章总结module背后的机制、利弊、以及各大编译器的支持情况。

(一)扒一扒头文件的由来

  • 1,C++是兼容C的:不但兼容了C的语法,也兼容了C的编译链接模型
  • 2,1973年初,C语言基本定型:有了预处理、支持结构体;编译模型也基本定型为:预处理、编译、汇编、链接四个步骤并沿用至今;1973年,K&R二人使用C语言重写了Unix内核。
  • 3,为何要有预处理?为何要有头文件?
  • 4,在C的诞生的年代,用来跑C编译器的计算机PDP-11的硬件配置如下:
    内存:64 KiB
    硬盘:512 KiB
    编译器无法把较大的源码文件放入狭小的内存,故当时的C编译器设计目标是能够支持模块化编译(将源码分成多个源码文件,挨个编译)、生成多个目标文件,最后整合(链接)成一个可执行文件。
    C编译器分别编译多个源码文件的过程,实际上是一个One pass compile,即:从头到尾扫描一遍源码、边扫描边生成目标文件、过眼即忘(以源码文件为单位)、后面的代码不会影响编译器前面的决策,该特性导致了C语言的以下特征:
    A: 结构体必须先定义再使用:否则无法知道成员的类型以及偏移,无法生成目标代码
    B: 局部变量先定义再使用,否则无法知道变量的类型以及在栈中的位置。且为了方便编译器管理栈空间,局部变量必须定义在语句块的开始处。
    C: 外部变量,只需要知道类型、名字(二者合起来便是声明)即可使用(生成目标代码),外部变量的实际地址由连接器填写
    D: 外部函数,只需知道函数名、返回值、参数类型列表(函数声明)即可生成调用函数的目标代码,函数的实际地址由连接器填写。
  • 5 头文件和预处理恰好满足了上述要求:头文件只需用少量的代码,声明好函数原型、结构体等信息,编译时将头文件展开到实现文件中,编译器即可完美执行One pass comlile过程了。

至此,我们看到的都是头文件的必要性、益处,头文件也有很多负面影响:

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

C++20中,加入了module。我们先看module 的基本使用姿势,最后再总结module比header的优势。

(二)module的使用

2.1 实现一个最简单的module

module_hello.cppm:定义一个完整的hello模块,并导出一个say_hello_to方法给外部使用。当前各编译器并未规定模块文件的后缀,本文统一使用".cppm"后缀名。".cppm"文件有一个专用名称"模块接口文件",值得注意的是,该文件不光可以声明实体,也可定义实体。

//module_hello.cppm
export module hello;
import <iostream>;
import <string_view>;
void internal_helper(){
   
	//do something;
}
export void say_hello_to(const std::string_view& something){
   
	internal_helper();
	std::cout<<"Hello "<<something<<" !"<<std::endl;
	return;
}

main函数中可以直接使用hello模块:

//main.cpp
import hello;
import <string_view>;

int main(){
   
	say_hello_to(std::string_view{
   "Netease"});
	internal_helper();//error
	return 0;
}

编译脚本如下,需要先编译module_hello.cppm生成一个pcm文件(module缓存文件),该文件包含了hello模块导出的符号。

#buildfile.sh
CXX="clang -fmodules-ts -std=c++2a"
$CXX -o module_hello.pcm --precompile -x c++-module module_hello.cppm
$CXX -o hello -fprebuilt-module-path=. main.cpp hello.cpp

以上代码有以下细节需要注意:

  • module hello; 声明了一个模块,前面加一个export,则意味着当前文件是一个模块接口文件(module interface file),只有在模块接口文件中可以导出实体(变量、函数、类、namespace等ÿ
  • 11
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值