目录
一、避免使用 handles 有其代价(Effective C++ 53)
二、让自己熟悉包括 TR1 在内的标准程序库(Effective C++ 54)
三、让自己熟悉 Boost(Effective C++ 55)
一、避免使用 handles 有其代价(Effective C++ 53)
要知道把对象封装在 Handle(句柄)中是有代价的。
什么是“Handle”(句柄)?
在 C++ 中 Handle 类指:
一个类内部只包含一个指针,
真正的数据存在堆里。
例子:
class String {
private:
char* data; // handle → 指向真正的对象
};
data 不是“字符串本体”。
data 只是一个“指针”(handle),
指向真正存放字符串的“内存区域”(真正的对象)。
String 对象本身:
┌──────────┐
│ data ---> ───────────────┐
└──────────┘ │
▼
┌─────────────┐
│ 'H''i''\0' │ ← 字符串真正储存在这里
└─────────────┘
也叫:
-
指针语义(pointer semantics)
-
pImpl(pointer to implementation)
-
智能指针(也算一种 Handle)
-
句柄类(handle class)
换句话说:
Handle = 外面看起来像对象,内部其实是指针。
为什么很多人喜欢用 Handle?
因为它有几个优点:
1)减少编译依赖(Pimpl)
.h 里只有指针,不包含真正实现 → 改动不会增量编译。
2)可以共享底层对象(引用计数)
多个 handle 指向同一个底层对象。
3)可以做到 copy-on-write
String 类曾经用过 COW。
4)可以隐藏实现细节(封装性强)
那为什么说“有代价”?(重要)
因为:把数据放在句柄里,而不是对象自身,就带来很多性能和语义成本。
代价 1:无法使用“值语义”,行为更像指针
我们写:
Widget a;
Widget b = a;
如果 Widget 是普通类,a 和 b 各有自己的数据。
但如果 Widget 是 Handle:
class Widget {
Impl* p;
};
那么:
b.p = a.p; // 两者共享同一个底层对象
看起来像拷贝,但“语义变了”,这是风险。
代价 2:多一次间接访问(慢)
访问字段需要:
w.p->value // 比 w.value 多一次指针跳转
指针跳转会影响:
-
cache miss
-
指令预测
-
优化能力
普通对象:
value // 直接访问
速度更快。
代价 3:内存布局不连续
特别重要!
例子:
std::vector<Widget> v;
如果 Widget 是普通类:
-
v 的内存是连续的
-
CPU 预取很容易
-
遍历速度极快
但如果 Widget 是 Handle:
-
vector 里其实是很多个指针
-
指向的 Impl 在堆里乱七八糟,完全不连续
-
遍历时 cache miss 爆炸 → 性能恶劣
代价 4:必须管理深拷贝 / 引用计数 / 复制控制
必须自己写:
-
拷贝构造
-
赋值运算符
-
析构函数
要么深拷贝:
p = new Impl(*other.p);
要么引用计数:
++p->refcount;
要么使用共享机制:
-
复杂
-
容易出 bug
普通对象不需要这些。
代价 5:更多内存管理风险
因为必须手写:
-
new
-
delete
-
copy
-
move
一旦漏写 → 崩溃 / double free / 内存泄漏
小结
Handle 有好处,但代价是:
性能变差、复杂度上升、语义变模糊、容易出错。
“避免使用 handles 有其代价” 的意思是:
你选择不用“值语义”而使用“指针语义”,
你会付出性能、语义、正确性方面的成本。
实例:值语义 vs Handle 语义
普通(值语义)
class Point {
int x, y;
};
复制:
Point b = a; // 快,语义清晰
Handle(指针语义)
class Point {
struct Impl { int x, y; };
Impl* p;
};
复制:
b.p = a.p; // 看似拷贝,实际共享
修改 b 会影响 a
完全不像“值拷贝”。
总结
Handle(句柄类)虽然减少编译依赖、便于封装,但代价是性能损失、结构复杂、语义变脆弱。
Effective C++ 提醒你:使用 Handle 是 trade-off,不是免费午餐。
二、让自己熟悉包括 TR1 在内的标准程序库(Effective C++ 54)
不要自己重复造轮子,标准库能做的事,就不要自己做。
熟悉标准库(包括 TR1 → 现代 C++ 标准库)能让你更高效、更安全、更少 bug。
TR1 是什么?
TR1(Technical Report 1)是 C++98 时代,标准委员会发布的一组“即将进入标准库的扩展组件”。
后来这些内容全部进入了 C++11,所以:
👉 TR1 = C++11 标准库的预览版
-
std::shared_ptr -
std::function -
std::bind -
std::unordered_map -
std::tuple -
std::regex
这些全部来自 TR1。
所以 Meyers 的意思是:
必须熟悉 C++11 标准库(因为它就是 TR1)。
R1 / C++11 标准库有哪些重要东西?(你要会用)
1)智能指针(shared_ptr / weak_ptr / unique_ptr)
来自 TR1,如今是 C++11 标准库核心。
用途:
-
做资源管理(RAII)
-
避免内存泄漏
-
避免双重 delete
-
安全的对象共享
std::shared_ptr<Widget> p(new Widget);
2)函数包装器(std::function / std::bind)
替代函数指针,能存任何可调用对象。
std::function<int(int)> f = [](int x){ return x*2; };
很多人分不清函数包装器和句柄:
普通函数指针: 只能指向 ↓
fp ───────→ [普通函数]
函数包装器: 能指向 ↓
std::function ─→ [普通函数]
[lambda + 捕获数据]
[std::bind 绑定后的对象]
[成员函数 + 对象实例]
[仿函数]
[任意可调用对象]
3)哈希表(unordered_map / unordered_set)
比 map/set 更快(哈希结构,不是红黑树)
std::unordered_map<std::string, int> m;
4)元组(std::tuple)
传递多个不同类型的数据的轻量容器。
auto t = std::make_tuple(1, "hi", 3.14);
5)正则表达式(std::regex)
标准库内置的正则。
6)随机数工具()
替代 rand(),质量更高。
7)类型萃取(type traits)
模板元编程的基础工具:
-
std::is_pointer<T> -
std::is_integral<T> -
std::remove_reference<T>
Effective C++ 在后面大量使用这些。
为什么要熟悉 TR1(C++11 标准库)?
因为:
✔ 它比自己写的更快
算法经过几十年优化。
✔ 它比自己写的更安全
例如 shared_ptr 自动释放资源,不泄漏。
✔ 它比自己写的更可移植
所有平台都支持。
✔ 它是现代 C++ 的基础
例如:
-
lambda
-
async
-
future
-
chrono
-
memory
-
algorithm
全部和这些组件紧密联系。
现代 C++ 的力量来自标准库,而不是语言本身。
会用标准库 = 会用 C++。
Effective C++ 不是让你背 API,而是:只要标准库能做,就不要自己写。
图总结
C++11 标准库 = TR1 + 更多东西
TR1 = 智能指针 + 哈希容器 + bind/function + tuple + regex + random + type_traits
Meyers 强调:
必须熟悉这些,否则会一直手写低效版的轮子。
能得到什么能力?
-
少写 50% 代码
-
少 80% bug
-
性能更高
-
和现代框架(Qt, Boost, LLVM)更兼容
-
用标准方案而不是自家奇怪代码
保证在项目里( PTZ、WebRTC、模块管理、NetDetect 等)用到大量:
-
shared_ptr
-
unordered_map
-
bind/function
-
type traits
-
tuple
-
chrono
-
future/thread
-
etc.
三、让自己熟悉 Boost(Effective C++ 55)
Boost 是什么?(一句话)
Boost = C++ 世界最强的“第三方标准库”,
里面的东西后来大部分都进了 C++11/C++14/C++17 标准。
现代 C++ 想写好,Boost 必须熟悉。
Boost 为什么这么牛?
因为 C++ 标准库里好多东西都是从 Boost 抽进去的:
| Boost 组件 | 进入 C++ 标准库 |
|---|---|
| shared_ptr | C++11 std::shared_ptr |
| weak_ptr | C++11 std::weak_ptr |
| function | C++11 std::function |
| bind | C++11 std::bind |
| thread | C++11 std::thread |
| regex | C++11 std::regex |
| chrono | C++11 std::chrono |
| unordered_map | C++11 unordered_map |
| tuple | C++11 std::tuple |
| random | C++11 |
| type_traits | C++11 type_traits |
现在用的很多现代 C++ 技术,都是 Boost 发明的。
Boost 适合用来干什么?
Boost 是一个巨大的库体系,涵盖:
1. 高质量智能指针
-
Boost shared_ptr(标准库原始实现)
-
Boost intrusive_ptr
2. 容器和数据结构
-
Boost circular_buffer(环形缓冲区)
-
Boost multi_array(多维数组)
-
Boost flat_map(比 map 更快)
3. 字符串和正则
-
Boost string_algo
-
Boost regex(标准库 regex 的前身)
4. 文件系统
-
Boost filesystem(完全搬进 C++17 的 std::filesystem)
5. 网络编程(广泛使用)
-
Boost Asio(现代 C++ 网络编程标准)
-
TCP, UDP, SSL
-
定时器
-
协程 I/O
-
碎碎念:我的项目(网络设备、PTZ、WebRTC、网关)非常适合用 Asio。
6. 日期与时间
-
Boost date_time(高精度时间)
-
Boost chrono(后进入 std::chrono)
7. 序列化(Serialize)
-
Boost Serialization(可以用来序列化对象)
8. 多线程和并发
-
Boost thread(进入 std::thread)
-
Boost atomic(进入 std::atomic)
9. 数学与科学计算
-
Boost ublas(线性代数)
-
Boost multiprecision(大数)
-
Boost geometry(空间几何)
10. 模板元编程(TMP)神器
-
Boost MPL
-
Boost Hana(C++14 TMP 终极神器)
-
Boost TypeIndex / TypeTraits
Boost 最大的价值是什么?
(1)质量极高
Boost 的每个库都需要:
-
设计审查
-
标准委员会审核
-
多平台测试
-
严格代码质量
比大多数开源库靠谱得多。
(2)现代 C++ 的试验田
Boost 的许多功能会先在 Boost 成熟,然后进入标准库。
想学未来 C++ 的方向,看 Boost 就够了
(3)无依赖,纯头文件库
很多 Boost 库是 header-only(不需要编译、安装)。
现代 C++ 的许多高级技巧和能力标准库还没有,Boost 里都有。
若你不用 Boost,就等于错过了 C++ 的一半力量。
碎碎念:我的场景(嵌入式 + 网络 + C++ 工程)可以用以下库
(PTZ 控制、WebRTC、任务执行池、线程池、HTTP、设备管理、NetDetect)
✔ Boost Asio(网络编程)
比 libhv 更底层、更强。
✔ Boost optional(可选值)
C++17 的 std::optional 之前都靠它。
✔ Boost filesystem(文件系统)
跨平台,方便读写日志、配置。
✔ Boost circular_buffer(环形缓冲区)
用于视频流、日志 buffer。
✔ Boost type_traits / MPL / Hana
学模板 → boost MPL 是最好的学习参考。
✔ Boost lockfree
高性能无锁队列,多线程项目会用到。
“Boost 入门实践建议表”
| 级别 | Boost 模块 | 为什么 |
|---|---|---|
| 入门 | optional, regex, filesystem | 最接近标准库,很好上手 |
| 进阶 | asio, lockfree, circular_buffer | 高性能网络和并发 |
| 高级 | MPL, Hana, TypeIndex | 模板元编程神器 |
| 实战 | serialization, ublas | 工程项目直接能用 |
总结
Boost = C++ 标准库的未来 + C++ 世界最大的“武器库”。
熟悉 Boost = 熟悉现代 C++。它太重要了。
5761

被折叠的 条评论
为什么被折叠?



