服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C/C++ - C++ module编程升级指南,子模块与分区全解析

C++ module编程升级指南,子模块与分区全解析

2024-01-02 14:09coding日记 C/C++

C++ 标准并没有特别提到子模块,但允许在模块名称中使用点(.),从而可以按任何你想要的层次结构来组织模块。

子模块

1.C++ 标准与子模块

C++ 标准并没有特别提到子模块,但允许在模块名称中使用点(.),从而可以按任何你想要的层次结构来组织模块。例如,以下是一个 DataModel 命名空间的示例:

export module datamodel;
import <vector>;

export namespace DataModel {
    class Person { /* ... */ };
    class Address { /* ... */ };
    using Persons = std::vector<Person>;
}

Person 和 Address 类都在 DataModel 命名空间内,也在 datamodel 模块中。可以通过定义两个子模块来重构:datamodel.person 和 datamodel.address。

C++ module编程升级指南,子模块与分区全解析

2.子模块的模块接口文件

datamodel.person 子模块的模块接口文件如下:

export module datamodel.person; // datamodel.person 子模块
export namespace DataModel {
    class Person { /* ... */ };
}

datamodel.address 子模块的模块接口文件如下:

export module datamodel.address; // datamodel.address 子模块
export namespace DataModel {
    class Address { /* ... */ };
}

最后,定义一个 datamodel 模块如下。它导入并立即导出两个子模块。

export module datamodel; // datamodel 模块
export import datamodel.person; // 导入并导出 person 子模块
export import datamodel.address; // 导入并导出 address 子模块
import <vector>;
export namespace DataModel {
    using Persons = std::vector<Person>;
}

当然,子模块中类的方法实现也可以放在模块实现文件中。例如,假设 Address 类有一个默认构造函数,仅打印一条语句到标准输出;该实现可以放在一个名为 datamodel.address.cpp 的文件中:

module datamodel.address; // datamodel.address 子模块
import <iostream>;
using namespace std;

DataModel::Address::Address() {
    cout << "Address::Address()" << endl;
}

3.使用子模块的好处

用子模块结构化代码的好处是,客户端可以导入他们想要使用的特定部分,或者一次性导入所有内容。例如,如果客户端仅对使用 Address 类感兴趣,以下导入声明就足够了:

import datamodel.address;

另一方面,如果客户端代码需要访问 datamodel 模块中的所有内容,那么以下导入声明是最简单的:

import datamodel;

模块分区

1.分区与子模块的区别

分区和子模块之间的区别在于,子模块结构对模块的使用者是可见的,允许用户选择性地只导入他们想使用的子模块。另一方面,分区用于内部结构化模块,对模块的使用者不可见。在模块接口分区文件中声明的所有分区最终必须由主要的模块接口文件导出。一个模块始终只有一个这样的主模块接口文件,即包含 export module 名称声明的接口文件。

2.创建模块分区

模块分区是通过将模块名称和分区名称用冒号分隔来创建的。分区名称可以是任何合法的标识符。例如,前一节中的 DataModel 模块可以使用分区而不是子模块来重构。以下是 datamodel.person.cppm 模块接口分区文件中的 person 分区:

export module datamodel:person; // datamodel:person 分区
export namespace DataModel {
    class Person { /* ... */ };
}

3.分区的实现文件注意事项

使用分区时的一个注意事项是:与分区相结合的实现文件只能有一个文件具有特定的分区名称。因此,以下声明开始的实现文件是不正确的:

module datamodel:address;

相反,你可以将 address 分区的实现放在 datamodel 模块的实现文件中:

module datamodel; // 不是 datamodel:address!
import <iostream>;
using namespace std;

DataModel::Address::Address() {
    cout << "Address::Address()" << endl;
}

警告:多个文件不能有相同的分区名称。因此,拥有多个具有相同分区名称的模块接口分区文件是非法的,且分区文件中声明的实现不能放在具有相同分区名称的实现文件中。相反,应该将这些实现放在模块的实现文件中。

4.编写分区模块的要点

编写分区结构的模块时,要记住的重要一点是,每个模块接口分区最终必须由主模块接口文件直接或间接导出。要导入分区,只需指定分区名称,前缀为冒号,例如 import :person。说 import datamodel:person 是非法的。请记住,分区对模块的使用者不可见;分区只在模块内部结构化。因此,用户不能导入特定的分区;他们必须导入整个模块。分区只能在模块内部导入,因此在冒号前指定模块名称是多余的(且非法的)。

以下是 datamodel 模块的主模块接口文件:

export module datamodel; // datamodel 模块(主模块接口文件)
export import :person;   // 导入并导出 person 分区
export import :address;  // 导入并导出 address 分区
import <vector>;
export namespace DataModel {
    using Persons = std::vector<Person>;
}

