C++语言导学 第三章 模块化 - 3.3 模块(C++20)
3.3 模块(C++20)
使用#include是一种古老的、易出错的且代价相当高的程序模块化组织方式。如果你在101个编译单元中使用#include header.h,编译器将会处理header.h的文件101次。如果你在header2.h之前使用#include header1.h,则header1.h中的声明和宏可能影响header2.h中代码的含义。相反,如果你在header1.h之前使用#include header2.h,则header2.h可能影响header1.h中的代码。显然,这不是一种理想的方式,实际上,自1972年这种机制被引入C语言之后,它就一直是额外代价和错误的主要来源。
我们的最终目的是想找到一种在C++中表达物理模块的更好方法。语言特性module尚未纳入ISO C++标准,但已是ISO技术规范[ModulesTS]。已有C++实现提供了module特性,因此我在这里冒一点风险推荐这个特性,虽然其细节可能发生改变,而且距离每个人都能使用它编写代码还有些时日。旧代码,即使用#include的代码,还会“生存”非常长的时间,因为代码更新代价很高且非常耗时。
我们考虑使用module表达3.2节中的Vectoe和use()例子:
//文件Vector.cpp:
module; //
//...
export class Vector{
public:
Vector(int s);
double& operator[](int i);
int size();
private:
double* elem;
int sz;
};
Vector::Vector(int s)
:elem{new double[s]}, sz{s}
{
}
double& Vector::opeartor[](int i)
{
return elem[i];
}
int Vector::size()
{
return sz;
}
export int size(const Vector& v){return v.size();}
这段代码定义了一个名为Vector的模块,它导出类Vector及其所有成员函数和非成员函数size()。
我们使用这个module的方式是在需要它的地方导入(import)它。例如:
//文件user.cpp:
import Vector; //获取Vector的接口
#include <cmath> //获取标准库数学函数接口,其中包含sqrt()
double sqrt_sum(Vector& v)
{
double sum = 0;
for(int i = 0; i != v.size(); ++i)
sum += std::sqrt(v[i]); //平方根求和
return sum;
}
我本可以对标准库数学函数也采用import,但我使用了老式的#include,借此展示新旧风格是可以混合的。在渐进地将#include旧代码更新为import新式代码的过程中,这种混合方式是必要的。
头文件和模块的差异不仅是语法上的。
- 一个模块只会编译一遍(而不是在使用它的每个编译单元中都编译一遍)。
- 两个模块可以按任意顺序导入(import)而不会改变它们的含义。
- 如果你将一些东西导入一个模块中,则模块的使用者不会隐式获得东西的访问权(但也不会被它们所困扰):import无传递性。
这些差异对可维护性和编译时性能的影响是惊人的。