C++ 20 Module

头文件包含一直是C/C++的传统,它使代码声明与实现分离,但它有一个非常大的问题就是会被重复编译,拖累编译速度。

通常一个标准头文件iostream展开后可能达几十万甚至上百万行。笔者使用下面的示例进行测试,新建一个main.cc,内容如下:

#include <iostream>int main(int argc, char* argv[])
{return 0;
}

然后分别使用g++和clang++来测试行数:

g++ -E main.cc | wc -c 
1003912
clang++ -E main.cc | wc -c 
999191

随着C++ 20 Module的出现以及各编译器对其的逐步实现,C++也能进行模块化编程,提高编译速度了。

由于历史原因,现有以头文件形式组织的C++代码,不会在短时间内消失,这种情况将持续相当长的时间,也许是几年,十几年,甚至几十年或者更长,所以目前的C++依旧可以在Module中包含头文件,让之与模块(Module)共存,方便使用现有代码。

目前主流的C++编译器有GCC、Clang和MSVC,各个编译器实现的进展不一,使用的命令行参数也不一样,为了简单起见,笔者先以GCC编译器的命令行和Makefile为例来介绍模块的基本写法及编译,再介绍Clang和MVVC使用CMake来编译项目。

本文使用的各编译器版本为:
GCC 13.2.0
Clang 17.0.6
MSVC 19.38.33134.0,即VS2022 17.8.4

CMake版本为:3.28.1

一、模块基础

1. 定义模块

libA.cpp:

export module libA;export int plus(int x, int y)
{return x + y;
}

2. 使用模块

main.cpp

import libA;int main(int argc, char *argv[])
{plus(1, 2);return 0;
}

3. 编译:

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

需要先编译libA,再编译main

二、模块进阶

1. 接口与实现分离

前面的libA模块代码全部在一个文件中,当代码比较多的时候,清晰度就会下降,可以使用接口与实现分离的形式:

api.cpp:

export module libA;
export
{
int plus(int x, int y);
}

plus.cpp

module libA;int plus(int x, int y)
{return x + y;
}

编译:

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

注意编译顺序,一定是要先编译模块接口(声明)文件api.cc,再编译模块定义(实现)文件plus.cc,否则会报错:

libA: error: failed to read compiled module: No such file or directory
libA: note: compiled module file is 'gcm.cache/libA.gcm'
libA: note: imports must be built before being imported
libA: fatal error: returning to the gate for a mechanical issue
compilation terminated.

在这里插入图片描述

2. Module Partition(模块分区)

当一个模块功能比较多时,C++支持将模块进行分区,每个文件只是模块中的一部分,这样可以将模块的接口与实现进一步分离。

libA/minus.cpp

export module libA:minus;export int minus(int x, int y)
{return x - y;
}

libA/plus.cpp

export module libA:plus;export int plus(int x, int y)
{return x + y;
}

libA/test.cpp

export module libA:test;
import <iostream>;
#include <string.h>export class CTest
{public:CTest(){printf("CTest()\n");}void foo(){printf("foo\n");}
};

libA/z.cpp

export module libA;export import :plus;
export import :minus;
export import :test;

在接口文件中引用分区模块时不写主模块名,只写分区名。在GCC中支持写上主模块名,但Clang与MSVC都不支持,所以通用写法是不写主模块名。

编译:

g++ -std=c++20 -fmodules-ts -xc++-system-header iostream
g++ -std=c++20 -fmodules-ts -c libA/minus.cpp
g++ -std=c++20 -fmodules-ts -c libA/plus.cpp
g++ -std=c++20 -fmodules-ts -c libA/test.cpp
g++ -std=c++20 -fmodules-ts -c libA/z.cpp
g++ -std=c++20 main.cpp -c main
g++ -std=c++20 libA/minus.o libA/plus.o libA/test.o libA/z.o main.o -o main

这里同样需要注意编译顺序,先编译系统级头文件,再编译自定义模块。

GCC可以使用import <iostream>;来引用标准库头文件,但是需要先手动编译,第一句即是,引用得的标准库头文件越多,自己手动编译得也越多。注意:有些标准库头文件目前还不能使用import来引用,将头文件编译为模块时会报错。为了最大程度上与其它编译器兼容,目前不建议使用import来引用标准库头文件

