即便是C++阵营里最忠实的信徒,也不得不承认:C++语言并不完美。实际上,世界上也没有完美的编程语言。
如何克服C++类型系统的不足?在C++中,如何利用约束、契约和断言来实施软件设计?如何处理被C++标准所忽略的动态库、静态对象以及线程等有关的问题?隐式转换有何替代方案?本书将为你一一解答这些问题。针对C++的每一个不完美之处,本书都具体地分析原因,并探讨实用的解决方案。书中也不乏许多作者创新的、你从未听说过或使用的技术,但这些确实能帮助你成为C++方面的专家。
本书适合有一定经验的C++程序员和项目经理阅读,也适合对C++编程的一些专门或高级话题感兴趣的读者参考。
第一部分 基础知识 1
第1章 强制设计:约束、契约和断言 3
1.1 绿蛋和火腿 4
1.2 编译期契约:约束 4
1.2.1 must_have_base() 5
1.2.2 must_be_subscriptable() 6
1.2.3 must_be_subscriptable_
as_decayable_pointer() 6
1.2.4 must_be_pod() 7
1.2.5 must_be_same_size() 9
1.2.6 使用约束 10
1.2.7 约束和TMP 11
1.2.8 约束:尾声 11
1.3 运行期契约:前置条件、后
置条件和不变式 12
1.3.1 前置条件 13
1.3.2 后置条件 13
1.3.3 类不变式 15
1.3.4 检查?总是进行 16
1.3.5 DbC还是不DbC 17
1.3.6 运行期契约:尾声 17
1.4 断言 18
1.4.1 获取消息 19
1.4.2 不恰当的断言 20
1.4.3 语法以及64位指针 21
1.4.4 避免使用verify() 21
1.4.5 为你的断言命名 22
1.4.6 避免使用#ifdef_DEBUG 23
1.4.7 DebugBreak()和int 3 24
1.4.8 静态/编译期断言 24
1.4.9 断言:尾声 26
第2章 对象生命期 27
2.1 对象生命周期 27
2.2 控制你的客户端 28
2.2.1 成员类型 28
2.2.2 缺省构造函数 28
2.2.3 拷贝构造函数 29
2.2.4 拷贝赋值 29
2.2.5 new和delete 30
2.2.6 虚析构 30
2.2.7 explicit 31
2.2.8 析构函数 31
2.2.9 友元 32
2.3 MIL及其优点 33
2.3.1 取得一块更大的场地 35
2.3.2 成员顺序依赖 37
2.3.3 offsetof() 38
2.3.4 MIL:尾声 39
第3章 资源封装 40
3.1 资源封装分类 40
3.2 POD类型 41
3.2.1 直接操纵 41
3.2.2 API函数和透明类型 42
3.2.3 API函数和不透明类型 42
3.3 外覆代理类 43
3.4 RRID类型 45
3.4.1 缺省初始化:缓式初
始化 46
3.4.2 未初始化 48
3.5 RAII类型 51
3.5.1 常性RAII和易变性
RAII 51
3.5.2 内部初始化和外部初
始化 53
3.5.3 RAII排列 53
3.6 RAII:尾声 54
3.6.1 不变式 54
3.6.2 错误处理 54
第4章 数据封装和值类型 55
4.1 数据封装的分类学 55
4.2 值类型和实体类型 56
4.3 值类型的分类学 56
4.4 开放式类型 58
4.4.1 POD开放式类型 58
4.4.2 C++数据结构 59
4.5 封装式类型 60
4.6 值类型 61
4.7 算术值类型 62
4.8 值类型:尾声 63
4.9 封装:尾声 64
第5章 对象访问模型 68
5.1 确定性生命期 68
5.2 返回拷贝 70
5.3 直接交给调用者 70
5.4 共享对象 71
第6章 域守卫类 73
6.1 值 73
6.2 状态 78
6.3 API和服务 83
6.3.1 API 83
6.3.2 服务 86
6.4 语言特性 87
第二部分 生存在现实世界 89
第7章 ABI 91
7.1 共享代码 91
7.2 C ABI需求 93
7.2.1 结构布局 93
7.2.2 调用约定、符号名以及
目标文件格式 94
7.2.3 静态连接 94
7.2.4 动态连接 95
7.3 C++ ABI需求 96
7.3.1 对象布局 97
7.3.2 虚函数 97
7.3.3 调用约定和名字重整 97
7.3.4 静态连接 99
7.3.5 动态连接 99
7.4 现在知道怎么做了 100
7.4.1 extern'C' 100
7.4.2 名字空间 103
7.4.3 extern'C++' 103
7.4.4 获得C++类的句柄 106
7.4.5 '由实现定义'的隐患 108
第8章 跨边界的对象 110
8.1 近乎可移植的虚函数表 110
8.1.1 虚函数表布局 111
8.1.2 动态操纵虚函数表 113
8.2 可移植的虚函数表 114
8.2.1 利用宏进行简化 116
8.2.2 兼容的编译器 116
8.2.3 可移植的服务端对象 117
8.2.4 简化可移植接口的
实现 119
8.2.5 C客户代码 120
8.2.6 OAB的约束 120
8.3 ABI/OAB尾声 121
第9章 动态库 123
9.1 显式调用函数 123
9.1.1 显式调用C++函数 124
9.1.2 打破C++访问控制 125
9.2 同一性:连接单元和连接
空间 125
9.2.1 连接单元 125
9.2.2 连接空间 126
9.2.3 多重身份 126
9.3 生命期 127
9.4 版本协调 128
9.4.1 丢失的函数 128
9.4.2 变化的签名 128
9.4.3 行为的改变 129
9.4.4 常量 129
9.5 资源所有权 130
9.5.1 共享池 130
9.5.2 返还给被调用方 130
9.6 动态库:尾声 131
第10章 线程 132
10.1 对整型值的同步访问 133
10.1.1 操作系统函数 134
10.1.2 原子类型 135
10.2 对(代码)块的同步访问:
临界区 136
10.2.1 进程间互斥体和进程内互斥体 137
10.2.2 自旋互斥体 138
10.3 原子整型的性能 139
10.3.1 基于互斥体的原子
整型 139
10.3.2 运行期按架构派发 141
10.3.3 性能比较 142
10.3.4 原子整型操作:尾声 143
10.4 多线程扩展 144
10.4.1 synchronized 144
10.4.2 匿名synchronized 147
10.4.3 atomic 147
10.5 线程相关的存储 148
10.5.1 重入 148
10.5.2 线程相关的数据/线
程局部存储 148
10.5.3 declspec(thread)和
TLS 150
10.5.4 Tss库 150
10.5.5 TSS的性能 155
第11章 静态对象 156
11.1 非局部静态对象:全局对象 157
11.1.1 编译单元内的顺序性 158
11.1.2 编译单元间的顺序性 159
11.1.3 利用main()避免全局
变量 161
11.1.4 全局对象尾声:顺
序性 162
11.2 单件 163
11.2.1 Meyers单件 163
11.2.2 Alexandrescu单件 164
11.2.3 即时Schwarz计数器:
一个极妙的主意 165
11.2.4 对API计数 166
11.2.5 被计数的API、外覆类、
代理类:最终得到一个
顺序化的单件 168
11.3 函数范围内的静态对象 169
11.3.1 牺牲缓式求值能力 171
11.3.2 自旋互斥体是救星 171
11.4 静态成员 172
11.4.1 解决连接问题 172
11.4.2 自适应代码 174
11.5 静态对象:尾声 175
第12章 优化 176
12.1 内联函数 176
12.1.1 警惕过早优化 176
12.1.2 只含有头文件的库 177
12.2 返回值优化 177
12.3 空基类优化 180
12.4 空派生类优化 183
12.5 阻止优化 184
第三部分 语言相关的议题 188
第13章 基本类型 189
13.1 可以给我来一个字节吗 189
13.1.1 标明符号 190
13.1.2 一切都在名字之中 190
13.1.3 窥探void内部 191
13.1.4 额外的安全性 191
13.2 固定大小的整型 192
13.2.1 平台无关性 193
13.2.2 类型相关的行为 195
13.2.3 固定大小的整型:
尾声 197
13.3 大整型 198
13.4 危险的类型 200
13.4.1 引用和临时对象 200
13.4.2 bool 201
第14章 数组和指针 204
14.1 不要重复你自己 204
14.2 数组退化为指针 206
14.2.1 下标索引操作符的
交换性 206
14.2.2 阻止退化 208
14.3 dimensionof() 209
14.4 无法将数组传递给函数 211
14.5 数组总是按地址进行传递 214
14.6 派生类的数组 215
14.6.1 通过指针保存多态
类型 216
14.6.2 提供非缺省的构造
函数 217
14.6.3 隐藏向量式new和
delete 218
14.6.4 使用std::vector 218
14.6.5 确保类型的大小相同 219
14.7 不能拥有多维数组 222
第15章 值 226
15.1 NULL的是非曲直 226
15.2 回到0 232
15.3 屈服于事实 235
15.4 字面量 236
15.4.1 整型 236
15.4.2 后缀 238
15.4.3 字符串 240
15.5 常量 243
15.5.1 简单常量 243
15.5.2 类类型常量 244
15.5.3 成员常量 245
15.5.4 类类型的成员常量 248
第16章 关键字 251
16.1 interface 251
16.2 temporary 253
16.3 owner 256
16.4 explicit(_cast) 261
16.4.1 使用显式访问函数 263
16.4.2 模拟显式转换 264
16.4.3 使用特性垫片 265
16.5 unique 266
16.6 final 267
16.7 不被支持的关键字 267
第17章 语法 270
17.1 类的代码布局 270
17.2 条件表达式 273
17.2.1 '使它布尔' 273
17.2.2 一个危险的赋值 275
17.3 for 277
17.3.1 初始化作用域 277
17.3.2 异质初始化类型 278
17.4 变量命名 280
17.4.1 匈牙利命名法 280
17.4.2 成员变量 281
第18章 Typedef 284
18.1 指针typedef 286
18.2 定义里面有什么 288
18.2.1 概念性的类型定义 288
18.2.2 上下文相关的类型
定义 289
18.3 别名 292
18.3.1 错误的概念性类型
互换 293
18.3.2 不能对概念性类型进
行重载 294
18.4 true_typedef 294
18.5 好的、坏的、丑陋的 300
18.5.1 好的typedef 300
18.5.2 坏的typedef 303
18.5.3 可疑的typedef 304
第四部分 感知式转换 308
第19章 强制 310
19.1 隐式转换 310
19.2 C++中的强制 311
19.3 适合使用C强制的场合 312
19.4 模仿强制 314
19.5 explicit_cast 316
19.6 literal_cast 321
19.7 union_cast 323
19.8 comstl::interface_cast 327
19.8.1 interface_cast_addref 328
19.8.2 interface_cast_noaddref 329
19.8.3 interface_cast_test 329
19.8.4 接口强制操作符的
实现 330
19.8.5 保护引用计数 333
19.8.6 interface_cast_base 334
19.8.7 IID_traits 335
19.8.8 interface_cast 尾声 336
19.9 boost::polymorphic_cast 337
19.10 强制:尾声 339
第20章 垫片 341
20.1 拥抱变化 拥抱自由 341
20.2 特性垫片 344
20.3 逻辑垫片 346
20.4 控制垫片 347
20.5 转换垫片 348
20.6 复合式垫片概念 350
20.6.1 访问垫片 351
20.6.2 返回值生命期 352
20.6.3 泛化的类型操纵 354
20.6.4 效率方面的考虑 356
20.7 名字空间和Koenig查找 357
20.8 为何不使用traits 359
20.9 结构一致性 360
20.10 打破巨石 362
20.11 垫片:尾声 363
第21章 饰面 365
21.1 轻量级RAII 366
21.2 将数据和操作绑定在一起 367
21.2.1 pod_veneer 368
21.2.2 创建日志消息 370
21.2.3 减少浪费 371
21.2.4 类型安全的消息类 372
21.3 '擦亮'饰面概念 374
21.4 饰面:尾声 376
第22章 螺栓 377
22.1 添加功能 377
22.2 皮肤选择 378
22.3 非虚重写 379
22.4 巧用作用域 380
22.5 拟编译期多态:逆反式
螺栓 383
22.6 参数化多态包装 384
22.7 螺栓:尾声 386
第23章 模板构造函数 387
23.1 不易察觉的开销 389
23.2 悬挂引用 389
23.3 模板构造函数特化 391
23.4 实参代理 392
23.5 明确实参的范畴 394
23.6 模板构造函数:尾声 395
第五部分 操作符 396
第24章 operator bool() 398
24.1 operator int() const 398
24.2 operator void *() const 399
24.3 operator bool() const 400
24.4 operator !() const 401
24.5 operator boolean const *()
const 401
24.6 operator int boolean::*()
const 402
24.7 在现实世界中操作 402
24.8 operator! 407
第25章 快速、非侵入性的字符串
拼接 408
25.1 fast_string_concatenator<> 409
25.1.1 与用户自定义的字符
串类协同工作 409
25.1.2 将'拼接子'串起来 410
25.1.3 fast_string_concatenator
类 411
25.1.4 内部实现 413
25.2 性能 417
25.3 与其他字符串类协作 420
25.3.1 整合进标准库中 420
25.3.2 整合进可改动的现存
类中 420
25.3.3 与不可更改的类互
操作 420
25.4 拼接提示 421
25.5 病态括号 422
25.6 标准化 423
第26章 你的地址是什么 424
26.1 无法得到真实的地址 424
26.1.1 STL式元素存放 424
26.1.2 ATL外覆类和CAdapt 425
26.1.3 获取真实的地址 426
26.2 在转换过程中发生了什么 427
26.3 我们返回什么 429
26.4 你的地址是什么:尾声 431
第27章 下标索引操作符 434
27.1 指针转换与下标索引操作符 434
27.1.1 选择隐式转换操作符 436
27.1.2 选择下标索引操作符 437
27.2 错误处理 437
27.3 返回值 439
第28章 增量操作符 441
28.1 缺少后置式操作符 442
28.2 效率 443
第29章 算术类型 446
29.1 类定义 446
29.2 缺省构造 447
29.3 初始化(值构造) 447
29.4 拷贝构造函数 450
29.5 赋值 450
29.6 算术操作符 451
29.7 比较操作符 452
29.8 访问值 452
29.9 sinteger64 453
29.10 截断、提升以及布尔测试 453
29.10.1 截断 453
29.10.2 提升 455
29.10.3 布尔测试 455
29.11 算术类型:尾声 456
第30章 短路 458
第六部分 扩展C++ 460
第31章 返回值生命期 461
31.1 返回值生命期问题分类 461
31.1.1 局部变量 462
31.1.2 局部静态对象 462
31.1.3 析构后指针
(Postdestruction Pointers) 462
31.2 为何按引用返回 462
31.3 解决方案1:
integer_to_string<> 462
31.4 解决方案2--TSS 465
31.4.1 --declspec(thread) 466
31.4.2 Win32 TLS 466
31.4.3 平台无关的API 469
31.4.4 RVL 470
31.5 解决方案3--扩展RVL 470
31.5.1 解决线程内的RVL-LS问题 471
31.5.2 RVL 472
31.6 解决方案4--静态数组大
小决议 472
31.7 解决方案5--转换垫片 474
31.8 性能 476
31.9 RVL:垃圾收集的大胜利 477
31.10 可能的应用 478
31.11 返回值生命期:尾声 478
第32章 内存 479
32.1 内存分类 479
32.1.1 栈和静态内存 479
32.1.2 栈扩张 480
32.1.3 堆内存 481
32.2 两者之间的折衷 481
32.2.1 alloca() 482
32.2.2 VLA 483
32.2.3 auto_buffer<> 483
32.2.4 使用auto_buffer 486
32.2.5 EBO,在哪里 487
32.2.6 性能 488
32.2.7 堆、栈以及其他 490
32.2.8 pod_vector<> 491
32.3 配置器 493
32.3.1 函数指针 493
32.3.2 配置器接口 494
32.3.3 每库初始化(Per-library
Initialization) 495
32.3.4 每调用指定(Per-Call
Specification) 496
32.4 内存:尾声 496
第33章 多维数组 497
33.1 激活下标索引操作符 498
33.2 运行时确定大小 499
33.2.1 可变长数组 499
33.2.2 vector< ... vector
... > 500
33.2.3 boost::multi_array 501
33.2.4 fixed_array_1/2/3/4d 501
33.3 编译期确定大小 505
33.3.1 boost::array 506
33.3.2 static_array_1/2/3/4d 506
33.4 块访问 508
33.4.1 使用std::fill_n() 509
33.4.2 array_size垫片 510
33.5 性能 512
33.5.1 运行期确定大小 513
33.5.2 编译期确定大小 514
33.6 多维数组:尾声 515
第34章 仿函数和区间 516
34.1 语法混乱 516
34.2 for_all() 517
34.2.1 数组 518
34.2.2 命名 518
34.3 局部仿函数 520
34.3.1 手写循环 520
34.3.2 自定义仿函数 521
34.3.3 内嵌的仿函数 521
34.3.4 温和一些 523
34.3.5 泛化的仿函数:类型隧
道(Type Tunneling) 524
34.3.6 再进一步,走得太
远了 526
34.3.7 局部仿函数和回调
API 527
34.4 区间 529
34.4.1 区间概念 529
34.4.2 概念性区间 531
34.4.3 可迭代区间 533
34.4.4 区间算法和标签 533
34.4.5 过滤器 535
34.4.6 虚伪 536
34.5 仿函数和区间:尾声 536
第35章 属性 537
35.1 编译器扩展 539
35.2 可供选择的实现方案 539
35.2.1 将属性的实现分门
别类 540
35.2.2 EMO 540
35.3 字段属性 541
35.3.1 field_property_get 541
35.3.2 field_property_set 545
35.3.3 内置式字段属性:
尾声 546
35.3.4 field_property_get_
external 546
35.3.5 field_property_set_
external 547
35.3.6 Hack掉 547
35.4 方法属性 548
35.4.1 method_property_get 548
35.4.2 method_property_set 555
35.4.3 method_property_getset 555
35.4.4 谨防无限循环 557
35.4.5 method_property_get
_external 558
35.4.6 method_property_set
_external 561
35.4.7 method_property_getset
_external 562
35.5 静态属性 564
35.5.1 静态字段属性 564
35.5.2 内置式静态方法属性 564
35.5.3 外置式静态方法属性 566
35.6 虚属性 567
35.7 属性的使用 568
35.7.1 泛化性 568
35.7.2 错误诊断中的类型
替换 569
35.8 属性:尾声 570
附录A 编译器和库 572
A.1 编译器 572
A.2 库 573
A.2.1 Boost 574
A.2.2 STLSoft 574
A.2.3 其他库 574
A.3 其他资源 575
A.3.1 期刊 575
A.3.2 其他语言 575
A.3.3 新闻组 576
附录B '谦虚点,别骄傲' 577
B.1 操作符重载 577
B.2 后悔DRY 579
B.3 偏执式编程 579
B.4 精神错乱 580
附录C Arturius 582
附录D 随书光盘 583
尾声 584
参考书目 585
《Imperfect C++ 中文版》译序
千万不要被书名所误导!这是一本拥抱(而非诋毁)C++的著作。它有着独特的定位:为解决C++语言自身各种“不完美”而为现实世界中的程序员提供切合实际的解决方案。
世界上没有完美的编程语言,C++亦然。在本书中,Matthew Wilson不但为我们指出C++中诸多不完美之处,还提供了经过实践检验的应对技术和技巧,便于我们利用“不完美的C++”编写出近乎完美的代码——强建、高效、灵活、可移植、优雅的代码,而这些代码在声称为“完美的语言”中往往更难实现。
本书对给出的每一个“不完美”都进行了有细致的探讨:为什么说它是一个“不完美”?对其修复的指导思想是什么?有时候只是告诫你避免做些什么,给出一些约束和建议,更多的时候则为你提供现实的解决方案,这些方案往往离不开对现代模板编程技术的使用。
书中包含有许多你未曾听过或用过的技术,有些属于作者的创新,有些则是对现有技术的精化,二者均被提升到“范式”的高度。例如:应用程序二进制接口(ABI)、垫片(Shim)、饰面(Veneer)、螺栓(Bolt-in)、区间(range)、属性(property)等。不少主题难度大,此前为其他C++专家所忽略,探讨它们除了需要勇气外,第一手经验更是不可或缺。作为STLSoft库的主创者,Matthew在举例时,Windows API、MFC、ATL、COM以及UNIX等都是信手拈来。
除了丰富的实践、扎实的理论以及缜密的逻辑外,Matthew的文笔流畅,语言幽默,说理直接,字里行间流露出过人的自信,使得本书极具阅读趣味。
本书具有一定的阅读门槛,目标读者为中、高级职业C++程序员。书中展示的代码示例、编程技术往往在几款甚至十几款编译器上进行验证, 辅以表格对其各色特性加以比较,并针对不同编译器所表现出的差异性而给出高效、可移植的解决方案——就像很多现实世界中的C++程序员应该做(而没做到)的那样。如果你正在寻找一本真材实料的“C++实战”参考书,这一本不应该让你失望。
本书中文版由我和刘未鹏先生合译。未鹏是国内C++新锐,思维敏捷,技术、文笔俱佳,我很高兴与他合作。
感谢陈冀康编辑給予的理解和支持。感谢朱艳的照料和热爱。荣珅则常常用他的小拳头乱砸书房的门,并大声地叫“爸爸”,这种干扰让我获得了必不可少的休息时间。
既有的经典名著使得C++新书问世难度加大,后来者若无过人之处就很难引进C++社群的注意,《Imperfect C++》、《C++ Common Knowledge》以及《C++ Template Metaprogramming 》等佳作一经问世便得到广泛的关注。作为译者(之一),我祝愿它们能够带给各位久违的快乐!
荣耀
2005年8月
南京师范大学
www.royaloo.com
译序
刀有很多种,有单刀,双刀,朴刀,戒刀,锯齿刀,砍山刀,鬼头刀,
雁翎刀,五凤朝阳刀,鱼麟紫金刀。
—— 古龙《飞刀,又见飞刀》
这里我们要说的刀,是瑞士军刀,瑞士军刀其实严格来说并不能算是一种刀,其功能的繁杂和精细已然超过了刀的范畴。它包含的工具一般有主刀、小刀、剪刀、开瓶器、木锯、小改锥、拔木塞钻、牙签、小镊子等,而在一些工具上还设计了多种功用,如开瓶器上,就具有开瓶、平口改锥、电线剥皮槽三种功用。随着时代的发展,一些新兴的电子技术也被引入瑞士军刀中,如内藏激光、电筒等。
1879的瑞士军刀原形仅具有6种功能,而今天的一把瑞士军刀上可具有多达30余种功能(威戈的"15403-超能太空刀"和维诺斯的"16795-瑞士冠军")。多功能性成为瑞士军刀的又一大特色。在一把小型刀具上赋予了许多的实用小工具,解决了人们在日常工作及生活中的种种难题。而且不论在何种环境下,瑞士军刀都表现出它非凡的实用价值及相应的质量品质。瑞士军刀的组合工具不仅表现在品种上,同时还在于巧妙的组合在同一种工具上,使有限的工具发挥更多的实功能。
见过瑞士军刀的人大约都知道,瑞士军刀唯一可以算是缺点的地方是它的体积稍嫌臃肿,虽然瑞士军刀有若干种类,其中有些体积也相当精简,不过一把瑞士军刀总归还是要比普通的刀大许多,不过这其实严格来说并不能算是缺点,设想你把一把瑞士军刀中的各项功能全都用独立的工具来承担,可想而知需要消耗多大体积,带来多大的不便。
瑞士军刀是军人在野外生存的必备工具,其小体积浓缩众多实用功能的精心设计能够将一把刀的容限发挥到最大,丝毫不逊于《第一滴血》中蓝博带在身上的那把锐利的寒光闪闪的钢刀。
那么现在你拿在手里的这本书就是一把瑞士军刀!
这是一本非常特别的C++书,在市
Matthew Wilson是一名软件开发顾问,STLSoft库的创建者。他为双月刊C/C++Users Journal撰写关于将C/C++与其他语言和技术进行整合的专栏文章,同时亦是C++ Experts Forum在线专栏作家。Wilson有十余年C++开发经验。目前定居于澳大利亚。他拥有英国曼彻斯特大学博士学位。
或许我不像喜欢我的孩子们那样喜爱C++,甚至或许我对C++的喜爱都比不上我对骑自行车在坡度为32度、光滑度为10%的柏油路上爬坡的热衷[注1],尽管有时这些喜爱之情的确十分接近。不过,我确实感激于上天能够赐予我这样的人生,让我得以将生命中的部分时间用来实践或阐释Frederick P.Brooks的名言:“尽量发挥想象力进行创造”。我更要感激的是我能够跟这门如此强大、危险却又如此诱人的语言相伴。
[注1]你们中的自行车爱好者会明白我为什么这样说。
这些话听起来似乎蛮华丽动听,但你可能是因为看到本书的书名才买下它的,以为本书是一本抨击C++的书。你可能是Java或C或其它主流语言的热衷者,因而买这本书可能是想从中找到支持你远离C++的理由。倘若果真如此,你会失望的,因为本书非但未抨击C++,甚至可以算是C++的一场盛典。不过你也先别急着离开,因为你或许能够从中找到令你开始接触C++的理由。
你将从中学到什么
我写这本书的目的在于给予开发同行一些能量。它以虽批评但具建设性的眼光来看待C++及其不完美之处,并给出实际的措施以避免或改善这种不完美。我希望你在读完本书后能够对下面这些问题有一个更好的把握:
如何克服C++类型系统中的某些不足
模板编程在提高代码灵活性和健壮性上的强大能力
如何在(当前标准尚不予置理的)未定义行为的险恶丛林中生存下来。后者包括动态库、静态对象以及线程等
隐式转换的代价,它所带来的问题,及其替代方案,即基于显式转换的、高效且易于控制的泛用性编程
如何编写能够与(或更容易令它们与)其他编译器、库、线程模型等兼容的软件
编译器“在幕后”都做了些什么?你对编译器可以施加什么样的影响
数组和指针之间微妙而棘手的互操作性,以及用于防止它们的行为变得彼此相似的技术
C++对RAII(Resource Acquisition Is Initialization,资源获取即初始化)机制的支持,以及该机制可以被运用到的各种问题领域
如何通过最大限度地榨取编译器的侦错能力来节省你自己所需花费的工夫
有了上面这些“装备”,你编写的代码将更有效率,更具可维护性,更健壮,也更具灵活性。
我的意图是,即便是经验丰富的C++实践者也能从本书中发现新思想和新技术,从而引发他们的灵感并提高其现有的实战能力。经验较少的程序员则能领会其中包含的有关原则并将相关技术应用到自己的工作中,在增长见识的同时弥补自己对一些技术细节理解上的疏漏。
我并不指望你们所有人都完全同意我所说的一切,但我期望哪怕是最有争议的内容也能激发你去真正弄明白你对这门强大语言的使用问题。
我假设你们已经知道的
除非有人想写一本很厚的书,否则他必须假设许多知识是读者已经知道的。当然,规定大家必须首先读完某些书籍的做法过于粗鲁,但我假设你们的知识和经验能使你们轻松地理解Scott Meyer的《Effective C++》系列和Herb Sutter的《Exceptional C++》系列中讲述的绝大部分概念。我还假设你们拥有一本语言圣经,即Bjarne Stroustrup的《The C++ Programming Language》。 我并不指望你们仔细阅读了该书的每一页(我都还没有做到这一点),但你们应当将这本书作为这门语言的终极参考,因为它的确是字字珠玑。
本书中包含有相当多的模板代码(哪一本现代C++书籍不是如此呢?),但我并没有假设你是个大师级的人物[注2],或掌握了高阶元编程知识。话虽如此,我还是建议你们最好熟悉如何使用模板,比如组成C++标准库中最流行的部分的那些内容。我努力将对模板的使用控制在一个合理的水平,但我们必须得承认,正是对模板的支持使得C++具有“自我修复”的能力,而这也是本书得以诞生的主要原因。
[注2]“参考书目”中列出的若干书籍,可在你成为大师级人物的漫漫征途中助上一臂之力,如果你愿意付出努力的话。
由于灵活性和实用性对我而言非常重要,所以书中的代码并非只能在少数极好的编译器上编译,实际上,书中几乎所有代码都可以在任一款“还算可以”的现代编译器(见附录A )上编译。当然,有一些很好的编译器是可以免费获得的,并且你也可以相信你的编译器能够编译这些代码。
只要有可能,我就会尽量避免涉及特定的操作环境、库和技术。然而我也略微谈到了一些,所以了解以下内容将会有所帮助(尽管并非必不可少):COM和CORBA、动态库(UNIX和Win32的)、STL、线程(POSIX和Win32的)、UNIX以及Win32。参考书目中包含有许多这些方面以及其他方面内容的好书。此外,熟悉不止一种机器架构也是有帮助的,当然这同样并非不可或缺。
由于C目前仍是语言间交互以及操作系统开发的通用语言,所以它继续作为一门极其重要的语言存在着。尽管本书是关于C++的,但在某些领域,C跟C++之间的共性会变得很重要,我认为在这些领域选择兼顾C/C++是合理的。事实上,正如我们将在本书的第二部分中看到的,我们有时需要求助于C来支持C++的一些高级用法。
此外,我还作了一个重要的假定。我假定你们也认为工作的质量是非常重要的,并且有动力去寻找达到这一目标的新途径。本书不敢妄称是这些所谓新途径的唯一源泉。确切地说,它提供了一种实践性的、有时甚至是异端的视角来看待我们在C++中遇到的问题。如果情况够好的话,本书或许也能成为你的精华书库中的成员之一。最终的责任落在你自己的肩上,剩下来要做的就是去寻找最好的工具来支持你的工作。
本书的组织方式
本书的主要内容分为六个部分,每一部分由一个绪论和五到七章组成,每一章又可以被进一步划分为若干节。
既然本书取名为“Imperfect C++”,那么我就会在书中尽量突显出实际的不完美之处,这就是你们在本书的通篇都会发现一些所谓的“不完美(Imperfection)”的原因。在书的前些部分,这些不完美之处出现地比较密集,这说明它们自身和它们的解决之道尚且是比较简单的。每个小节都对应着语言的某个特定的特性,并且通常会介绍它的一个不完美之处。只要有可能,我就会提供一些具体的技巧或技术来解决这些问题,或至少为开发者提供控制问题的方法。随着本书内容逐步展开,这些不完美之处将会变得不再像前面那样散碎,显得更为重大,从而伴随有更大篇幅、更加详细的讨论。
本书并没有采用时髦的“自助餐”式的写法,也不存在一根必须从头读到尾的贯穿全书的主线。当然,大部分后面章节的内容是根据前面章节的内容进行描述的,有时甚至建立在之前的章节之上,所以除非你故意作对,否则最好还是按顺序阅读。然而,一旦你读完一遍后,回头再来参考其中的某些部分时,你就可以根据需要跳至任意一处而不再需要通读所有内容了。在每一章中,各节一般是按内容顺序排列的,所以我建议你也应该按顺序来阅读每一节。
在难度方面,第一部分到第四部分显然经历了一个从易到难的过程,从相当简单明了直到有相当高的要求[注3]。尽管第五部分和第六部分需要依赖第三部分和第四部分的一些内容,但它们相对来说不再那么具有挑战性了。你可以优哉游哉地一直读到附录。
[注3]第三部分和第四部分的某些内容直到现在还让我感到头疼呢!
主要内容介绍完之后是四个简短的附录。附录A介绍了在探讨本书各项议题时使用的编译器和库的详细信息。附录B向你展示了一个年轻的C++工程师在刚踏入这一充满陷阱的领域时所犯的一些令人目瞪口呆的错误。附录C介绍了Arturius 项目,这是一个免费的源码开放的编译器多路分发器,你也可以在配书光盘中找到它。最后,附录D介绍了配书光盘的内容。
我的编码风格十分一致,甚至可以说是严格,你也可以说它过于“学究气”,我以前的同事和使用我库的人们早就这样说过。但我之所以采取这种风格是因为这么一来代码中的所有东西都有其明确的位置,人家不至于会问“某某东西到哪里去了”这种问题,同时这也意味着当我几年后重新面对这些代码时,我还可以轻松搞定它。当然这么做也有缺点:我需要一个21英寸的显示器和一个工业用激光打印机。
为了尽量减少我的编码风格对阅读本书的影响,我在书中出现的代码示例中使用了一些自由风格。你将会在示例中看到很多省略号(...),它们一般表示前面的有关例子中已经包含了该段代码,或者表示这些省却的代码是我们司空见惯的样板式代码(例如禁止客户代码访问某些方法,见2.2节)。只有编码风格中对可靠性有着显著影响的方面才会被加以讨论(见第17章)[注4]。
[注4]如果你非得完整见识一下我的编码风格的话,你可以到配书光盘中的库里找到足够多的样例。
参考资料
我在阅读其他C++书籍时感到不满的一件事是:作者指出事实的同时从不提及标准的相关部分。所以我在写作本书的过程中,除了会不断引用一些相关的书籍和文章外,还会在描述C++语言行为的同时提供其在C++(C++98)或C(C99)标准中的相关参考。
补充材料
光盘
配书光盘中包含有一些库、编译器(包括书中描述的大量编码技术)、测试程序、工具,以及其他有用的软件,另外还有许多摘自各种出版物的相关文章。关于光盘的详细内容,请参考附录D。
网上资源
你也可以通过如下网址来获取补充资源:http://imperfectcplusplus.com。[注5]
[注5]这个站点上也有一个勘误表页
无封面