最近看了两篇关于 C++ 20 Modules 很有意思的文章,戳:

《Understanding C++ Modules: Part 1: Hello Modules, and Module Units》

《Understanding C++ Modules: Part 2: export, import, visible, and reachable》

众所周知,C++靠预处理器处理头文件的方法被诟病已久。在 C++17 Module TS 后,标准委员会的 dalao 们终于在 C++20Modules 并入标准= =。(此处应该有掌声?

那么我们要怎么创建一个 Module 呢?标准引入了新的关键字 importmodule,并使用保留关键字 export 来导入、定义和导出 Module。

// hello_world.cpp
export module demos.hello.world;

export auto get_text()
{
  return "Hello C++ Modules!";
}
// main.cpp
import demos.hello.world;
import <iostream>;

int main()
{
  std::cout << get_text() << std::endl;
}

这是一个 C++20 Modules 版的 Hello World。注意 export module xxx.yyy.zzz 是一个 Module 的导出定义,函数 get_text 加上 export 就成为 Module 的导出符号。

Module 的名字可以是 aaa.bbb.ccc.ddd 这样的,此处借鉴了其他语言的命名规范,提升了可读性。

根据标准,每个 Module 都是一个 TU(Translation Unit) ,这样就可以在构建时得到更好的 Cache 编译的效果,缩短编译时间。

最后说下 import <iostream> 这样的用法。这是为了兼容以前老的头文件的写法,意思是把一个头文件当作一个独立的 TU 来编译,并且避免了命名空间污染和主文件中符号的影响。

-------------华丽分割线1--------------

一些语言,如 C# 有 partial class 的语法,可以把一个完整的 class 拆分为多个文件,最后合并编译。C++ Modules 提供了一个特性叫 Module Partition,可以把 Module 拆分在多个 .cpp 文件中定义。

在标准中,使用 Module 名称 + 冒号 + Partition 名称 的方式来定义一个 Module Partition

// home.cpp
export module home;
export import :father;
export import :mother;
// home_father.cpp
export module home:father;

export auto get_father_name()
{
  return "Xiaoming";
}
// home_mother.cpp
export module home:mother;

export auto get_mother_name()
{
  return "Xiaohong";
}
// main.cpp
import home;

int main()
{
  auto father = get_father_name();
  auto mother = get_mother_name();
}

这样 fathermother 就成为 Module home 的两个 Partition。吐槽一下 export import :xxx 这种语法,它的意思是把 Partition 先 import 进来再 re-export 出去,让用户可见。

这里多了一个概念:Module Interface Unit。很 Simple,对于一个 .cpp 文件,如果是 export module xxx 这样的,就是一个 Module Interface Unit,意思是导出接口单元。

比如上面的 home.cpp, home_father.cpp 和 home_mother.cpp 都是 Module Interface Unit

-------------华丽分割线2--------------

除了 Module Interface Unit,还有一个对应的东西叫 Module Implementation Unit ,模块实现单元。

同样很 Easy,如果一个 .cpp 文件中定义的是 module xxx,注意前面没有 export ,那么它就是一个 Module Implementation Unit

// animal.cpp
export module animal;
import :dogs;
import :cats;

export auto get_cat_name();
export auto get_dog_name();
// animal_cats.cpp
module animal:cats;

// "export" is not allowed here.
auto get_cat_name()
{
  return "狗·德川家康·薛定鄂·保留";
}
// animal_dogs.cpp
module animal:dogs;

// "export" is not allowed here.
auto get_cat_name()
{
  return "DOGE";
}
// main.cpp
import animal;

int main()
{
  auto cat_name = get_cat_name();
  auto dog_name = get_dog_name();
}

Partition catsdogs 就是两个 Module Implementation Unit ,而 animal.cpp 是一个 Module Interface Unit

注意 Implementation Unit 里面不允许有 export 出现,且需要在 Interface Unit 中 import 对应的 Partition。

想偷懒?同志好想法,还有种不需要 Partition 的简化写法。

// animal.cpp
export module animal;

export auto get_cat_name();
export auto get_dog_name();
// animal_impl.cpp
module animal;

auto get_cat_name()
{
  return "狗·德川家康·薛定鄂·保留";
}

auto get_cat_name()
{
  return "DOGE";
}

是不是感觉似曾相识?这个和目前的老式头文件声明和定义分离的用法如出一辙。

animal_impl.cpp 定义的还是叫 Module Implementation Unit ,animal.cpp 叫 Module Interface Unit

WHY? 既然可以写在一起,为什么要分离呢。一个是满足代码习惯,还有一个就是如果频繁修改实现,可以不动接口,提高增量编译速度~

-------------华丽分割线3--------------

考虑到程序员编码的方便性,标准规定了五花八门的 export 语法。如下所示,我们来欣赏一下。

// Export everything within the block.
export
{
  int some_number = 123;

  class foo
  {
  public:
    void invoke() { }
  private:
    int count_ = 0;
  };
}

// Export namespace.
export namespace demo::test
{
  struct tips
  {
    int abc;
  }

  void free_func() { }
}

// Export a free function.
export void here_is_a_function() { }

// Export a global variable.
export int global_var = 123;

// Export a class.
export class test
{

};

下面几种是非法的 export 写法,是无法编译的。

// Anonymous namespace cannot be exported.
export namespace
{

}

// Cannot export static variables.
export static int static_variable = 123;

// Cannot export static functions.
export static void foo()
{

}

// OK, export a namespace.
export namespace test
{
  // Error, cannot define static members in an exported namespace.
  static int mine = 123;

  // Error, as mentioned above.
  static void geek() { }
}

-------------华丽分割线4--------------

文中还介绍了其他的一些坑,比如 Implementation Unit BeastReachability & Visibility 巴拉巴拉,大家可以参考上面两篇文章……

个人觉得 Modules 作为重量级特性,还是给 C++ 带来了革新。引用一句某老外的说法:“Make CPP great again!”。

期待编译器这边的实现和新的 Build System 及可能的包管理工具,貌似一直在推进。

------- Over -------

C++20 的 Modules的更多相关文章

  1. 【RDA】使用RDA(Remote Diagnostic Agent)工具对数据库进行健康检查

    [RDA]使用RDA(Remote Diagnostic Agent)工具对数据库进行健康检查 分类: Linux RDA英文全称叫做"Oracle Remote Diagnostic Ag ...

  2. Asp.net Mvc4 基于Authorize实现的模块访问权限

    在MVC中,我们可以通过在action或者controller上设置Authorize[Role="xxx"] 的方式来设置用户对action的访问权限.显然,这样并不能满足我们的 ...

  3. zhihu spark集群,书籍,论文

    spark集群中的节点可以只处理自身独立数据库里的数据,然后汇总吗? 修改 我将spark搭建在两台机器上,其中一台既是master又是slave,另一台是slave,两台机器上均装有独立的mongo ...

  4. httpd配置文件规则说明和一些基本指令

    html { font-family: sans-serif } body { margin: 0 } article,aside,details,figcaption,figure,footer,h ...

  5. Linux 下的一个全新的性能测量和调式诊断工具 Systemtap, 第 3 部分: Systemtap

    Systemtap的原理,Systemtap与DTrace比较,以及安装要求和安装步骤本系列文章详细地介绍了一个Linux下的全新的调式.诊断和性能测量工具Systemtap和它所依赖的基础kprob ...

  6. centos7安装elasticsearch

    [root@aaron tools]# wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.5.1.zi ...

  7. ElasticSearch实战:Linux日志对接Kibana

    本文由云+社区发表 ElasticSearch是一个基于Lucene的搜索服务器.它提供了一个分布式多用户能力的全文搜索引擎,基于RESTFul web接口.ElasticSearch是用Java开发 ...

  8. httpd配置文件httpd.conf规则说明和一些基本指令

    apache httpd系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html 本文主要介绍的是httpd的配置文件,包括一些最基本的指令.配置规 ...

  9. django练习题

    1.Web框架的本质是什么?为什么要有Web框架? 所有的Web应用,本质上其实就是一个socket服务端,用户端程序其实就是一个socket客户端.对于真实开发中的python web程序来说,一般 ...

  10. 使用命令行创建一个vue项目的全部命令及结果

    dell@DESKTOP-KD0EJ4H MINGW64 /f/05 项目 $ npm install --global vue-cli npm WARN deprecated coffee-scri ...

随机推荐

  1. 深入理解ecshop2.7.3整合discuzX3.2(97%的完美方案)

    转载http://blog.csdn.net/squallleonheart/article/details/39555259 ecshop整合discuzX3.2 ecshop安装包:ECShop_ ...

  2. C++命名空间

    C++命名空间 本讲基本要求 * 掌握:命名空间的作用及定义:如何使用命名空间.     * 了解:使用早期的函数库 重点.难点     ◆命名空间的作用及定义:如何使用命名空间.     在学习本书 ...

  3. Libvirt 虚拟化库剖析

    讲到向外扩展计算(比如云计算),libvirt 可能是您从未听说过的最重要的库之一.libvirt 提供一种虚拟机监控程序不可知的 API 来安全管理运行于主机上的来宾操作系统.libvirt 本身不 ...

  4. 使用 C# 编程对RTF文档的支持

    http://www.68design.net/Development/Aspnet/Basis-AspNet/26011-1.html

  5. iOS开发中常用的宏

    前言 今天将一些简化工程代码的宏定义拿出来分享一下,自定义一些宏可以有效的简化代码,提高编码效率. Application #define APPLICATION [UIApplication sha ...

  6. SQL数据转移

    方法一:如果想把数据库A中的表Table1中的数据复制到数据库B中的表Table2中,也就是要预先建立Table2,可以使用一下语句: use B goinsert into Table2 selec ...

  7. 第三十三节,sys解释器相关模块

    首先要引入import sys模块 sys.argv 功能:获取向脚本文件传入的参数,返回的列表,列表里的第一个元素是脚本文件路径和名称,后面的元素是传入的向脚本传入的参数 使用方法:sys.argv ...

  8. apache 做负载

    首先说明一下,我感觉这种办法不太好,不能叫负载吧.不知道跳转到的服务器把数据返回给用户,还通不通过Apache的服务器,还有就是不能断点下载了 方法 1.打开httpd.conf  把如下模块前面的# ...

  9. Elastic 基础篇(2)

    1.基本概念 1)Elastic和RDMS对比 RDMS Elastic 数据库database 索引index 表table 类型type 行row 文档document 列column 字段fie ...

  10. unity3d的playmaker插件使用教程,三、对象出入触发,声音播放

    对象出入触发是游戏常见的情形.包含同一时候声音播放 首先建立进去区域.新建一个立方体,去掉mesh render. 而且选中 is trigger同意进入 样例里用了unity3d的第一人视角控制,可 ...