【Effective C++ 读书笔记】导读 + 第一章

导读

  1. 声明 是告诉编译器某个东西的名称和类型。定义 的任务是提供编译器 一些声明时所遗漏的细节。
  2. size_t 是一种 typedef,是 C++ 计算个数时用的某种不带正负号类型。
  3. 默认构造函数是一个可以在不带有实参的情况下被调用的函数,即 没有参数 或者 每个参数都有默认值。
  4. 被声明为 explicit 的构造函数通常比 non-explicit 的更受欢迎,因为它们禁止编译器执行非预期的类型转换。
  5. 拷贝构造函数定义了一个对象如何进行 值传参 (passed by value)。但以值传参的方式传递用户自定义类型往往是个坏主意,引用常量传参 (pass-by-reference-to-const) 往往是比较好的选择。
  6. “未定义行为 (undefined behavior)”: 由于各种因素,某些 C++ 构件的行为没有定义,因此我们无法稳定预估运行期间会发生什么。(尽量避免未定义行为,这可能会使程序有时执行正常,有时崩坏,而又有时会产出不正确结果)
  7. ctor: 构造函数;dtor: 析构函数。
  8. “TR1 (Technical Report 1)” 是一份 描述加入 C++ 标准程序库的诸多新机制 的规范。所有 TR1 组件都被置于 std 的 tr1 命名空间中。
  9. “Boost” 是一个组织也是一个网站,其提供了 可移植,同僚复审和源码开放的 C++ 程序库。

条款 1: 将 C++ 视为一个语言联邦

C++ 属于多重范式的语言,我们可以将 C++ 视为一个由相关语言组成的联邦而不是单一语言。认识 C++ 的 “次语言” 并简单理解其守则就可以了 (不同 “次语言” 之间的守则可能不一样)。

  1. C: 当以 C++ 内的 C 成分工作时,高效编程守则映射出 C语言 的局限: 没有模板,没有异常,没有重载。
  2. C++ 面向对象: 面向设计古典守则 (类,封装,继承,多态,虚函数等等)。
  3. 模板 C++: 泛型编程,不排除 “唯模板适用” 的特殊条款,模板元编程 (TMP)…
  4. STL: 遵循 STL 的规约 (容器 + 迭代器 + 算法 + 函数对象)

示例: 值传参?引用传参?

  • C: 值传参 比 引用传参 高效。
  • 面向对象: 对于用户自定义类型,常量引用传参更高效。
  • 模板: 同面向对象,由于我们不知道要处理的对象的类型。
  • STL: 迭代器和函数都是在 C指针 基础上塑造出来的,因此 值传参 守则再次适用。

条款 2: 尽量用 const, enum, inline来替换 #define

总而言之,让 编译器 替换 预处理器。

#define ASPECT_RATIO 1.653

当预处理器进行了文本替换,则被替换的文本 (符号名称 ASPECT_RATIO) 可能没进入 符号表 (symbol table) 中。同时,报错提示会显示被替换的内容 (1.653) 而非目标文本 (ASPECT_RATIO),这会使得排错困难,尤其这可能出现在别人写的头文件中而你并不知道。

我们无法使用 #define 来创建一个 class 专属常量,因为 #define 并不重视作用域,同时它也不能够提供封装性 (没有所谓的 私有#define 的东西)。

class StaticTest {
public:
	// static const double value = 1.3; 不允许声明中赋值,需要在类外进行定义
	static const double value;
	static const int size = 10; // 可以在声明时赋值
	// static const int size; 若不在声明时赋值,下一行会出错因为其依赖size值
	int score[size];
};

const double StaticTest::value = 1.2;
// const int StaticTest::size = 10; 这句会出错,重复初始化
const int StaticTest::size; // 定义式,不赋值就没问题

注: “in-class 初值设定” 只允许对整数常量进行。

万一某些 “编译器” 不允许 类静态整数常量 在 类内进行初值设定,则我们可以改用 “the enum hack” 的补偿做法。

注: 取一个常量的地址是合法的,但取一个 enum 的地址就不合法,取 #define 的地址也通常不合法。如果我们想要避免别人获得指向我们整数常量的指针或引用,我们可以使用 enum。

enum hack 是 模板元编程的基础技术。

宏 和 内联函数

  • 宏看起来像函数,但其不会导致函数调用所带来的额外开销。
  • 但是宏有各种缺点 (不可预料行为和类型安全)。
  • 使用模板内联函数,我们可以获得宏的效率,同时还有一般函数所拥有的可预料行为以及类型安全性。

