Skip to content

C++语言导学(2): 模块化

引言

程序组织的最佳方式是将程序看作依赖关系定义良好的一组模块,在逻辑上通过语言特性表达模块化,在物理上通过文件模块化实现高效的分别编译。

程序源码文件模块化最重要的一步是将每个组成部分的接口和实现分离开来,借助分别编译、后续链接的方式提高编译效率。

以往是通过头文件(*.hpp)和实现文件(*.cpp)来达到分离目的,在编译为动态链接库(*.so)供其他人使用的时候特别方便,可以达到隐藏细节的目的。

但使用 #include 是一种古老、易出错且代价不小的程序模块化组织方式,从C++20开始引入 module 特性,GCC11支持此功能:

$ g++ -std=c++20 -fmodules-ts file.cpp

使用模块功能可以提高编译效率和可维护性:

  • 一个模块只会编译一次,而include只要包含一次就会编译一次
  • 两个模块可以按任意顺序导入而不会改变它们的含义
  • import没有传递性

但目前来看,使用module功能不是那么容易,尤其是旧代码迁移、新旧代码并存编译会是一个不小的挑战。

另外,代码的静态库、二进制等分发会比较困难,细微的ABI差异都导致无法共享使用。

module例子: 头文件变成模块

hello.hpp:

constexpr int num=3;

run.cpp:

import "hello.hpp";

需要先将这个单独的头文件编译为模块后才能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:

export module TestMod.funcs;

export void func1();
export void func2();

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:

export module TestMod;
export import TestMod.Vector;
export import TestMod.funcs;
  • 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

资料