自定义模块必须先编译各分区的实现(minus.cpp、plus.cpp、test.cpp),最后编译模块的接口(z.cpp)。

当文件比较多的时候,使用命令行直接一个个文件编译比较慢,也容易出错,所以可以改用Makefile来编译,编写Makefile如下:

CXX := g++
CXXFLAGS := -std=c++20 -fmodules-ts -gdwarf-4
Target := mainSOURCE := $(wildcard libA/*.cpp)
SOURCE += $(wildcard *.cpp)OBJS := $(addsuffix .o, $(SOURCE))all: $(Target)$(Target): std $(OBJS)$(CXX) $(CXXFLAGS) $(OBJS) -o $(Target)std:$(CXX) $(CXXFLAGS) -xc++-system-header iostream$(CXX) $(CXXFLAGS) -xc++-system-header cmath%.cpp.o: %.cpp$(CXX) $(CXXFLAGS) -c $< -o $@clean:rm $(OBJS) gcm.cache $(Target) -rf

此时只需要执行make即可编译项目,make clean清除生成的文件。为了让Makefile遵循前面的编译顺序,笔者在文件命名时即按字母顺序进行了相应的排序,所以$(wildcard libA/*.cpp)得到的文件顺序是符合要求的。

为了让lldb也可以调试生成的程序,添加了-gdwarf-4选项。

当源文件有修改,编译时可能会出现CRC不匹配的错误,如下:
在这里插入图片描述
需要执行make cleanmake即可。

3. 子模块

子模块与分区模块非常像,只是模块分区使用冒号:分隔,而子模块使用点号.分隔,同时在引入子模块时,必须带有主模块名,即主模块名.子模块名

libA/test.cpp

export module libA.test;
import <iostream>;
#include <string.h>export class CTest
{public:CTest(){printf("CTest()\n");}void foo(){printf("foo\n");}
};

libA/z.cpp

export module libA;export import :plus;
export import :minus;
export import libA.test;

4. 私有模块

私有模块必须在主模块或者子模块中写,不能写在分区模块中,格式为:

module :private;

其后的所有内容都将作为模块的私有部分。

注意:目前GCC编译器还不支持私有模块:

sorry, unimplemented: private module fragment

5. 引用头文件

文章开头有说过,头文件与模块方式将长期共存,模块也可以引用头文件,可以使用全局模块的方式,也可以在模块内引用。一般在全局模块中引用,模块内引用可能会报一些意想不到的错误。

全局模块引用:

module;
#include <iostream>
export module libA:plus;

模块内引用:

export module libA:plus;
#include <stdio.h>

三、Clang与MSVC使用CMake来编译项目

前面的示例中,使用了GCC的命令行方式或者Makefile来编译C++模块代码,但是Clang与MSVC使用的参数与GCC不一样,也比GCC复杂,Clang与MSVC在默认情况下都要求模块文件使用新加的扩展名,比如Clang为.cppm.ccm.cxxm, .c++m,MSVC为.ixx。同时,针对模块分区编译的输出文件也有要求,Clang与MSVC都要求为主模块名-分区名.扩展名的格式,Clang扩展名为pcm,MSVC为ifc。具体信息:
GCC可以参考:https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Modules.html
Clang可以参考:https://clang.llvm.org/docs/StandardCPlusPlusModules.html
MSVC可以参考:https://learn.microsoft.com/zh-cn/cpp/cpp/modules-cpp?view=msvc-170

三大编译器不统一,对开发人员来说确实是一件痛苦的事。

好消息是CMake已经支持Clang和MSVC编译器的C++ Module了,而GCC由于还没有实现扫描模块依赖的功能,所以CMake还无法提供支持。

下面还是以前面的示例来介绍使用CMake来编译项目。

在顶层目录创建CMakeLists.txt

cmake_minimum_required(VERSION 3.28)
project(main)set(CMAKE_CXX_STANDARD 20)
add_subdirectory(libA)add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} PRIVATE libA)

在libA目录创建CMakeLists.txt

cmake_minimum_required(VERSION 3.28)
project(libA)set(CMAKE_CXX_STANDARD 20)aux_source_directory(. SOURCE)
#这里需要去掉路径中的./
string(REPLACE "./" "" SOURCE "${SOURCE}")add_library(${PROJECT_NAME})
target_sources(${PROJECT_NAME}PUBLICFILE_SET cxx_modules TYPE CXX_MODULES FILES${SOURCE}
)

注意:cmake_minimum_required(VERSION 3.28)一定要是3.28及以上,且需要去掉模块路径中的./

为了方便CMake处理模块,建议将同一个模块放在一个目录中,该目录中所有文件都添加到target_sources的C++模块集内。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/642085.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++:迭代器失效问题

目录 1.vector迭代器失效问题 1.底层空间改变 ​编辑 2.指定位置元素的删除操作 2.Linux下的迭代器失效检测 1.扩容 2.删除 3.解决方法 1.vector迭代器失效问题 迭代器的主要作用就是让算法能够不用关心底层数据结构&#xff0c;其底层实际就是一个指针&#xff0c;或者是…

前端使用css去除input框的默认样式

关键点&#xff1a; /* 关键点&#xff0c;让输入框无边框 */outline:none; border:none; 1.效果图 2.html <div class"container"><input type"text" placeholder"请输入用户名"><input type"text" placeholder&q…

springboot+mysql校园社团信息管理系统-计算机毕业设计源码62756

目 录 摘要 第1章 绪论 1.1 研究背景 1.2 研究意义 1.3论文结构与章节安排 第2章 相关技术 ....... 2.1开发技术 2.2 Java简介 2.3 MVVM模式 2.4 B/S结构 2.5 MySQL数据库 2.6 SpringBoot框架介绍 第3章 系统分析 6 3.1 可行性分析 6 3.2 系统流程分析 6 3.3 …

详解华为铁三角工作法完全解密.ppt

华为铁三角工作法是华为“以客户为中心”的思想在客户界面的集中体现。是一种以客户经理、方案经理和交付经理为核心的销售方法&#xff0c;将为客户服务所需要的主要能力&#xff0c;一直延伸到客户界面&#xff0c;并统一运作。 华为铁三角工作法的核心&#xff1a; - 让听…

CC工具箱使用指南:【现状规划用地变化检查(村规)】

一、简介 在规划工作中&#xff0c;有一个普遍性的需求&#xff0c;就是需要检查规划前后在用地上究竟发生了哪些变化。 这一点很重要&#xff0c;不仅是要展示给别人看&#xff0c;自己也要十分注意。 规划方案完成后&#xff0c;一定要进行用地变化的检查&#xff0c;曾经…

定时任务组件Quartz

1.Quartz介绍 官网&#xff1a;Quartz Enterprise Job Scheduler 2.Quartz框架的使用思路 1&#xff09;job - 任务 - 你要做什么事&#xff1f; 2&#xff09;Trigger - 触发器 - 你什么时候去做&#xff1f; 3&#xff09;Scheduler - 任务调度 - 你什么时候需要去做什么…

NVM (Node Version Manager) 安装使用

博文目录 文章目录 管理工具安装使用 管理工具 GitHub, nvm-windows nvm-windows: Similar (not identical) to nvm, but for Windows 管理 Node.js 版本有多种工具可选择, 其中使用最广泛的是 nvm, 目前 72.3k Star, 不支持 Windows 系统, nvm-windows, 是其他大佬为 Windows…

ubuntu安装kibana

1、安装elastic search7 参考&#xff1a;elastic search入门-CSDN博客 2、安装kibana&#xff0c;版本对不上&#xff0c;不匹配&#xff0c;不能正常启动。 kibana要改成7.11.1版本的试试。 nohup wget https://artifacts.elastic.co/downloads/kibana/kibana-7.11.1-linu…

Spring-配置文件

一、引子 了解完Spring的基本概念后&#xff0c;我们紧接着来了解Spring中的核心文件--Spring配置文件。 二、配置Bean 我们在上一节Spring的基本概念中快速使用了一下Spring&#xff0c;其中我们在配置文件中主要涉及到就是Bean标签的配置&#xff1a;主要的配置字段有id, …

Servlet系列:生命周期(init、 service、destroy)详解

Servlet的生命周期是由Web容器&#xff08;如Tomcat&#xff09;管理的&#xff0c;包括以下三个阶段&#xff1a; 加载和实例化&#xff1a;当Web应用程序启动时&#xff0c;Web容器会加载和实例化Servlet。加载和实例化过程可以在应用程序启动时自动完成&#xff0c;也可以通…

Mac上如何设置映射某个网站站点域名的IP

最近某常用的站点换 IP 了&#xff0c;但是 DNS 服务器还没有修改&#xff0c;这就导致无法访问&#xff08;换 DNS 服务器也不行&#xff09;。在用了一段时间的 IP 访问之后&#xff0c;还是没好&#xff0c;不知道是 DNS 污染还是咋了&#xff0c;所以最后还是手动改一下吧。…

MySQL定期整理磁盘碎片

MySQL定期整理磁盘碎片&#xff1a;提升数据库性能的终极指南 MySQL作为一个强大的关系型数据库管理系统&#xff0c;在长时间运行后可能会产生磁盘碎片&#xff0c;影响数据库性能。本博客将深入讨论如何定期整理MySQL磁盘碎片&#xff0c;以确保数据库的高效运行。我们将介绍…

mac滚动截图

参考博客 https://www.zhihu.com/question/313673726/answer/2938671835 首先去AppStore搜索 iShot 这个也是要钱的&#xff0c;不过我输入appleID后&#xff0c;并没有扣我钱&#xff0c;不知道设么回事 然后打开iShot 点击观看视频&#xff0c;然后mac会自动打开一个新的…

Softing mobiLink Power:集成FDI技术,简化多协议总线设备的配置方式

Softing的mobiLink Power是一款多协议总线通信工具&#xff0c;用于调试和维护过程自动化现场设备。目前&#xff0c;该工具已具备完整的FDI CommServer&#xff08;包括FF、PA、HART&#xff09;&#xff0c;并支持与FDI客户端一起使用&#xff0c;如艾默生的AMS Device Confi…

LeetCode 670 最大交换数

周一&#xff0c;非常冷&#xff0c;大风呼呼的&#xff0c;上班路都走不动。 好消息&#xff0c;马上要过年了。大风吹&#xff0c;天气好。 过年过年&#xff0c;回家过年~ 学生时代的迷茫是不应该存在的&#xff0c;最好的时光应该尽情享受&#xff0c;而不应该自己给加层…

西门子触摸屏维修6AV7880-0AA22-2DA2

西门子SIMATIC ITP1000平板PC机10.1 英寸采用 Intel Core i5 Skylake CPU&#xff0c;适合需要达16GB RAM的较高图形性能的工业移动计算应用。该 CPU 还提供了足够性能储备以满足将来的要求。安装的 Microsoft Windows 7 和 Microsoft Windows 10 操作系统以合理方式集成到现有…

68. redis计数与限流中incr+expire的坑以及解决办法(Lua+TTL)

文章目录 一、简介二、代码演进第一版代码&#xff08;存在bug隐患&#xff09;第二版代码&#xff08;几乎无隐患&#xff09;第三版代码(完美无瑕&#xff09; 一、简介 在日常工作中&#xff0c;经常会遇到对某种操作进行频次控制或者统计次数的需求&#xff0c;此时常用的…

Elasticsearch分布式一致性原理剖析(一)-节点篇

前言 “Elasticsearch分布式一致性原理剖析”系列将会对Elasticsearch的分布式一致性原理进行详细的剖析&#xff0c;介绍其实现方式、原理以及其存在的问题等(基于6.2版本)。 ES目前是最流行的分布式搜索引擎系统&#xff0c;其使用Lucene作为单机存储引擎并提供强大的搜索查…

解决Git添加.gitignore文件后不生效的问题

1. 问题描述 如上图所示&#xff0c;在已存在.gitignore文件且已经提交过的Git管理的项目中&#xff0c;其中.class、.jar文件以及.idea目录内的内容全部都还是被Git管理了&#xff0c;可见.gitignore文件并没有生效。 2. 原因发现 .gitignore文件只能作用于 Untracked Files…

eNSP学习——配置通过FTP进行文件操作

原理概述&#xff1a; FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;是在TCP/IP网络和Internet上最早使用的协议之一&#xff0c;在TCP/IP协议族中属于应用层协议&#xff0c;是文件传输的Internet标准。主要功能是向用户提供本地和远程主机…