item01 视 C++ 为一个语言联邦

C++ 次语言组成部分 :::info

  • C
  • Objectc-Oriented C++
  • Template C++
  • STL :::

    item02 尽量以 const,enum,inline 替换 #define

    :::tips 或者说尽量以编译器替换预处理器! ::: 用#define会造成哪些问题呢:

  • 可能自己debug的时候不好debug:

    1. #define ASPECT_RATIO 1.653
  • 如果调用ASPECT_RATIO的时候报错,并不会显示ASPECT_RATIO,而只是会显示值,因为此名称并未进入记号表

  • 预处理器会在出现该宏定义的时候就复制一份值,因此会出现多份,而const只会出现一份。

    class 中定义常量

    1. class GamePlayer
    2. {
    3. private:
    4. static const int NumTurns = 5; // 常量式申明
    5. int Scores[NumTurns]; // 使用该常量
    6. };

上面是声明式而非定义式,如果编译器需要一个定义式时,需要提供如下定义式:

  1. const int GamePlayer::NumTurns; // NumTurns 的定义

define 无法创建一个 class 专属变量,因为它不重视作用域(scope),所以也不能提供任何封装性

enum hack

旧式编译器可能会要求将初值赋在定义式

  1. class GamePlayer
  2. {
  3. private:
  4. static const int NumTurns; // 申明在头文件
  5. };
  6. const int GamePlayer::NumTurns = 5; // 定义在实现文件

如果编译器需要在编译期间就知道一个 class 的常量值,如上述 Scores[NumTurns] 数组就要求知道 NumTurns 大小,而编译器又不支持在声明式中赋初值时,可以改用 “the enum hack” 做法

  1. class GamePlayer
  2. {
  3. private:
  4. enum { NumTurns = 5 };
  5. int Scores[NumTurns];
  6. };

其理论基础是,enum 可以被充当 int 使用,需要熟悉 enum hack 的理由:

  • enum hack 更像 #define,例如取 const 变量地址是合法的,而去 enum 的地址就不合法,有时这正是想要的,可以避免别人用 pointer 或者 reference 指向你的整数常量
  • 不够优秀的编译器可能会给 const 整数变量分配不必要的内存空间,而 enum 和 #define 则一定不会
  • 使用广泛,而且 enum hack 是模板元编程(template metaprograming)的基础技术

使用 inline 替代 #define

可以带来宏的效率以及类型安全性

  1. #define CALL_WITH_MAX (a, b) f ((a) > (b) ? : (a), (b))
  2. int a = 5, b = 0;
  3. CALL_WITH_MAX (++a, b); // a 被累加一次
  4. CALL_WITH_MAX (++a, b+10); // a 被累加二次

即使全部加上括号,也可能会有非预期的行为发生。template inline 可以避免这些问题

  1. template <typename T>
  2. inline void CALL_WITH_MAX (const T&a, const T& b)
  3. {
  4. f(a > b ? a : b);
  5. }

而且 inline 遵守作用域(scope),完全可以实现 class private inline

item03 尽量使用const

const 成员函数

:::info 将const施加在成员函数的目的是为了确认该成员函数可以作用于const对象上。 :::

  • 能够使 class 接口更加清晰,得知哪个函数会改动对象而哪个不会
  • 使 pass by reference-to-const 成为可能

const 重载

:::tips 两个成员函数如果只是常量性不同,可以被重载 :::

  1. class TextBlock {
  2. public:
  3. const char& operator[] (std::size_t) const { return text[position]; } // operator[] for const 对象
  4. char& operator[] (std::size_t) { return text[position]; } // operator[] for non-const 对象
  5. private:
  6. std::string text;
  7. };
  8. TextBlock tb("Hello");
  9. const TextBlock ctb("Hello");
  10. tb[0] = 'x'; // 没问题
  11. ctb[0] = 'x'; // 错误

bitwise constness 和 logical constness

:::info 关于const意味着什么,有两个理念,即bitewise constness&logical constness ::: 主张 bitwise constness 的人认为,一旦成员函数被申明为 const,就不能改变任何成员变量(除 static),即不更改任意一个 bit。然后这种观念很容易出现不合理的地方,比如某个对象内包含一个指针,某个 const 成员函数未改变指针但是改变了指针所指的值,虽然符合 bitwise constness,但是导致了反直观效果。

主张 logical constness 的人认为,允许 const 成员函数改变某些 bits,但是应该不被客户端感知,或者说符合预期。如下面获取一个高速缓存的文本区块的长度,其中 textLength 和 lengthIsValid 会实时变更,且符合预期,则可以使用 mutable 关键字申明可变。

在const和non-const成员函数中避免重复

如果实现 const 和 non-const 两个版本的函数,代码会重复冗长,可以重载时以 non-const 调用 const 避免重复,反向则不行。

  1. class CTextBlock {
  2. public:
  3. const char& operator[] (std::size_t) const { return text[position]; }
  4. char& operator[] (std::size_t) {
  5. return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
  6. }
  7. private:
  8. std::string text;
  9. };
  1. 非const成员函数将自己的this指针转换成const版本的,
  2. 然后在调用就会调用const版本的成员函数;
  3. 然后再将const版本的成员函数的返回值进行转换(可选),转换成非const版本

    item04 确定对象使用前已经被初始化

    :::danger 永远在使用对象之前将他初始化 ::: 赋值和初始化是有区别的,在构造函数内实行的是赋值,初始化是通过列表初始化进行的 ```cpp A::A(const std::string& name, const std::string& Address) { theName = name; theAddress = Address; }

A::A(const std::string& name, const std::string& Address) : theName(name) , theAddress(Address) {} ```

不同编译对象的static成员初始化顺序

C++ 对于不同编译单元的 non-local static 对象初始化次序无明确定义。这很难,最常见的,多个编译单元内的 non-local static 对象经由“模板隐式具现化(implict template instantiations)”形成。 :::tips 一个简单的解决方法,可以把 non-local static 对象搬到专属函数,作为 local static 对象,该函数返回 reference 指向该对象,用户直接调用函数而非对象,最常见的就是单例模式的实现。 ::: 该方法的理论基础是:C++ 保证函数内的 local-static 对象会在“访问函数”、“首次遇上对象定义式”时被初始化。