C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(1)

在使用模块之前,头文件用于提供代码重用的接口。头文件确实有许多问题,比如避免同一头文件的多重包含以及确保头文件的包含顺序正确。还有,简单的#include,例如,<iostream>就添加了几千行代码,编译器不得不编译。如果几个源文件#include <iostream>,所有这些翻译单元变得更大了。这还只是包含了一个简单的头文件。想像一下,如果需要<iostream>,<vector>,<format>等等。

模块解决了所有这些问题,甚至更多。模块被import的顺序并不重要。模块一旦被编译为进制格式,每当该模块被import进行另一个源文件时,编译器就可以使用。这与头文件是一个明显的对比,头文件是每当编译器碰到#include那个头文件时都要进行一而再再而三的编译。因此,模块可以极大节省编译时间。当在模块中修改了内容时,也节省了增加的编译时间,例如,修改一个模块接口文件中的导出函数实现,不需要触发使用那个模块的用户的重新编译。模块不受外部定义宏的影响,任何在模块中定义的宏对模块之外的任何代码不可见,也就是说,模块是自我隔离的。因此,推荐如下:

注意:有了以上讨论的好处,如果编译器支持模块,新写的代码应该使用模块来结构化代码成为逻辑上分隔的构造块。

如果可能的话,历史遗留代码也可以慢慢转换为模块。然而,遗留代码太多了,许多第三方库还不支持模块,现在也不是所有的编译器完全支持模块。基于这些原因了解传统的头文件是如何工作的依然重要。这就是本章仍然要包含头文件的讨论的原因。

注意:如何编译模块与编译器相关。咨询编译器的文档来学习特定编译器的模块工作方式。

注意:如果你的编译器不支持模块,那就只能将我们的例子转化为非模块的代码了,但还是建议你与时俱进,跟上时代的脚步吧,找一个支持模块的编译器,使你的工作事半功倍;而不是因循守旧,事倍功半。

1、将代码非模块化

如果想要使用还不完全支持模块的编译器来编译本博客中的代码,可以按如下步骤将代码非模块化:

  • 重命名.cppm模块接口文件为.h头文件。
  • 在每个.h头文件的顶部添加#pragma once。
  • 去除export module xyz声明
  • 替换module xyz声明为#include来包含相应的头文件。
  • 替换import与export import声明为合适的#include指令。如果代码使用了import std;那就需要用#include指令来包含所有需要的单个头文件了。
  • 去除任何export关键字。
  • 去除所有的module;,它指出了全局模块部分的开始。
  • 如果函数定义或变量定义出现在一个.h头文件中,在前面添加inline关键字。

2、标准命名的模块(c++23)

通过导入标准命名模块std可以访问c++标准库的任何东东。该命名模块使得整个标准库可用,包括所有的定义在<cstddef>中的C功能。然而,只要通过std命名空间就可以使用所有的C功能。对于遗留代码,可以考虑导入std.compat命名模块,它导入了所有的std import的,同时使得std命名空间与全局空间的C功能可用。在新的代码中不推荐使用std.compat。

3、模块接口文件

模块接口文件定义了由模块提供的功能的接口,通常以.cppm作为文件扩展名。模块接口文件以声明标示文件是由一个特定的名字定义的模块开头。这叫做模块声明。模块名字可以是任何合法的c++标识符。名字可以包含.但是不能以.开始或结束,并且不能一行中包含多个.。合法的名字的例子是datamodel,mycompany.datamodel,mycompany.datamodel.core,datamodel_core,等等。

注意:目前,还没有模块接口文件的标准扩展名。然而,大多数编译器支持.cppm(C++模块)扩展名,所以我们也这么用。可以检查一下你的编译器使用的是什么样的扩展名。

模块需要显式声明要把什么导出去,也就是说,当客户端代码导入模块时什么是可见的。模块可以导出任何声明,例如变量声明,函数声明,类型声明,using指令,以及using声明。还有,导入声明也可以被导出。从模块中导出实体通过export关键字来完成。从模块中没有导出的东东只在模块自身内可见。所有导出的实体的集合叫做模块接口。

下面是一个模块接口文件的例子,名字叫做Person.cppm,定义了一个person模块,导出了一个Personal类。注意,它导入了由std提供的功能。

cpp 复制代码
export module person;    // Named module declaration

import std;                // Import declaration

export class Person        // Export declaration
{
public:
	explicit Person(std::string firstName, std::string lastName)
		: m_firstName{ std::move(firstName) }
        , m_lastName{ std::move(lastName) } { }

	const std::string& getFirstName() const { return m_firstName; }
	const std::string& getLastName() const { return m_lastName; }

private:
	std::string m_firstName;
	std::string m_lastName;
};

在标准术语中,所有东东以命名模块声明开始(上面代码段中的第一行),直到文件结尾,叫做模块范围。

Person类可以通过导入person模块来使用,如下(test.cpp):

cpp 复制代码
import person;
import std;

using namespace std;

int main()
{
	Person person{ "Kole", "Webb" };
	println("{}, {}", person.getLastName(), person.getFirstName());
}

任何东东都可以从模块导出,只要它有名字。例子为类定义,函数原型,类枚举类型,using声明与指令,命名空间,等等。如果命名空间使用export关键字显式导出,该命名空间中的所有东东也自动导出。例如,下面的代码段导出了整个DataModel命名空间;因此,没有必要再显式导出单个类与类型别名:

cpp 复制代码
export module datamodel;

import std;

export namespace DataModel
{
	class Person { /* ... */ };

	class Address { /* ... */ };

	using Persons = std::vector<Person>;
}

也可以使用export block导出一个声明的整块,下面是一个例子:

cpp 复制代码
export
{
    namespace DataModel
    {
        class Person { /* ... */ };
        class Address { /* ... */ };
        using Persons = std::vector<Person>;
    }
}
相关推荐
HackKong12 分钟前
小白怎样入门网络安全?
网络·学习·安全·web安全·网络安全·黑客
LKID体13 分钟前
Python操作neo4j库py2neo使用之py2neo 删除及事务相关操作(三)
开发语言·python·neo4j
小屁孩大帅-杨一凡14 分钟前
Python-flet实现个人视频播放器
开发语言·python·音视频
算家云17 分钟前
快速识别模型:simple_ocr,部署教程
开发语言·人工智能·python·ocr·数字识别·检测模型·英文符号识别
螺旋天光极锐斩空闪壹式!28 分钟前
自制游戏:监狱逃亡
c++·游戏
Thomas_Cai28 分钟前
Python后端flask框架接收zip压缩包方法
开发语言·python·flask
霍先生的虚拟宇宙网络31 分钟前
webp 网页如何录屏?
开发语言·前端·javascript
澜世32 分钟前
2024小迪安全基础入门第三课
网络·笔记·安全·网络安全
温吞-ing33 分钟前
第十章JavaScript的应用
开发语言·javascript·ecmascript
Bald Baby33 分钟前
JWT的使用
java·笔记·学习·servlet