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;通常来说,返回一个带有类型的变量能够使代码更清晰。