item01 视 C++ 为一个语言联邦
C++ 次语言组成部分 :::info
- C
- Objectc-Oriented C++
- Template C++
-
item02 尽量以 const,enum,inline 替换 #define
:::tips 或者说尽量以编译器替换预处理器! ::: 用#define会造成哪些问题呢:
可能自己debug的时候不好debug:
#define ASPECT_RATIO 1.653
如果调用ASPECT_RATIO的时候报错,并不会显示ASPECT_RATIO,而只是会显示值,因为此名称并未进入记号表
- 预处理器会在出现该宏定义的时候就复制一份值,因此会出现多份,而const只会出现一份。
class 中定义常量
class GamePlayer
{
private:
static const int NumTurns = 5; // 常量式申明
int Scores[NumTurns]; // 使用该常量
};
上面是声明式而非定义式,如果编译器需要一个定义式时,需要提供如下定义式:
const int GamePlayer::NumTurns; // NumTurns 的定义
define 无法创建一个 class 专属变量,因为它不重视作用域(scope),所以也不能提供任何封装性
enum hack
旧式编译器可能会要求将初值赋在定义式
class GamePlayer
{
private:
static const int NumTurns; // 申明在头文件
};
const int GamePlayer::NumTurns = 5; // 定义在实现文件
如果编译器需要在编译期间就知道一个 class 的常量值,如上述 Scores[NumTurns] 数组就要求知道 NumTurns 大小,而编译器又不支持在声明式中赋初值时,可以改用 “the enum hack” 做法
class GamePlayer
{
private:
enum { NumTurns = 5 };
int Scores[NumTurns];
};
其理论基础是,enum 可以被充当 int 使用,需要熟悉 enum hack 的理由:
- enum hack 更像 #define,例如取 const 变量地址是合法的,而去 enum 的地址就不合法,有时这正是想要的,可以避免别人用 pointer 或者 reference 指向你的整数常量
- 不够优秀的编译器可能会给 const 整数变量分配不必要的内存空间,而 enum 和 #define 则一定不会
- 使用广泛,而且 enum hack 是模板元编程(template metaprograming)的基础技术
使用 inline 替代 #define
可以带来宏的效率以及类型安全性
#define CALL_WITH_MAX (a, b) f ((a) > (b) ? : (a), (b))
int a = 5, b = 0;
CALL_WITH_MAX (++a, b); // a 被累加一次
CALL_WITH_MAX (++a, b+10); // a 被累加二次
即使全部加上括号,也可能会有非预期的行为发生。template inline 可以避免这些问题
template <typename T>
inline void CALL_WITH_MAX (const T&a, const T& b)
{
f(a > b ? a : b);
}
而且 inline 遵守作用域(scope),完全可以实现 class private inline
item03 尽量使用const
const 成员函数
:::info 将const施加在成员函数的目的是为了确认该成员函数可以作用于const对象上。 :::
- 能够使 class 接口更加清晰,得知哪个函数会改动对象而哪个不会
- 使 pass by reference-to-const 成为可能
const 重载
:::tips 两个成员函数如果只是常量性不同,可以被重载 :::
class TextBlock {
public:
const char& operator[] (std::size_t) const { return text[position]; } // operator[] for const 对象
char& operator[] (std::size_t) { return text[position]; } // operator[] for non-const 对象
private:
std::string text;
};
TextBlock tb("Hello");
const TextBlock ctb("Hello");
tb[0] = 'x'; // 没问题
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 避免重复,反向则不行。
class CTextBlock {
public:
const char& operator[] (std::size_t) const { return text[position]; }
char& operator[] (std::size_t) {
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
private:
std::string text;
};
- 非const成员函数将自己的this指针转换成const版本的,
- 然后在调用就会调用const版本的成员函数;
- 然后再将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 对象会在“访问函数”、“首次遇上对象定义式”时被初始化。