2. C++基础回顾:指针与引用、内存管理(RAII)、STL容器(vector, map, unordered_map)在高频场景下的选择
各位同学,欢迎来到第二章。这一章我们不讲花哨的算法,而是把C++最基础、但也最容易翻车的几个点再过一遍。为什么?因为在高频行情解析里,每一微秒都算数。你写的每一行代码,都可能成为整个系统的瓶颈。
我个人习惯是,先把地基打牢,再谈上层建筑。所以这一章,我们重点聊聊指针与引用、RAII内存管理,以及那几个高频场景下最常用的STL容器。嗯,咱们直接开始。
2.1 指针与引用:别再用错了
先问个问题:你平时写代码,用指针多还是引用多?
我见过不少新手,甚至有些工作两三年的工程师,对这两者的区别还是模模糊糊。其实说白了,引用就是指针的语法糖,但它更安全、更直观。
核心区别就三点:
- 引用必须初始化,不能为空。 这意味着你拿到一个引用,就可以放心用,不用像指针那样先判空。
- 引用不能改变指向。 一旦绑定,就不能再指向别的对象。指针可以随意改。
- 引用没有地址。 对引用取地址,得到的是原对象的地址。指针本身有地址。
在高频场景下,我建议你:
- 能用引用,就不用指针。 减少一次判空,就少一次分支预测失败的风险。
- 必须用指针时,优先考虑智能指针。 裸指针容易造成内存泄漏或悬空指针。
避坑指南: 我曾经在一个行情网关项目里,因为一个裸指针没有及时置空,导致在行情数据回放时访问了已释放的内存,整个系统直接崩溃。从那以后,我所有的新代码都强制使用智能指针。
2.2 RAII:C++最优雅的内存管理
RAII,全称是 Resource Acquisition Is Initialization。名字挺唬人,但说白了就是:资源在构造函数中获取,在析构函数中释放。
你想想看,在高频交易系统里,我们经常要处理大量的行情数据包。每个数据包都需要分配内存、解析、然后释放。如果手动管理,很容易出现忘记释放或者重复释放的问题。
RAII 的好处就是:自动管理生命周期,异常安全。 哪怕函数中间抛出异常,栈上的对象也会自动调用析构函数,资源不会泄漏。
举个例子:
// 手动管理(不推荐)
void processPacket(char* rawData) {
MarketData* md = new MarketData();
// ... 解析逻辑 ...
if (someError) {
delete md; // 容易忘记
return;
}
delete md; // 如果上面return了,这里就泄漏了
}
// RAII管理(推荐)
void processPacket(const std::vector<char>& rawData) {
std::unique_ptr<MarketData> md = std::make_unique<MarketData>();
// ... 解析逻辑 ...
if (someError) {
return; // 自动释放,不用担心
}
// 函数结束,自动释放
}
我的经验: 在行情解析模块里,我几乎只用 std::unique_ptr 和 std::shared_ptr。前者用于独占资源,后者用于共享资源。记住一点:能用 unique_ptr 就别用 shared_ptr,因为 shared_ptr 的引用计数有原子操作开销,在高频场景下不可忽视。
2.3 STL容器在高频场景下的选择
STL 容器是 C++ 的利器,但用不好就是绊脚石。在高频行情解析中,我们最常用的是 vector、map 和 unordered_map。下面我逐个分析。
2.3.1 vector:连续内存的王者
vector 是我用得最多的容器,没有之一。为什么?因为行情数据通常是连续的字节流,vector 的连续内存布局让 CPU 缓存命中率极高。
适用场景:
- 存储行情数据包(如逐笔成交、快照)
- 作为缓冲区,接收网络数据
- 需要随机访问的场景
注意事项:
- 预分配容量。 如果你知道大概会有多少数据,提前
reserve(),避免频繁扩容导致的内存拷贝。 - 避免在头部插入。
vector在头部插入是 O(n) 的,高频场景下绝对不要这么干。 - 考虑使用
std::array。 如果大小固定,std::array比vector更轻量。
避坑指南: 我曾经在解析深度行情快照时,没有预分配 vector 容量,结果每次行情更新都触发一次扩容,导致延迟从 5 微秒飙升到 50 微秒。后来加上 reserve(10000),延迟直接降回 6 微秒。嗯,预分配真的很重要。
2.3.2 map vs unordered_map:有序 vs 无序
这两个容器经常被拿来比较。简单说:
map:基于红黑树,有序,插入/查找 O(log n)unordered_map:基于哈希表,无序,插入/查找 O(1) 平均
在高频场景下,我个人的选择原则是:
- 需要有序遍历? 用
map。比如按时间戳排序的订单簿。 - 只需要快速查找? 用
unordered_map。比如根据股票代码查找行情数据。 - 数据量很小(比如几十个)? 其实
vector加线性查找可能更快,因为哈希计算的开销可能比线性查找还大。
来看个对比表格:
| 特性 | map | unordered_map |
|---|---|---|
| 底层结构 | 红黑树 | 哈希表 |
| 查找复杂度 | O(log n) | O(1) 平均 |
| 内存占用 | 较低 | 较高(需要桶和负载因子) |
| 迭代顺序 | 按键排序 | 无序 |
| 适用场景 | 需要有序、数据量中等 | 需要快速查找、数据量大 |
注意: unordered_map 的哈希冲突在高负载因子下会退化,最坏情况可能变成 O(n)。我建议把负载因子控制在 0.7 以下,或者使用 reserve() 预分配足够的桶。
2.4 本章知识体系图
下面这张图总结了本章的核心知识点和它们在高频场景下的关系:
2.5 总结
这一章我们回顾了三个核心基础点:
- 指针与引用:引用更安全,指针更灵活。高频场景下优先用引用。
- RAII:C++最优雅的内存管理方式。用智能指针代替裸指针,省心又安全。
- STL容器选择:vector 是高频场景的首选;需要有序用 map,需要速度用 unordered_map。
记住,基础不牢,地动山摇。这些看似简单的知识点,在高频行情解析中会反复出现。下一章我们就要开始实战了,到时候你会发现,今天讲的每一个点都会用到。