ZaynPei Lv6

RAII 计时器类

为了实现“自动统计终止时间”且不破坏业务逻辑的整洁性,我们可以封装一个计时器类。利用 C++ 对象的生命周期机制,将计时的开始点放在构造函数中,将计时的结束与统计点放在析构函数中。

原理:C++ 保证栈对象在离开其作用域(即遇到右大括号 })时,会自动调用析构函数。

利用匿名作用域:通过手动添加 { … } 代码块,我们可以精确控制计时器的生命周期,从而统计任意粒度(子过程 sub_stage)的耗时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <chrono>
#include <string>
#include <thread>

class ScopedTimer {
public:
// 构造函数:记录开始时间,并保存阶段名称
explicit ScopedTimer(const std::string& stage_name)
: name_(stage_name), start_(std::chrono::high_resolution_clock::now()) {
}

// 析构函数:自动在作用域结束时调用
~ScopedTimer() {
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start_).count();

// 打印统计结果(实际工程中可能是写入日志或发送给监控系统)
std::cout << "[Stage: " << name_ << "] took " << duration << " us." << std::endl;
}

private:
std::string name_;
std::chrono::time_point<std::chrono::high_resolution_clock> start_;
};

// 模拟的使用场景
void ProcessRequest() {
// 全局开始(可选)
ScopedTimer global_timer("Total Request");

{
// 子过程 1:数据去重
ScopedTimer t("Dedup");
// 模拟操作...
std::this_thread::sleep_for(std::chrono::milliseconds(10));
} // 遇到 '}',"Dedup" 计时器析构,自动打印时间

{
// 子过程 2:封装类/业务逻辑
ScopedTimer t("Business Logic");
// 模拟操作...
std::this_thread::sleep_for(std::chrono::milliseconds(20));
} // 遇到 '}',"Business Logic" 计时器析构,自动打印时间

} // 遇到 '}',"Total Request" 计时器析构

自动化与无侵入性:

不需要在每个 return 语句前都手动写一遍 end_time = now(); print();。

代码逻辑非常干净,计时逻辑与业务逻辑分离。

异常安全性(Exception Safety):

这是 RAII 最强大的地方。如果在子过程中抛出了异常(Exception),导致函数提前退出,栈展开(Stack Unwinding)机制依然会保证局部变量的析构函数被调用。

这意味着即使发生错误,我们依然能记录到该阶段截止目前的耗时,不会丢失监控数据。

灵活粒度控制:

正如题目中所述,利用 {} 可以随意圈定需要计时的范围,无需重构函数。

共享指针的共享计数常见实现方法有哪些?

主要有两种主流的实现策略:

外部控制块(Control Block / Auxiliary Block)—— 标准库 std::shared_ptr 的做法

原理:当智能指针初始化时,在堆上额外申请一块内存(控制块)。这个控制块内部包含:引用计数(ref_count)、弱引用计数(weak_count)、自定义删除器(deleter)等。

优点:非侵入式。任何类型的对象(包括基础类型 int)都可以被管理,不需要修改对象本身的定义。

侵入式计数(Intrusive Counting)—— 如 boost::intrusive_ptr

原理:将引用计数直接作为成员变量嵌入到被管理的对象内部。对象必须继承自特定的基类(包含计数器)。

优点:性能更高。少了一次堆内存分配(不需要控制块),且缓存局部性(Cache Locality)更好。这对高频交易(HFT)等高性能场景很有吸引力。

On this page