条款3: 尽可能使用 const

如果关键字 const 出现在了星号 (*) 左边,这表示被指物是常量;若出现在 星号 (*) 右边,表示指针自身是常量。如果出现在星号两边,这表示被指物和指针两者都是常量。

迭代器的作用就像一个 T* 指针。声明 迭代器 为 const 就像声明指针为 const 一样。如果我们希望迭代器所指的东西不可被改动,则需要 const_iterator。

令函数返回一个常量值,往往可以降低因用户错误而造成的意外,而又不至于放弃安全性和高效性。(将 operator* 的返回值声明为 const,可以防止以下没意义的赋值动作)

class Rational{...};
const Rational opeartor*(const Rational &lhs, const Rational &rhs);
Rational a, b, c;
...
(a * b) = c;

const 成员函数

将 const 实施于成员函数的目的,是为了确认该成员函数可作用于 const 对象身上。我们有const成员函数可用来处理取得的const对象。

注: 两个成员函数如果只是常量性 (constness) 不同,可以被重载。

若函数的返回类型是一个内置类型,则改动函数返回值就不合法。(就算其合法,如果以值作为返回对象,那么就算改变,也只是改变了副本的值而非本身)

bitwise const: 成员函数只有在不更改对象的任何成员变量 (除了static) 时才可以说是const的。
然而许多成员函数虽然不十足具备 const 性质但却能通过 bitwise 测试,比如一个更改了指针所指物的成员函数。
logical constness: 一个 const 成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才可如此。(mutable 关键字可以释放掉 非静态成员变量的bitwise constness约束)

条款4: 确认对象被使用前已先被初始化

内置类型的变量在某些语境下会被初始化 (如 0),但在其他语境中却不保证。
抽象类型的成员变量有时候会被初始化,但有时候不会。

读取未初始化的值会导致不明确的行为。

如果使用 C 部分的 C++,则初始化可能会招致运行期成本,就也不保证发生初始化。而当进入 非C部分 的 C++,规则可能就有所改变。(这也就是为什么 array 不保证其内容被初始化,但 vector 却有此保证)

成员初始化列表

内置类型之外的东西,我们需要确保每一个构造函数都将对象的每一个成员初始化。

C++ 规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。(初始化发生在成员的默认构造函数被自动调用之时)
因此,使用所谓的成员初始化列表替换赋值动作是比较好的习惯。

对于大多数类型,相比起先调用 默认构造函数 之后再调用 拷贝赋值运算符,单独只调用一次拷贝构造函数是更加高效的。对于内置类型,其初始化和赋值成本相同,但是为了一致性最好也使用成员初始化列表来进行初始化。

可以规定总是在 成员初始化列表中 列出所有成员变量,以免还得去记哪些成员变量可以无需初值。

注: 如果成员变量是 const 或 references,它们就一定需要初值而不能被赋值。

最简单的做法: 总是使用成员初始化列表,这样做有时候绝对必要,但又往往比赋值更加高效。

多个成员初始化列表可能会导致重复工作。此时,我们也可以通过 "伪初始化"来将赋值操作移到某个私有函数中供其他构造函数调用 (这在 成员变量初值由文件和数据库读入 这个场景中特别有用)。然而,比起这个方法,成员初始化列表完成的真正初始化通常更加可取。

成员初始化次序: 基类总是更早于派生类被初始化,而类的成员变量总是以其声明次序被初始化。

初始化次序不明确的场景

对于不同编译单元 (不同源文件) 内的 非局部静态对象 (不在函数中的静态对象),要决定其初始化次序相当困难,无解。

用一个设计解决: 将每个 非局部静态对象 搬到专属的函数中 (对象在函数中被定义为静态变量),接着这些函数返回一个指向该对象的引用。这就使得 局部静态对象 替代了 非局部静态对象。这一做法的基础在于: C++ 中,函数内的局部静态对象会保证在该函数被调用期间 以及 首次遇上该对象的定义 的时候被初始化。
同时,若你从未调用过该静态对象的仿真函数,绝不会引发构造和析构成本。

总结

因此,为了避免在对象初始化之前被使用,我们需要:

  • 手工初始化内置型的非成员对象。
  • 使用 成员初始化列表 来对成员对象初始化。
  • 在 初始化次序不确定 的场景 加强你的设计。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值