C++20学习记录:modules 和 <=>
Posted 河边小咸鱼
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++20学习记录:modules 和 <=>相关的知识,希望对你有一定的参考价值。
- 本篇笔记记录了对于 C++20 新特性中模块和三路比较运算符的一些尝试。
- 主要参考地址:cppreference
目录
一、前言
这次抽空对 C++20 的新特性进行一点尝试,首先目标定为我比较感兴趣的模块部分和一个新的运算符 <=>
。
我的环境上的 gcc 版本为 8.4.1
,我查了一下编译器支持,发现这个版本对于 C++20 的好多新特性无法完全支持,所以首先是手动编译安装了一下 11.2.0
的 gcc (真的慢…编了一个多小时),所以本文使用的测试编译器为 gcc 11.2.0
。
二、模块
1. 概念
模块是 C++20 中我个人比较感兴趣的一部分,它的功能为支持代码的模块化,其作用根据设计思路而言主要是显著地提高与宏地隔离并大大优化编译时间,而且它也可以使得代码更加"卫生"。
对于C++传统的头文件系统,主要的问题如下:
- 不够卫生:一个头文件中的代码可能会影响同一翻译单元中包含的另一个
#include
中的代码的含义,因此#include
并非顺序无关。宏是这里的一个主要问题,尽管不是唯一的问题。 - 分离编译的不一致性:两个翻译单元中同一实体的声明可能不一致,但并非所有此类错误都被编译器或链接器捕获。
- 编译次数过多:从源代码文本编译接口比较慢。从源代码文本反复地编译同一份接口非常慢。
由此,标准委员会推出了模块这一新特性。这一特性说实话并不算新,现在很火的py和java都是很早就使用上了模块。但是对于C++而言我觉得这一新特性挑战还是很大,因为和以往的风格有很大不同。
对于模块,其关键思想如下:
export
指令使实体可以被import
到另一个模块中。import
指令使从另一个模块export
出来的的实体能够被使用。import
的实体不会被隐式地再export
出去。import
不会将实体添加到上下文中,它只会使实体能被使用(因此,未使用的import
基本上是无开销的)。
2. 代码测试
由此开始记录我的测试过程。首先创建模块文件 test.cpp
和主文件 main.cpp
如下:
//test.cpp 模块文件
export module helloworld;
export auto hello()
return "Hello C++ 20!";
//main.cpp 主文件
#include <iostream>
import helloworld;
int main()
std::cout << hello() << std::endl;
可以看到在 test.cpp
中导出了 helloworld
模块和其中的函数 hello()
,并在 main.cpp
中导入了 helloworld
模块。随后尝试进行编译,编译和执行过程如下:
g++ -std=c++20 -fmodules-ts -c test.cpp #编译模块
g++ -std=c++20 -fmodules-ts -c main.cpp #编译主文件
g++ -std=c++20 test.o main.o -o App #编译可执行文件
需要注意的是:
模块必须优先被编译,否则会报错。
g++ -std=c++20 -fmodules-ts test.cpp main.cpp -o App #正确
g++ -std=c++20 -fmodules-ts main.cpp test.cpp -o App #错误
执行:
./App
输出:
Hello C++ 20!
编译完以后,可以看到目录下相比平常的编译,多了一个名叫 gcm.cache
的目录,如下图:
进入其中,发现有一个以模块名命名的 .gcm
格式文件,这个就是编译好的模块文件。
我看 cppreference 上说 C++20 按理是支持导入以往的标准库的,例如 import <iostream>;
,但是经过我的测试,gcc 无法实现,查了一下发现 gcc 确实是没有完全支持。
3. 小结
目前来看可能这个新特性并没有被完全的支持,而且其相较以往的头文件格式改变比较大,受限于老代码和稳定性、支持性之类的考量,可能不会被太快的接受。但是它在编译速度上的优势我觉得还是很有前途的,现阶段主要的问题应该是老代码的模块化重构,比如说当前无法导入标准库的主要原因就是标准库还没有重构完成,也许在 C++23 会重构完成吧,到时候可能模块化就会用的更加方便、更容易被人接受。
三、三路比较运算符
1. 概念
简单说就是一个新的运算符,功能是比较左右操作数的大小,格式为 左操作数 <=> 右操作数
。 其逻辑如下:
- 若
左操作数 < 右操作数
则(a <=> b) < 0
- 若
左操作数 > 右操作数
则(a <=> b) > 0
- 若
左操作数 和 右操作数
相等/等价则(a <=> b) == 0
总体而言使用还是挺简单的,一个运算符就可以进行大小的比较。下面是截的一点描述,简单来讲就是这个运算符的返回值是一个被封装了很多层的类型的右值。
简单看一下源码,相关定义位于 /usr/local/include/c++/11.2.0/compare
,最底层的枚举值为 小于:-1
、等于:0
、大于:1
。
然后就是各种封装内联对象,可以看到下图的定义和初始化。联系上图可以得知最底层储存类型为 signed char
,所以十六进制下的值就是如下图一注释一样,less=0xff...
。
2. 代码测试
测试代码如下,使用起来也非常简单,没有什么好说的,但是需要注意比较结果是无法直接 cout
的,原因应该是作用域枚举没有重载 <<
。
#include <compare>
#include <iostream>
int main()
double foo = -0.0;
double bar = 0.0;
auto res = foo <=> bar;
if (res < 0)
std::cout << "-0 小于 0";
else if (res > 0)
std::cout << "-0 大于 0";
else // (res == 0)
std::cout << "-0 与 0 相等";
编译:
g++ -std=c++20 main.cpp -o App
执行:
./App
输出:
-0 与 0 相等
3. 小结
这算是一个小更新,但是说实话让 C++ 有了更多的看起来很秀的操作。在实际判断中这个运算符可以省去一些变量名的重复编写,我觉得可以避免一些低级的失误,比如说写错变量名之类的。总之还是挺有意思的一个小更新。
以上是关于C++20学习记录:modules 和 <=>的主要内容,如果未能解决你的问题,请参考以下文章