好的,各位朋友,欢迎来到今天的“C++ std::chrono
高级:自定义时钟与时间点操作”特别节目!我是你们的老朋友,BUG 终结者。今天我们要聊聊 C++11 引入的 std::chrono
库的深水区,尤其是如何玩转自定义时钟和时间点。
std::chrono
的基本回顾:时间,时长,时钟
在深入之前,我们先简单回顾一下 std::chrono
的三大基石:
- Duration(时长): 代表时间段,比如 5 秒,10 分钟。
- Time Point(时间点): 代表时间轴上的一个特定时刻,比如 2023 年 10 月 27 日 10:00:00。
- Clock(时钟): 提供当前时间点,以及时间单位的信息。
这三者之间的关系,就像是:
概念 | 类比 | 作用 |
---|---|---|
时长 | 一段旅程 | 表示时间的长度,比如“开车开了 3 个小时” |
时间点 | 地标 | 时间轴上的一个具体位置,比如“到达了长城” |
时钟 | 指南针/GPS | 提供当前位置(时间点),以及测量距离(时长)的工具,比如“现在几点”、“走了多久” |
为什么要自定义时钟?
标准库已经提供了 system_clock
, steady_clock
, high_resolution_clock
等时钟。那为什么我们还需要自定义时钟呢?原因有很多:
- 模拟时间: 在单元测试中,我们可能需要控制时间流逝,以便测试时间相关的逻辑。
- 硬件时钟: 某些嵌入式系统或专用硬件可能提供自己的时钟源,需要通过自定义时钟来访问。
- 逻辑时钟: 在分布式系统中,可能需要使用逻辑时钟来保证事件的顺序,而不是依赖物理时间。
- 性能优化: 标准时钟可能不满足某些特定场景下的性能需求,自定义时钟可以进行优化。
自定义时钟的骨架:
要自定义一个时钟,我们需要定义一个类,并满足 std::chrono
的时钟概念 (Clock concept) 的要求。 简单来说,需要满足以下条件:
-
typedef
成员:rep
: 表示时钟 tick 的数值类型,通常是int
,long long
等。period
:std::ratio
类型,表示时钟 tick 的周期。例如,std::ratio<1, 1000>
表示 1 毫秒。duration
:std::chrono::duration<rep, period>
,表示时钟 tick 的时长。time_point
:std::chrono::time_point<clock_type>
,表示时钟的时间点。
-
静态成员函数
now()
:- 返回当前时间点 (
time_point
)。
- 返回当前时间点 (
-
(可选) 静态常量
is_steady
:true
表示时钟是稳定的 (steady),即时间不会倒流。false
表示时钟可能不稳定 (比如system_clock
)。
一个简单的自定义时钟示例:
让我们先从一个最简单的自定义时钟开始,它仅仅是一个墙上时钟,以秒为单位:
#include <iostream>
#include <chrono>
class WallClock {
public:
using rep = long long;
using period = std::ratio<1>; // 秒
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<WallClock>;
static constexpr bool is_steady = false; // 墙上时钟可能被手动调整
static time_point now() noexcept {
auto now = std::chrono::system_clock::now().time_since_epoch();
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now).count();
return time_point(duration(seconds));
}
};
int main() {
WallClock::time_point now = WallClock::now();
std::cout << "WallClock time: " << now.time_since_epoch().count() << " seconds" << std::endl;
return 0;
}
在这个例子中:
- 我们定义了一个名为
WallClock
的类。 rep
是long long
,period
是std::ratio<1>
(表示秒)。now()
函数返回当前时间点,它从system_clock
获取当前时间,然后转换为秒。is_steady
被设置为false
,因为墙上时钟可能会被手动调整。
模拟时钟:单元测试的利器
接下来,我们创建一个模拟时钟,这在单元测试中非常有用,因为我们可以控制时间流逝:
#include <iostream>
#include <chrono>
class MockClock {
public:
using rep = long long;
using period = std::ratio<1>; // 秒
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<MockClock>;
static constexpr bool is_steady = true; // 模拟时钟应该是稳定的
static void set_now(time_point tp) {
current_time = tp;
}
static time_point now() noexcept {
return current_time;
}
private:
static time_point current_time;
};
MockClock::time_point MockClock::current_time = MockClock::time_point(MockClock::duration(0)); // 初始化为 0
int main() {
MockClock::time_point start = MockClock::now();
std::cout << "MockClock start time: " << start.time_since_epoch().count() << " seconds" << std::endl;
MockClock::set_now(MockClock::time_point(MockClock::duration(10))); // 设置时间为 10 秒
MockClock::time_point now = MockClock::now();
std::cout << "MockClock current time: " << now.time_since_epoch().count() << " seconds" << std::endl;
return 0;
}
在这个例子中:
- 我们添加了一个
set_now()
函数,允许我们设置模拟时钟的当前时间。 current_time
是一个静态成员变量,用于存储当前时间。is_steady
被设置为true
,因为模拟时钟应该是稳定的。
高精度时钟:追求极致
如果你需要比标准时钟更高的精度,你可以尝试使用 std::chrono::high_resolution_clock
。但是,如果 high_resolution_clock
的精度仍然不够,你可以尝试使用硬件提供的时钟源,并将其封装成一个自定义时钟。
时间点操作:加减,比较,转换
有了时钟,我们就可以获取时间点了。接下来,我们需要对时间点进行各种操作,比如加减,比较,转换等。
- 加减: 可以使用
+
和-
运算符来对时间点进行加减操作。 - 比较: 可以使用
==
,!=
,<
,>
,<=
,>=
运算符来比较时间点。 - 转换: 可以使用
std::chrono::time_point_cast
来将时间点转换为不同的精度。
#include <iostream>
#include <chrono>
int main() {
auto now = std::chrono::system_clock::now();
// 加法
auto later = now + std::chrono::seconds(10);
std::cout << "10 seconds later: " << std::chrono::system_clock::to_time_t(later) << std::endl;
// 减法
auto earlier = now - std::chrono::minutes(5);
std::cout << "5 minutes earlier: " << std::chrono::system_clock::to_time_t(earlier) << std::endl;
// 比较
if (later > now) {
std::cout << "Later is indeed later than now." << std::endl;
}
// 转换精度
auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>(now);
std::cout << "Current time in milliseconds: " << now_ms.time_since_epoch().count() << std::endl;
return 0;
}
时间点与 time_t
的转换
std::chrono::system_clock
的时间点可以与 time_t
类型进行转换,time_t
是 C 语言中表示时间的类型。
std::chrono::system_clock::to_time_t(time_point)
: 将system_clock
的时间点转换为time_t
。std::chrono::system_clock::from_time_t(time_t)
: 将time_t
转换为system_clock
的时间点。
#include <iostream>
#include <chrono>
#include <ctime>
int main() {
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
std::cout << "Current time as time_t: " << now_c << std::endl;
std::cout << "Current time (ctime): " << std::ctime(&now_c) << std::endl; // 使用 ctime 转换为可读字符串
auto tp = std::chrono::system_clock::from_time_t(now_c);
std::cout << "Time point from time_t: " << std::chrono::system_clock::to_time_t(tp) << std::endl;
return 0;
}
高级技巧:使用 std::ratio
定义非标准时间单位
std::ratio
允许我们定义任意的时间单位,而不仅仅是秒,毫秒,微秒等。例如,我们可以定义一个“赫兹”作为时间单位:
#include <iostream>
#include <chrono>
int main() {
using Hertz = std::ratio<1, 1>; // 1 赫兹 = 1 秒
using KiloHertz = std::ratio<1000, 1>; // 1 千赫兹 = 1000 秒
using duration_khz = std::chrono::duration<long long, KiloHertz>;
duration_khz d(5); // 5 千赫兹
std::cout << "5 KHz is " << d.count() << " ticks" << std::endl;
std::cout << "5 KHz in seconds is " << std::chrono::duration_cast<std::chrono::seconds>(d).count() << " seconds" << std::endl;
return 0;
}
自定义时钟的实际应用场景
- 游戏开发: 可以使用自定义时钟来控制游戏中的时间流逝,例如,可以创建一个“游戏时钟”,让游戏中的时间比现实时间快或慢。
- 嵌入式系统: 可以使用自定义时钟来访问硬件提供的时钟源,例如,可以创建一个“硬件时钟”,直接读取硬件寄存器中的时间值。
- 分布式系统: 可以使用自定义时钟来实现逻辑时钟,例如,可以使用 Lamport 时钟或向量时钟来保证事件的顺序。
- 性能分析: 可以使用自定义时钟来测量代码的执行时间,例如,可以创建一个“性能时钟”,只测量 CPU 时间或 GPU 时间。
线程安全问题
当多个线程访问同一个自定义时钟时,需要考虑线程安全问题。如果时钟的状态是可变的(例如,模拟时钟),则需要使用互斥锁或其他同步机制来保护时钟的状态。
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
class ThreadSafeClock {
public:
using rep = long long;
using period = std::ratio<1>;
using duration = std::chrono::duration<rep, period>;
using time_point = std::chrono::time_point<ThreadSafeClock>;
static constexpr bool is_steady = true;
static void set_now(time_point tp) {
std::lock_guard<std::mutex> lock(mutex_);
current_time_ = tp;
}
static time_point now() noexcept {
std::lock_guard<std::mutex> lock(mutex_);
return current_time_;
}
private:
static time_point current_time_;
static std::mutex mutex_;
};
ThreadSafeClock::time_point ThreadSafeClock::current_time_ = ThreadSafeClock::time_point(ThreadSafeClock::duration(0));
std::mutex ThreadSafeClock::mutex_;
int main() {
std::thread t1([]() {
for (int i = 0; i < 10; ++i) {
ThreadSafeClock::set_now(ThreadSafeClock::time_point(ThreadSafeClock::duration(i)));
std::cout << "Thread 1: " << ThreadSafeClock::now().time_since_epoch().count() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
std::thread t2([]() {
for (int i = 10; i < 20; ++i) {
ThreadSafeClock::set_now(ThreadSafeClock::time_point(ThreadSafeClock::duration(i)));
std::cout << "Thread 2: " << ThreadSafeClock::now().time_since_epoch().count() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
});
t1.join();
t2.join();
return 0;
}
在这个例子中,我们使用 std::mutex
来保护 current_time_
的访问,确保线程安全。
总结
今天我们一起探索了 C++ std::chrono
库中自定义时钟和时间点操作的高级用法。我们学习了如何创建自定义时钟,包括简单的墙上时钟、模拟时钟和线程安全时钟。我们还学习了如何对时间点进行加减,比较,转换等操作。希望这些知识能帮助你在实际项目中更好地使用 std::chrono
库。
记住,时间就是金钱,哦不,时间就是代码! 掌握时间处理,你就能更好地掌控程序的命运。
感谢大家的收看,我们下期再见! 别忘了点赞,关注,转发! (虽然这里不能点赞…)