5.使用分区结构化的 datamodel 模块

import datamodel;

int main() {
    DataModel::Address a;
}

注意:分区用于内部结构化模块。分区在模块外部不可见。因此,模块的用户不能导入特定分区;他们必须导入整个模块。早先提到,模块名称声明隐含地包含一个导入名称声明。但对于分区,情况并非如此。例如,datamodel:person 分区没有隐含的 import datamodel 声明。在这个例子中,甚至不允许在 datamodel:person 接口分区文件中添加显式的 import datamodel 声明。这样做会导致循环依赖:datamodel 接口文件包含 import :person 声明,而 datamodel:person 接口分区文件会包含 import datamodel 声明。

实现分区

1.定义和用途

实现分区不需要在模块接口分区文件中声明,它也可以在模块实现分区文件中声明,这是一个带有 .cpp 扩展名的普通源代码文件,在这种情况下,它是一个实现分区,有时也称为内部分区。这种分区不会被主模块接口文件导出。例如,假设你有以下数学主模块接口文件(math.cppm):

export module math; // math 模块声明
export namespace Math {
    double superLog(double z, double b);
    double lerchZeta(double lambda, double alpha, double s);
}

假设进一步数学函数的实现需要一些不能被模块导出的辅助函数。实现分区是放置这些辅助函数的完美位置。以下在名为 math_helpers.cpp 的文件中定义了这样的实现分区:

module math:details; // math:details 实现分区
double someHelperFunction(double a) {
    return /* ... */;
}

2.实现分区的访问

其他数学模块实现文件可以通过导入这个实现分区来访问这些辅助函数。例如,一个数学模块实现文件(math.cpp)可能看起来像这样:

module math;
import :details;

double Math::superLog(double z, double b) {
    return /* ... */;
}

double Math::lerchZeta(double lambda, double alpha, double s) {
    return /* ... */;
}

当然,使用带有辅助函数的这种实现分区只有在多个其他源文件使用这些辅助函数时才有意义。

原文地址:https://mp.weixin.qq.com/s?__biz=Mzg2NzY2NTUyMg==&mid=2247484830&idx=1&sn=a268ed4680369dea920f7b04d8919c98

延伸 · 阅读

精彩推荐
  • C/C++C语言中杨氏矩阵与杨辉三角的实现方法

    C语言中杨氏矩阵与杨辉三角的实现方法

    这篇文章主要给大家介绍了关于C语言中杨氏矩阵与杨辉三角的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价...

    森明帮大于黑虎帮12552021-11-04
  • C/C++C++递归实现螺旋数组的实例代码

    C++递归实现螺旋数组的实例代码

    这篇文章主要介绍了C++递归实现螺旋数组的实例代码,代码简单易懂,非常不错,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...

    【VIP】黏黏的大香蕉11412021-08-31
  • C/C++详解C语言对字符串处理函数的实现方法

    详解C语言对字符串处理函数的实现方法

    这篇文章主要为大家介绍了C语言对字符串处理函数的实现方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助...

    GREEN@dehua10522022-08-01
  • C/C++VC双画布消除屏幕闪烁实例详解

    VC双画布消除屏幕闪烁实例详解

    这篇文章主要介绍了VC双画布消除屏幕闪烁实例详解的相关资料,需要的朋友可以参考下...

    bianceng8102021-05-19
  • C/C++从汇编看c++中默认构造函数的使用分析

    从汇编看c++中默认构造函数的使用分析

    c++中,如果为一个类没有明确定义一个构造函数,那么,编译器就会自动合成一个默认的构造函数。下面,通过汇编程序,来看一下其真实情况...

    C++教程网4482020-11-23
  • C/C++C++实现二分法求连续一元函数根

    C++实现二分法求连续一元函数根

    这篇文章主要为大家详细介绍了C++实现二分法求连续一元函数根,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    Alex山南水北7202021-09-10
  • C/C++C语言 详细讲解接续符和转义符的使用

    C语言 详细讲解接续符和转义符的使用

    接续符是用来告诉编译器行为的符号,那编译器遇到接续符是什么行为呢,就是去掉接续符,然后把下一行连接到现在这行上面,转义符是主要用于表示无...

    清风自在 流水潺潺4122022-11-11
  • C/C++c++类的隐式转换与强制转换重载详解

    c++类的隐式转换与强制转换重载详解

    转换函数的名称是类型转换的目标类型,因此,不必再为它指定返回值类型;转换函数是被用于本类型的数值或变量转换为其他的类型,也不必带参数...

    C++教程网6292020-12-26