18 December 2021

C++ 核心指南目录

Github 上有个 C++ 核心指南。C++ 创始人 Bjarne Stroustrup 亲自参与维护。值得好好学习。

这个指南根据不同主题,分为多个章节,比如 P 代表编程哲学,I 代表接口设计,F 表示函数,C 表示类与类层级,R 表示资源管理,Per 表示性能,CP 表示并发与并行计算,Con 表示常量与不变性等。

在每个章节下,又用阿拉伯数字对规则进行进行标注。比如 P.1 Express ideas directly in code 这个章节讲的是如何直接地用代码表达编程设计理念。

每一个规则的阐述包含:

  • Reasons 理由
  • Examples 示例
  • Alternatives 替代方案
  • Exceptions 例外情况
  • Enforcement 工具强化
  • See also 指南中的其他相关规则
  • Notes 备注说明
  • Discussion 扩展讨论

接下来,学习学习第一条指南:P.1 Express ideas directly in code 直接用代码表达设计想法。

理由

一般来说,编译器是不会处理代码注释,更不会解析设计文档。很多程序员可能也没耐心仔细阅读代码注释和设计文档。所以,如果能直接用代码清晰的表达程序意图的话,可以提升机器和人类对代码的理解度。

例子

class Date {
public:
    Month month() const;
    int month();
};

以上代码中,第一个函数声明的表达内容更清晰: month 函数返回 Month 对象,并且该函数是一个 const 成员函数,所以 month 不改变 Date 的成员变量,也不能调用 Date 类中非 const 的成员函数。

相比较而言,第二个函数的声明,表达的代码信息和设计理念就少很多。别的程序员可能要连蒙带猜地使用你设计的函数,这样,容易产生的 bug。

坏例子:

void f(vector<string>& v)
{
    string val;
    cin >> val;
    // ...
    int index = -1;                    // bad, plus should use gsl::index
    for (int i = 0; i < v.size(); ++i) {
        if (v[i] == val) {
            index = i;
            break;
        }
    }
    // ...
}

std::find 标准库函数可以更直接地表达程序的目的。

void f(vector<string>& v)
{
    string val;
    cin >> val;
    // ...
    auto p = find(begin(v), end(v), val);  // better
    // ...
}

C++ 程序员要熟悉标准库和 GSL 库。可复用的库抽象了常用的功能,经过验证、性能保障、不易出错、有助理解。

例子

change_speed(double s);   // bad: what does s signify?
change_speed(2.3);

从上面的函数定义,我们无法得出这个 double 类型的变量 s 是什么意思。是新的速度,还是速度增量,速度的单位是什么?

以下例子中,最后一个使用了 C++ 的字面量后缀。可以用来表示不同的物理量。

change_speed(Speed s);    // better: the meaning of s is specified
change_speed(2.3);        // error: no unit
change_speed(23_m / 10s); // meters per second

另外,如果我们想让这个函数接收两种不同的变量,即新的速度或者速度的改变量,我们可以再额外定义个名为 Delta 的变量类型。

change_speed(Delta d);

以下程序定义了 _m_s 后缀,这样就可以很直观的表达速度是如何计算出来的,即:米/秒。

long double operator""_m(long double x) {return x;}
long double operator""_s(long double x) {return x;}
int main()
{
    auto speed = 23.1_m / 10.0_s;
    cout << speed << endl;
    return 0;
}
2.31

需要注意的是,根据 C++11 标准,只有以下数据类型才是合法的 operator""_xx 参数:

char const *
unsigned long long
long double
char const *, size_t
wchar_t const *, size_t
char16_t const *, size_t
char32_t const *, size_t

所以,其实还可以这样定义后缀,用来返回字符串的 size

size_t operator""_sz(char const *str, size_t sz) {return sz;}
int main()
{
    cout << "size of \'hello world\' is: " << "hello world"_sz << endl;
    return 0;
}
size of 'hello world' is: 11