C++语言导学(2): 模块化¶
引言¶
程序组织的最佳方式是将程序看作依赖关系定义良好的一组模块,在逻辑上通过语言特性表达模块化,在物理上通过文件模块化实现高效的分别编译。
程序源码文件模块化最重要的一步是将每个组成部分的接口和实现分离开来,借助分别编译、后续链接的方式提高编译效率。
以往是通过头文件(*.hpp
)和实现文件(*.cpp
)来达到分离目的,在编译为动态链接库(*.so
)供其他人使用的时候特别方便,可以达到隐藏细节的目的。
但使用 #include
是一种古老、易出错且代价不小的程序模块化组织方式,从C++20开始引入 module
特性,GCC11支持此功能:
使用模块功能可以提高编译效率和可维护性:
- 一个模块只会编译一次,而include只要包含一次就会编译一次
- 两个模块可以按任意顺序导入而不会改变它们的含义
- import没有传递性
但目前来看,使用module功能不是那么容易,尤其是旧代码迁移、新旧代码并存编译会是一个不小的挑战。
另外,代码的静态库、二进制等分发会比较困难,细微的ABI差异都导致无法共享使用。
module例子: 头文件变成模块¶
hello.hpp:
run.cpp:
需要先将这个单独的头文件编译为模块后才能import使用:
$ g++ -c -std=c++20 -fmodule-header hello.hpp # 生成gcm.cache/,/hello.hpp.gcm
$ g++ -c -std=c++20 -fmodules-ts run.cpp # 生成run.o
module例子: 模块接口和实现在一起¶
Vector.cpp
module;
export module Vector;
export class Vector{
public:
Vector(int s);
double& operator[](int i);
int size() const;
private:
double *elem;
int sz;
};
Vector::Vector(int s)
:elem{new double[s]}, sz{s}
{
}
double& Vector::operator[](int i){
return elem[i];
}
int Vector::size() const{
return sz;
}
export int size(const Vector& v){ return v.size(); }
main.cpp:
import Vector;
#include <iostream>
#include <cmath>
double sqrt_sum(Vector& v)
{
double sum = 0;
for(int i=0; i != v.size(); i++)
{
sum += std::sqrt(v[i]);
}
return sum;
}
int main()
{
Vector v(5);
v[0] = 1;
v[1] = 2;
v[2] = 3;
v[3] = 4;
double sum = sqrt_sum(v);
std::cout << "size: " << size(v) << ", sum: " << sum << std::endl;
return 0;
}
编译与执行:
$ g++ -c -std=c++20 -fmodules-ts Vector.cpp # 创建Vector.o和gcm.cache/Vector.gcm
$ g++ -c -std=c++20 -fmodules-ts main.cpp # 创建main.o
$ g++ main.o Vector.o -o main # 创建main
or
$ g++ -std=c++20 -fmodules-ts Vector.cpp main.cpp -o main
$ ./main
size: 5, sum: 6.14626
查看模块文件信息(.gcm
):
$ readelf -p.gnu.c++.README gcm.cache/Vector.gcm
String dump of section '.gnu.c++.README':
[ 0] GNU C++ primary module interface
[ 21] compiler: 11.2.1 20210728 (Red Hat 11.2.1-1)
[ 4e] version: 2021/07/28-09:09
[ 68] module: Vector
[ 77] source: Vector.cpp
[ 8a] dialect: C++20/coroutines
[ df] repository: gcm.cache
module例子: 模块接口与实现分离¶
Vector.cpp:
module;
export module Vector;
export class Vector{
public:
Vector(int s);
double& operator[](int i);
int size() const;
private:
double *elem;
int sz;
};
export int size(const Vector& v);
Vector_impl.cpp:
module;
module Vector;
Vector::Vector(int s)
:elem{new double[s]}, sz{s}
{
}
double& Vector::operator[](int i){
return elem[i];
}
int Vector::size() const{
return sz;
}
int size(const Vector& v){ return v.size(); }
main.cpp:
#include <iostream>
#include <cmath>
import Vector;
double sqrt_sum(Vector& v)
{
double sum = 0;
for(int i=0; i != v.size(); i++)
{
sum += std::sqrt(v[i]);
}
return sum;
}
int main()
{
Vector v(5);
v[0] = 1;
v[1] = 2;
v[2] = 3;
v[3] = 4;
double sum = sqrt_sum(v);
std::cout << "size: " << size(v) << ", sum: " << sum << std::endl;
return 0;
}
编译与执行:
$ g++ -c -std=c++20 -fmodules-ts Vector.cpp # 生成Vector.o和gcm.cache/Vector.gcm
$ g++ -c -std=c++20 -fmodules-ts Vector_impl.cpp # 生成Vector_impl.o
$ g++ -c -std=c++20 -fmodules-ts main.cpp # 生成main.o
$ g++ -std=c++20 -fmodules-ts main.o Vector_impl.o Vector.o -o main
or
$ g++ -std=c++20 -fmodules-ts Vector.cpp Vector_impl.cpp main.cpp -o main
注意, Vector.cpp如果文件名改成Vector.hpp则会认为是普通头文件,而不是模块的接口文件(cmi),需加上 -x c++
选项:
$ g++ -c -std=c++20 -fmodules-ts Vector.hpp # 会直接当做普通的头文件处理而报错
Vector.hpp:1:1: error: module-declaration not permitted in header-unit
$ g++ -c -std=c++20 -fmodules-ts -x c++ Vector.hpp # 让编译器自己判断出是module接口文件(cmi)
$ g++ -c -std=c++20 -fmodules-ts -x c++ Vector.hpp Vector_impl.cpp
$ g++ -std=c++20 -fmodules-ts -x c++ Vector.hpp Vector_impl.cpp main.cpp -o main
module例子:子模块¶
- 子模块: TestMod.Vector
Vector.hpp:
module;
export module TestMod.Vector; // 这里其实是一个完整的模块名字,这个'.'没有任何从属意义
export class Vector{
public:
Vector(int s);
double& operator[](int i);
int size() const;
private:
double *elem;
int sz;
};
export int size(const Vector& v);
Vector.cpp:
module;
module TestMod.Vector;
Vector::Vector(int s)
:elem{new double[s]}, sz{s}
{
}
double& Vector::operator[](int i){
return elem[i];
}
int Vector::size() const{
return sz;
}
int size(const Vector& v){ return v.size(); }
- 子模块: TestMod.funcs
funcs.hpp:
funcs.cpp:
module;
#include <iostream>
module TestMod.funcs;
void func1(){
std::cout << "func1" << std::endl;
}
void func2(){
std::cout << "func2" << std::endl;
}
- 模块: TestMod
TestMod.hpp:
- main.cpp
#include <iostream>
#include <cmath>
import TestMod;
double sqrt_sum(Vector& v)
{
double sum = 0;
for(int i=0; i != v.size(); i++)
{
sum += std::sqrt(v[i]);
}
return sum;
}
int main()
{
Vector v(5);
v[0] = 1;
v[1] = 2;
v[2] = 3;
v[3] = 4;
double sum = sqrt_sum(v);
std::cout << "size: " << size(v) << ", sum: " << sum << std::endl;
func1();
func2();
return 0;
}
编译:
$ g++ -c -std=c++20 -fmodules-ts -x c++ Vector.hpp Vector.cpp
$ g++ -c -std=c++20 -fmodules-ts -x c++ funcs.hpp funcs.cpp
$ g++ -c -std=c++20 -fmodules-ts -x c++ TestMod.hpp
$ g++ -c -std=c++20 -fmodules-ts -x c++ Vector.hpp Vector.cpp funcs.hpp funcs.cpp TestMod.hpp
$ g++ -std=c++20 -fmodules-ts -x c++ Vector.hpp Vector.cpp funcs.hpp funcs.cpp TestMod.hpp main.cpp -o main
$ ./main
size: 5, sum: 6.14626
func1
func2
资料¶
- 微信搜索: 「 MinYiLife 」, 关注公众号!
- 本文链接: https://www.lesliezhu.com/blog/2022/10/23/cpp_2/
- 版权声明: 原创文章,如需转载请注明文章作者和出处。谢谢!