最近在读 Effective C++,想要做点笔记,归类和书中的每个模块一样,但跟模块里的具体顺序可能不太一致。不会对书中每个细节都涉及,主要记下自己觉得重要的内容。
What is C++?
C++ 是一个多重范型编程语言( multiparadigm programming language),一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(genetic)、元编程形式(metaprogramming)的语言。
我们 可以视 C++ 为主要的四个次语言组成,包括:C、Object-Oriented C++、Template C++、STL。(觉得这个归类好有道理...废话...)
const 那些事
语法:如果关键字 const 出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。
STL 迭代器的作用就像个 T* 指针,声明迭代器为 const 就像声明指针为 const 一样(即声明一个 T* const 指针),表示这个迭代器不得指向不同的东西,但所指的东西的值是可以改动的。如果要迭代器所指的东西不可被改动(即希望 STL 模拟一个 const T* 指针),则要用 const_iterator.
const vector<int>::iterator iter = vec.begin(); // 所指物可变,迭代器不可变 vector<int>::const_iterator cIter = vec.begin(); // 所指物不可变,迭代器可变
尽可能不要用 #define 定义常量,而是用 const 变量。
定义常量指针时有必要将指针(而不只是指针所指之物)声明为 const。(成为指向 const 的 const 指针)
class 专属常量应该使其成为 class 的一个 static 成员,这样既可以限制常量作用域,而且只有一个实体。
令函数返回一个常量值,可以降低因客户错误造成的意外,又不至于放弃安全性和高效性。
例如重载运算符 *,加入返回的不是 const, 可能会出现这样的错误。
if (a*b = c) ... // 将比较误写成了赋值
两个成员函数如果只是常量性不同,可以被重载。例如:
const char& operator[](size_t position) const { } char& operator[](size_t position){ }
bitwise constness:
成员函数只有在不更改对象之任何成员变量时才可以说是 const。bitwise constness 正是 C++ 对常量性的定义。
结果是很多成员函数不十足具备 const 性质,却能通过编译。例如 char* 属于对象而非其所指物,那么就可以通过成员函数返回的指针去修改其所指物,可以通过编译器检测。
logical constness:
一个成员函数可以修改它所处理的对象内的某些 bits,但只有在客户端侦测不出的情况下才行。
要在 const 成员里修改数据,需要将变量声明为 mutable。mutable 释放掉 non-static 成员变量的 bitwise constness 约束。例如: mutable size_t textLength;
编译器强制实施 bitwise constness,但你编写程序时应该使用“概念上的常量性”。
const 与 非 const 的复用
当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本代码可避免重复。
注意:const 成员函数调用 non-const 成员函数是一种错误行为。
class TextBlock{ public: const char& operator[] (std::size_t position) const { ...; ...; return text[position]; } char& operator[] (std::size_t position) { return const_cast<char&>( static_cast<const TextBlock&>(*this)[position] ); //要看懂这个转化 } }
函数的 little tips
参数传递:
对于内置(也就是 C-like)类型而言 pass-by-value 通常比 pass-by-reference 高效。在 Object-Oriented C++ 中,由于用户自定义构造函数和析构函数的存在, pass-by-reference-to-const 往往更好,Template C++ 也如是。而对 STL 的迭代器和函数对象而言,旧式的 C pass-by-value 更加适用。
inline:用 inline 函数代替形似函数的宏,以避免不必要的错误。
对象初始化
为内置对象进行手工初始化,因为 C++ 不保证初始化它们。
构造函数最好使用成员初始初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在 class 中的声明次序相同。使用成员初值列有时候绝对必要,又往往比赋值更高效。
class 的成员变量总是以其声明次序被初始化。为避免问题,你初始化的顺序应该和声明的顺序一样。
为免除”跨编译单元之初始化次序“问题,请以 local static 对象替换 non-local static 对象。因为 C++ 对”定义于不同的编译单元内的 non-local static 对象“初始化相对顺序并无明确定义,要消除该问题,应该如下设计:将每个 non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为 static )。C++ 保证,函数内的 local static 对象会在”该函数被调用期间“”首次遇上该对象之定义式“时被初始化。
class FileSystem{}; FileSystem& tfs() { static FileSystem fs; return fs; } class Directory{...}; Directory::Directory(params){ ...; std::size_t disks = tfs().numDisks(); ...; } Directory& tempDir() { static Directory td; return td; }
然而,任何一种 non-const static 对象,不论它是 local 还是 non-local,在多线程的环境下”等待某事发生“都会有麻烦,处理这个麻烦的一个做法是:
在程序的单线程启动阶段(single-threaded startup portion)手工调用所有 reference-returning 函数,这可消除与初始化有关的"竞速形势”(race condition)。