Chapter 3 Modularity – A Tour of C++

3.3 Modules(C++20)

c++对于#include的使用已经有着非常长的时间了,但是这一机制容易造成错误,且开销也较大。如果你在101个translation unit(例如cpp文件)中#include header.h,那么该头文件将被编译器处理101次。如果你#include header1.h在header2.h之前,那么header1中的宏可能会影响header2。因此这两个头文件是互相影响的,显然该机制并不合理。

不过现在我们有机会以一种更好的方法来表现c++中的模块化理念。这一特性被称为module,但是它还未包含在ISO c++中。

// Vector.cpp 文件

module; // 该编译模式定义了一个module

export module Vector; // 定义了一个被称为Vector的module

export class Vector
{
public:
    Vector(int s);
    double& operator[](int i);
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()
{
    return sz;
}

export int size(const Vector& v) { return v.size(); }

上述代码定义了一个被称为Vector的module,其输出了class Vector以及其所有的成员函数与非成员函数size()。当我们需要使用该module时,只需要在代码中进行import即可。

// user.cpp 文件

import Vector;
#include <cmath>

double sqrt_sum(Vector& v)
{
    double sum = 0;
    for(int i = 0l i != v.size(); ++i)
        sum += std::sqrt(v[i]);

    return sum;
}

在上述代码中,我们并没有使用import来使用标准库中的cmath,而是使用了古老的#include。但是,我们需要注意,header与module的差别不仅仅在于语法:

  • 一个module只会被编译一次,无论其在多少个translation unit中被使用。
  • 两个module在文件中import的顺序并不会造成任何影响。
  • 如果你在一个module中import了某些内容,该module的使用者并不能隐式地读取到你所import的内容;import并不具有传递性。

module在维护性与编译时间这两方面有着非常大的优势。

3.5.5 Static Assertions

大多数exception的报错都发生在程序运行时。但是我们也可以在编译时进行检测,并生成一些编译器错误信息,我们只需要使用static_assert()即可。

3.6 Function Arguments and Return Values

对于函数所传递的参数或者返回值,我们需要注意以下几点:

  • 对象被复制(copy)还是被分享(share)?
  • 对象如果被分享,那么它是不是可变的?
  • 对象如果被move了,那么是不是留下一个“空对象”?

3.6.3 Structured Binding

一个函数只能返回一个单一变量,但是其可以是一个class对象并包含多个成员。

struct Entry
{
    string name;
    int value;
};

Entry read_entry(istream& is)
{
    string s;
    int i;
    is >> s >> i;
    return {s, i};
}

auto e = read_entry(cin);
cout << "{ " << e.name << "," << e.value << " }\n";

上述代码中,{s, i}构成了Entry对象的返回值。同样,我们也可以Entry的成员变量“打包”为一个返回对象,这就是所谓的structured binding。

auto [n, v] = read_entry(is);
cout << "{ " << n << "," << v << " }\n";

通常我们会将structured binding运用于没有private数据的对象,同时成员的数量必须与binding变量的数量相同,且class中没有静态数据。

void incr(map<string, int>& m)
{
    for(auto& [key,value] : m)
        ++value;
}

3.7 Advice

[08] 不要在头文件中使用using namespace。

[18] 如果你的函数不能throw exception,那么将其声明为noexcept。

[26] 不要过度使用structured binding;通常来说,返回一个带有类型的变量能够使代码更清晰。

 

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据