C++高精度时间库——<chrono>

在前面学习C语言我们就已经学习了C语言的时间库<time.h>。它操作简单、是全平台行为一致的时间库。但是它却存在着很多的问题------类型不安全,精度较低(通常为秒),扩展只能依赖编译器特制的宏。

而现代项目中如果要进行性能测试、时间间隔计算的话,C语言的<time.h>显然是不够的。因此在C++11中,引入了<chrono>这个时间库。接下来我们就来一次学习一下。

相关的英文文档为:Standard library header <chrono> (C++11) - cppreference.com

相关的测试代码已经上传至作者的个人gitee:楼田莉子/CPP代码学习

目录

设计哲学

库中的单位

[时长单位(Duration Units)](#时长单位(Duration Units))

[时钟单位(Clock Types)](#时钟单位(Clock Types))

[日历基础单位(Calendar Fundamentals)](#日历基础单位(Calendar Fundamentals))

[日历复合单位(Calendar Composites)](#日历复合单位(Calendar Composites))

[时刻单位(Time of Day)](#时刻单位(Time of Day))

[时区单位(Time Zone)](#时区单位(Time Zone))

字面量单位(Literals)

模板类

std::chrono::duration

std::chrono::treat_as_floating_point

std::chrono::duration_values

时间点

time_point

clock_time_conversion

时钟

[C++11 的基础三时钟](#C++11 的基础三时钟)

[C++20 新增的专业时钟](#C++20 新增的专业时钟)

[辅助模板:is_clock 与 is_clock_v](#辅助模板:is_clock 与 is_clock_v)

日历

日历基础类型

日历复合类型

关键运算符与函数

代码示例:

函数

时长(Duration)操作函数

[时间点(Time Point)操作函数](#时间点(Time Point)操作函数)

日历(Calendar)操作函数

输入/输出(I/O)函数

字面量(Literals)运算符

测试

日期计算

性能测试


<chrono>设计哲学

在C++11之前,我们处理时间基本靠 time_tclock()struct timespec。这种方式的问题在于:类型不安全 。函数参数传递 time_t,你无法直观知道单位是秒还是毫秒;做时间运算时,极易因单位混淆导致bug

<chrono> 库最伟大的贡献,就是引入了 编译期类型安全精度无关 的时间处理模型。它将时间抽象为三个核心概念:

  1. 时间段 (Duration) :由 数值单位 组成的强类型。例如 std::chrono::seconds(10) 就是10秒,std::chrono::milliseconds(1000) 是1000毫秒,它们在类型上就不同,不能直接隐式转换,避免了单位混淆。

  2. 时间点 (Time Point) :某个特定时钟上的一个点,由 时钟 (Clock)时间段 (Duration) 共同定义。它本质上是从某个固定的纪元 (Epoch) 开始到现在的时间段。例如 std::chrono::system_clock::now() 返回的就是一个时间点。

  3. 时钟 (Clock) :提供了获取当前时间点的入口 (now()),并定义了时间点的精度和纪元。标准提供了三种时钟(如文档所列),这是新手最容易踩坑的地方,下面我会重点讲。

库中的单位

时长单位(Duration Units)

这些是表示时间间隔的类型,基于duration模板定义。

单位名称 引入标准 说明
std::chrono::nanoseconds C++11 纳秒,周期为std::nano
std::chrono::microseconds C++11 微秒,周期为std::micro
std::chrono::milliseconds C++11 毫秒,周期为std::milli
std::chrono::seconds C++11 秒,周期为std::ratio<1>
std::chrono::minutes C++11 分钟,周期为std::ratio<60>
std::chrono::hours C++11 小时,周期为std::ratio<3600>
std::chrono::days C++20 天,周期为std::ratio<86400>
std::chrono::weeks C++20 周,周期为std::ratio<604800>
std::chrono::months C++20 月(平均格里高利月),周期为std::ratio<2629746>
std::chrono::years C++20 年(平均格里高利年),周期为std::ratio<31556952>

时钟单位(Clock Types)

时钟定义了时间的原点(纪元)和刻度,用于产生时间点。

时钟名称 引入标准 说明
std::chrono::system_clock C++11 系统范围实时时钟(挂钟时间),通常基于Unix时间戳
std::chrono::steady_clock C++11 单调时钟,不受系统时间调整影响,适合测量间隔
std::chrono::high_resolution_clock C++11 当前设置下最高精度的时钟,通常是上述之一的别名
std::chrono::utc_clock C++20 协调世界时(UTC),处理跳秒
std::chrono::tai_clock C++20 国际原子时(TAI),与UTC有固定偏移且无跳秒
std::chrono::gps_clock C++20 全球定位系统时(GPS),始于1980-01-06
std::chrono::file_clock C++20 文件系统使用的时钟别名,用于获取文件时间戳
std::chrono::local_t C++20 伪时钟,代表本地时间,不关联具体时区

日历基础单位(Calendar Fundamentals)

C++20引入,表示格里高利历中的基本日期元素。

类型名称 引入标准 说明
std::chrono::day C++20 表示月份中的某一天(1-31)
std::chrono::month C++20 表示年份中的某个月(1-12)
std::chrono::year C++20 表示格里高利历中的年份(如2024)
std::chrono::weekday C++20 表示星期几(0代表周日,1周一,...6周六)
std::chrono::last_spec C++20 标签类,用于表示月份的最后一天或最后一个工作日

日历复合单位(Calendar Composites)

由基础类型组合而成,用于表示完整的日期或特定规则。

类型名称 引入标准 说明
std::chrono::year_month C++20 特定的年+月组合(如2024y/May)
std::chrono::month_day C++20 特定的月+日组合(如May/21d),忽略年份
std::chrono::year_month_day C++20 完整的日期(如2024-05-21)
std::chrono::year_month_day_last C++20 某月的最后一天(如2024年5月的最后一天)
std::chrono::month_day_last C++20 某月的最后一天,忽略年份
std::chrono::year_month_weekday C++20 某年第N个月的星期W(如2024年5月的第3个星期二)
std::chrono::year_month_weekday_last C++20 某个月的最后一个星期W
std::chrono::month_weekday C++20 某个月的第N个星期W,忽略年份
std::chrono::month_weekday_last C++20 某个月的最后一个星期W,忽略年份
std::chrono::weekday_indexed C++20 表示某个月的第几个星期几(如第3个星期二)
std::chrono::weekday_last C++20 表示某个月的最后一个星期几

时刻单位(Time of Day)

将时长拆分为时、分、秒等,用于表示一天内的时间。

类型名称 引入标准 说明
std::chrono::hh_mm_ss C++20 将时长拆分为小时:分钟:秒及亚秒的模板类,支持12小时/24小时格式

时区单位(Time Zone)

C++20引入,用于处理时区、本地时间和转换。

类型名称 引入标准 说明
std::chrono::time_zone C++20 代表一个IANA时区(如"Asia/Shanghai")
std::chrono::zoned_time C++20 将时区与时间点关联的组合类型
std::chrono::tzdb C++20 IANA时区数据库的快照
std::chrono::sys_info C++20 特定时间点在某个时区的详细信息(偏移量、缩写、是否DST)
std::chrono::local_info C++20 描述本地时间到系统时间转换的低级信息(用于处理歧义或不存在的时间)
std::chrono::choose C++20 枚举,用于指定在本地时间歧义时如何选择(earliestlatest
std::chrono::time_zone_link C++20 时区的替代名称(链接)
std::chrono::nonexistent_local_time C++20 异常,表示本地时间不存在(如夏令时快进时段)
std::chrono::ambiguous_local_time C++20 异常,表示本地时间有歧义(如夏令时回拨时段)

字面量单位(Literals)

定义在std::literals::chrono_literals内联命名空间中,用于便捷书写。

字面量运算符 引入标准 返回类型/说明
operator""h C++14 返回std::chrono::duration,表示小时
operator""min C++14 返回std::chrono::duration,表示分钟
operator""s C++14 返回std::chrono::duration,表示秒
operator""ms C++14 返回std::chrono::duration,表示毫秒
operator""us C++14 返回std::chrono::duration,表示微秒
operator""ns C++14 返回std::chrono::duration,表示纳秒
operator""y C++20 返回std::chrono::year,表示年份

模板类

std::chrono::duration

这是最核心的类模板,它将一个数值(tick数)和一个时间单位(tick周期)绑定在一起,实现了类型安全的时间长度表示。

cpp 复制代码
template<class Rep,class Period = std::ratio<1>> 
class duration;
  • Rep :存储tick计数的算术类型,如 long longintdouble

  • Period :一个 std::ratio 类型,编译期指定每个tick代表的秒数。例如,std::ratio<1> 为1秒,std::milli 为1/1000秒。

核心接口与表达式

接口类别 表达式/代码示例 作用与说明
构造函数 duration<Rep, Period> d(5); 用tick计数值构造时长。通常通过预定义类型或字面量创建,如 5s100ms
观察值 d.count() 返回底层的tick计数值(Rep类型)。
算术运算 d1 + d2, d1 - d2 时长之间加减,返回相同单位的时长。
d * 3, d / 2 时长乘以或除以一个标量(Rep类型)。
比较运算 d1 == d2, d1 < d2 比较两个时长的长短(自动处理不同单位)。
单位转换 duration_cast<seconds>(d) 显式将时长转换为其他单位(可能截断)。
duration<double>(d) 若目标 Rep 为浮点数,可隐式 转换(无需cast)。
特殊值 duration::zero() 返回零长度的时长。
duration::min() 返回该时长类型能表示的最小值(最负值)。
duration::max() 返回该时长类型能表示的最大值。

示例代码:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<chrono>
#include <ratio>//提供编译期有理数算术的支持。
using namespace std::chrono;
//duration 的基本操作
void TestDuration()
{
    // 1. 构造与观察
    milliseconds ms(1024);           // 1024毫秒
    std::cout << ms.count() << " ms\n"; // 输出: 1024 ms

    // 2. 算术运算
    seconds s = duration_cast<seconds>(milliseconds(2500) + milliseconds(500));
    std::cout << s.count() << " s\n"; // 输出 3 s

    // 3. 比较运算
    if (minutes(1) > seconds(30)) 
    {
        std::cout << "1分钟比30秒长\n";
    }

    // 4. 单位转换
    seconds s2 = duration_cast<seconds>(milliseconds(1500)); // 显式转换: 1s
    std::cout << s2.count() << " s (截断后)\n"; // 输出: 1 s

    duration<double, std::ratio<60>> d_min = seconds(90); // 隐式转换: 1.5分钟
    std::cout << d_min.count() << " 分钟\n"; // 输出: 1.5 分钟

    // 5. 特殊值
    std::cout << "duration<int>::zero(): " << duration<int>::zero().count() << "\n";
}

结果为:

std::chrono::treat_as_floating_point

这是一个类型特征(type trait),用于判断一个类型是否应被视为浮点数,从而决定是否允许从高精度到低精度的隐式转换。

cpp 复制代码
//C++11定义
template< class Rep >
struct treat_as_floating_point : std::is_floating_point<Rep> {};

//C++17定义
template< class Rep >
constexpr bool treat_as_floating_point_v = treat_as_floating_point<Rep>::value;

它继承自 std::is_floating_point,因此默认情况下,只有当 Repfloatdoublelong double 时,其值为 true

核心规则与表达式

表达式 值示例 作用与说明
treat_as_floating_point_v<int> false 指示 int 不被视为浮点数。
treat_as_floating_point_v<double> true 指示 double 被视为浮点数。
编译期分支 if constexpr (treat_as_floating_point_v<Rep>) 在模板编程中,根据此特征选择不同的实现路径。
控制隐式转换 duration<double, milli> d = seconds(1); 允许 :因为目标Repdoubletreat_as_floating_pointtrue1秒隐式转为1000.0毫秒
duration<int, milli> d = seconds(1); 禁止 (编译错误):因为目标Repinttreat_as_floating_pointfalse,隐式转换可能截断数据,必须用 duration_cast

示例代码:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<chrono>
#include <ratio>//提供编译期有理数算术的支持。
using namespace std::chrono;
//treat_as_floating_point 
void TestTAFP()
{
    // 检查特征值
    std::cout << std::boolalpha;
    std::cout << "treat_as_floating_point<int>: "
        << treat_as_floating_point<int>::value << "\n";   // false
    std::cout << "treat_as_floating_point<double>: "
        << treat_as_floating_point<double>::value << "\n"; // true

    // 演示其对转换的影响
    seconds s(1); // 1秒

    // 1. 目标为 double (treat_as_floating_point == true) -> 隐式转换 OK
    duration<double, std::milli> d1 = s; // 自动变为 1000.0 毫秒
    std::cout << "d1 (double毫秒): " << d1.count() << " ms\n";

    // 2. 目标为 int (treat_as_floating_point == false) -> 隐式转换 ERROR
    // duration<int, milli> d2 = s; // 编译错误: 存在截断风险

    // 3. 必须显式转换
    duration<int, std::milli> d2 = duration_cast<milliseconds>(s);
    std::cout << "d2 (int毫秒, 显式转换): " << d2.count() << " ms\n";
}

结果为:

std::chrono::duration_values

这是一个工具类模板,它为给定的 Rep 类型提供了获取零值、最小值和最大值的统一静态接口。

cpp 复制代码
template<class Rep>
struct duration_values;

核心静态接口与表达式

静态函数 表达式示例 作用与说明
zero() duration_values<Rep>::zero() 返回 Rep 类型的零值表示。通常为 Rep(0)
min() duration_values<Rep>::min() 返回 Rep 类型能表示的最小(最负)值。
max() duration_values<Rep>::max() 返回 Rep 类型能表示的最大值。

主要用途 :它主要被标准库内部用于实现 duration::zero()duration::min()duration::max() 成员函数。例如,duration::zero() 的实现通常会返回 duration(duration_values<Rep>::zero())

自定义类型支持 :如果你想用自定义类型(如高精度有理数类)作为 durationRep,可以通过特化 duration_values 来告知标准库如何获取该类型的这些边界值,从而使你的类型能与 <chrono 无缝协作。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<chrono>
#include <ratio>//提供编译期有理数算术的支持。
using namespace std::chrono;
void TestDurationV()
{
    // 1. 获取基本类型的边界值
    std::cout << "duration_values<int>::zero(): "
        << duration_values<int>::zero() << "\n";
    std::cout << "duration_values<int>::min(): "
        << duration_values<int>::min() << "\n";
    std::cout << "duration_values<int>::max(): "
        << duration_values<int>::max() << "\n";

    std::cout << "duration_values<double>::zero(): "
        << duration_values<double>::zero() << "\n";
    std::cout << "duration_values<double>::min(): "
        << duration_values<double>::min() << "\n";
    std::cout << "duration_values<double>::max(): "
        << duration_values<double>::max() << "\n";

    // 2. 它们与 duration 的成员函数返回的时长对象的关系
    // 以下两者本质上是等价的
    auto zero_dur1 = duration<int>::zero(); // 返回 duration<int> 对象
    auto zero_dur2 = duration<int>(duration_values<int>::zero()); // 显式构造

    std::cout << "duration<int>::zero().count(): "
        << zero_dur1.count() << "\n";
    std::cout << "手动构造的零时长 count(): "
        << zero_dur2.count() << "\n";
}

结果为:

时间点

time_point

这是最核心的类模板,它将一个时钟(Clock)和一个时长(Duration)结合起来,表示相对于该时钟纪元(epoch)的一个具体时间点。

cpp 复制代码
template<class Clock, class Duration = typename Clock::duration>
class time_point;
  • Clock :关联的时钟类型,如 system_clocksteady_clock。它定义了时间点的原点(纪元)。

  • Duration :用于度量从纪元到该时间点的时长的类型,默认为时钟的 duration 类型(通常是 microsecondsnanoseconds)。

核心接口与表达式

接口类别 表达式/代码示例 作用与说明
构造函数 time_point<Clock, Duration> tp; 默认构造,创建一个值初始化的时间点(对于某些时钟,表示其纪元)。
time_point<Clock, Duration> tp(d); duration(从纪元开始的时长)构造一个时间点。
获取时长 tp.time_since_epoch() 核心观察函数 。返回一个 Duration 类型的值,表示从时钟纪元到 tp 的时间长度。
算术运算 tp + d, tp - d 时间点 ± 时长 = 新的时间点。dduration 类型。
tp1 - tp2 两个时间点相减,得到一个 duration 对象,表示它们之间的时间间隔。要求两个时间点使用相同的时钟。
比较运算 tp1 == tp2, tp1 < tp2 比较两个时间点的先后顺序(要求相同时钟)。
单位转换 time_point_cast<duration>(tp) 将时间点的内部时长单位显式转换为另一种 duration 类型(可能截断)。
floor<duration>(tp), ceil<duration>(tp) (C++17/20) 将时间点向下、向上取整到指定的时长精度。
特殊值 time_point::min() 返回该时钟能表示的最早时间点(通常是非常久远的过去)。
time_point::max() 返回该时钟能表示的最晚时间点。

示例代码:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <chrono>
#include <thread>

using namespace std::chrono;

void TestTimePoint()
{
    // 1. 获取当前时间点(基于 system_clock)
    system_clock::time_point now = system_clock::now();
    std::cout << "当前时间点已获取\n";

    // 2. 获取从纪元到现在的时长
    auto since_epoch = now.time_since_epoch();
    std::cout << "从纪元至今已过去 "
        << duration_cast<seconds>(since_epoch).count() << " 秒\n";

    // 3. 时间点算术:计算 1 小时后的时间点
    system_clock::time_point one_hour_later = now + hours(1);
    std::cout << "1 小时后的时间点已计算\n";

    // 4. 时间点减法:计算两个时间点的间隔
    auto diff = one_hour_later - now;
    std::cout << "时间间隔为 " << duration_cast<minutes>(diff).count() << " 分钟\n";

    // 5. 时间点比较
    if (one_hour_later > now) 
    {
        std::cout << "1小时后确实晚于现在\n";
    }

    // 6. 单位转换示例
    auto now_ms = time_point_cast<milliseconds>(now);
    std::cout << "当前时间点的毫秒精度计数: "
        << now_ms.time_since_epoch().count() << " ms\n";

    // 7. 休眠示例(演示时间点与实际等待)
    auto start = steady_clock::now();
    std::this_thread::sleep_for(milliseconds(100));
    auto end = steady_clock::now();
    std::cout << "休眠了 "
        << duration_cast<microseconds>(end - start).count()
        << " 微秒\n";

}

结果为:

clock_time_conversion

这是一个特性类(trait),它提供了在不同时钟的时间点之间进行转换的机制。在C++20中,随着新时钟(如 utc_clock)的引入,这个类变得尤为重要。标准库通过特化为特定的时钟对提供转换逻辑。

cpp 复制代码
template<class Dest, class Source> 
struct clock_time_conversion;
  • DestSource:通常是时钟类型。这个模板本身没有定义通用的转换,而是通过为特定的时钟对提供特化来实现转换逻辑。

核心接口与表达式

这个类的主要接口是一个函数调用运算符(operator()),它接受一个源时钟的时间点,并返回目标时钟的时间点(或是一个能转换为时间点的类型,如 sys_time)。

表达式 作用与说明
通用形式 clock_time_conversion<Dest, Source>{} 创建一个转换器对象。
converter(tp_source) 执行实际的转换。
C++20 常用转换示例 clock_time_conversion<system_clock, utc_clock>{}(utc_tp):将 utc_time 转换为 sys_time
clock_time_conversion<utc_clock, system_clock>{}(sys_tp):将 sys_time 转换为 utc_time
clock_time_conversion<system_clock, tai_clock>{}(tai_tp):将 tai_time 转换为 sys_time
便捷函数 clock_cast<Dest>(tp_source) (C++20):这是 clock_time_conversion 的简化包装,使用更直观。例如 clock_cast<utc_clock>(sys_tp)

示例代码:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <chrono>
#include <thread>

using namespace std::chrono;

void TestCTC()
{
    // 获取当前系统时间点(UTC 时间)
    auto sys_now = system_clock::now();

    // 1. 使用 clock_cast 将系统时间转换为 UTC 时钟的时间点
    utc_clock::time_point utc_now = clock_cast<utc_clock>(sys_now);
    std::cout << "系统时间已转换为 UTC 时间\n";

    // 2. 使用 clock_time_conversion 直接进行转换(等价于 clock_cast)
    clock_time_conversion<utc_clock, system_clock> converter;
    utc_clock::time_point utc_now2 = converter(sys_now);
    std::cout << "使用转换器再次转换成功\n";

    // 3. 转换回系统时间
    auto sys_now2 = clock_cast<system_clock>(utc_now);
    if (sys_now == sys_now2) 
    {
        std::cout << "往返转换后时间点一致\n";
    }

    // 4. 与 TAI 时间互转(展示不同时钟系统)
    tai_clock::time_point tai_now = clock_cast<tai_clock>(sys_now);
    std::cout << "系统时间已转换为 TAI 时间\n";

    // 计算当前 UTC 和 TAI 的偏移(通常为 37 秒,因跳秒而变)
    auto offset = tai_now - clock_cast<tai_clock>(utc_now);
    std::cout << "当前 UTC 与 TAI 偏移: "
        << duration_cast<seconds>(offset).count() << " 秒\n";
}

结果为:

时钟

<chrono 中,一个时钟类通常提供以下核心接口(以静态成员的形式):

接口 表达式 作用与说明
当前时间点 clock::now() 静态成员函数,返回当前时刻的 time_point。这是最常用的接口。
时间点类型 clock::time_point 该时钟专属的时间点类型,通常是 time_point<clock> 的别名。
时长类型 clock::duration 该时钟的"原生"精度(tick周期),通常是 duration 的别名,如 microseconds
周期类型 clock::period durationPeriod 模板参数,一个 ratio 类型,表示 tick 周期(秒)。
是否稳定 clock::is_steady 布尔值常量。true 表示该时钟是单调的(不会向后调整),适合测量时间间隔。

C++11 的基础三时钟

这三个时钟从 C++11 起就存在,覆盖了绝大多数日常使用场景。

时钟名称 核心特性 典型用途 是否稳定 (is_steady)
system_clock 表示系统实时时钟(挂钟时间),通常基于 Unix 时间戳(1970-01-01 00:00:00 UTC)。 获取当前日期时间、与日历交互、需要存储或展示给用户的时间。 通常为 false(管理员可修改系统时间)
steady_clock 单调时钟,保证只能向前移动,不受系统时间调整影响。 测量时间间隔、性能测试、超时计时。 true
high_resolution_clock 当前设置下具有最高精度的时钟。通常是 system_clocksteady_clock 的别名。 需要最高精度计时的场景(但请注意,精度和稳定性需查阅具体实现)。 取决于其底层别名

示例代码:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono;
//C++11的基础时钟
void TestClocks11()
{
    // 1. system_clock: 获取当前时间点并转换为可读形式
    auto sys_now = system_clock::now();
    std::time_t sys_now_c = system_clock::to_time_t(sys_now);
    std::cout << "system_clock 当前时间: " << std::ctime(&sys_now_c);

    // 2. steady_clock: 测量代码块执行时间
    auto start = steady_clock::now();
    std::this_thread::sleep_for(milliseconds(100)); // 模拟工作
    auto end = steady_clock::now();
    auto elapsed = duration_cast<microseconds>(end - start);
    std::cout << "steady_clock 测量耗时: " << elapsed.count() << " us\n";

    // 3. high_resolution_clock: 最高精度计时(用法与 steady_clock 类似)
    auto hr_start = high_resolution_clock::now();
    // ... 一些工作 ...
    auto hr_end = high_resolution_clock::now();
    auto hr_elapsed = duration_cast<nanoseconds>(hr_end - hr_start);
    std::cout << "high_resolution_clock 耗时: " << hr_elapsed.count() << " ns\n";

    // 4. 检查时钟的稳定性
    std::cout << std::boolalpha;
    std::cout << "system_clock 是否稳定: " << system_clock::is_steady << "\n";
    std::cout << "steady_clock 是否稳定: " << steady_clock::is_steady << "\n";
}

结果为:

C++20 新增的专业时钟

C++20 极大地扩展了时钟家族,引入了处理不同时间标准的能力,主要面向科学计算、卫星通信等专业领域。

时钟名称 核心特性与用途
utc_clock 协调世界时(UTC)。它处理跳秒(leap second),能精确表示闰秒时刻。now() 返回 utc_time
tai_clock 国际原子时(TAI)。它是连续的时间尺度,没有跳秒,与 UTC 有固定偏移(当前为 37 秒)。
gps_clock 全球定位系统时(GPS)。它与 TAI 有固定偏移(TAI = GPS + 19 秒),起点为 1980-01-06。
file_clock 用于文件系统时间戳的时钟别名。它提供了与 system_clock 相互转换的便捷方法 to_sys()/from_sys(),以处理不同操作系统的文件时间纪元。
local_t 这是一个伪时钟(pseudo-clock),它本身不提供 now() 函数。它用于标记一个时间点是与本地时间(未指定时区)关联的,需要配合时区库(如 zoned_time)才有实际意义。

示例代码:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono;
//C++20的时钟
void TestClocks20()
{
    // 1. 获取当前 UTC 时间
    auto utc_now = utc_clock::now();
    std::cout << "UTC 时间已获取\n";

    // 2. 获取当前 TAI 时间
    auto tai_now = tai_clock::now();
    std::cout << "TAI 时间已获取\n";

    // 3. 时钟转换 (C++20 的 clock_cast)
    auto sys_from_utc = clock_cast<system_clock>(utc_now);
    auto sys_from_tai = clock_cast<system_clock>(tai_now);

    std::cout << "UTC 和 TAI 时间已转换为 system_clock\n";

    // 4. file_clock 的使用
    auto file_now = file_clock::now();
    auto sys_from_file = clock_cast<system_clock>(file_now); // 或 clock_cast<system_clock>
    std::cout << "文件时间已转换为系统时间\n";

    // 5. 注意:local_t 不能直接调用 now()
    // auto local_now = local_t::now(); // 编译错误
    // 它通常与 zoned_time 配合使用
}

示例代码:

辅助模板:is_clockis_clock_v

这是 C++20 引入的一个类型特征,用于在编译期判断一个类型是否符合时钟的要求。

表达式 作用与说明
is_clock<T>::value 如果类型 T 满足时钟的所有要求(嵌套类型 durationrepperiodtime_point 以及静态函数 now() 等),则为 true
is_clock_v<T> 辅助变量模板,等同于 is_clock<T>::value

示例代码:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <chrono>
#include <thread>
using namespace std::chrono;
//C++20辅助模板------检测是否为时钟类型
class MYclass
{};
void TestCheck()
{
    std::cout << std::boolalpha;
    std::cout << "is_clock_v<system_clock>: "
        << std::chrono::is_clock_v<std::chrono::system_clock> << "\n"; // true
    std::cout << "is_clock_v<MYclass>: "
        << std::chrono::is_clock_v<MYclass> << "\n"; // false
}

结果为:

日历

日历基础类型

这些是构成日期的基本原子类型,每个都封装了特定的日历字段。

类型名称 引入标准 核心接口与表达式 作用与说明
day C++20 day d(21); 构造一个表示某日的对象(1-31)。
d.ok() 检查该日是否有效(如 2月30日返回 false)。
d == day(21) 比较两个日是否相等。
d += days(1) 日期的加减运算(会进位到月份)。
month C++20 month m(5);month m = May; 构造一个表示某月的对象(1-12)。标准库提供了 JanuaryDecember 常量。
m.ok() 检查月份是否在有效范围(1-12)。
m == May 比较月份。
year C++20 year y(2024);2024y(字面量) 构造一个表示年份的对象。
y.is_leap() 判断该年是否为闰年。
y.ok() 检查年份是否在 [-32767, 32767] 范围内(通常够用了)。
weekday C++20 weekday wd(3);wd = Wednesday; 构造一个表示星期几的对象(0=周日,1=周一...6=周六)。有 Sunday..Saturday 常量。
wd.c_encoding() 返回 C 风格的编码(0-6,周日为0)。
wd.iso_encoding() 返回 ISO 风格的编码(1-7,周一为1,周日为7)。
wd.ok() 检查是否有效。
last_spec C++20 last(单例常量) 一个标签类型,用于表示"月份的最后一天"或"最后一个 weekday"。例如 May/last 表示5月31日。

日历复合类型

这些类型组合基础元素,表示更丰富的日期概念。

类型名称 核心接口与表达式 作用与说明
month_day month_day md = May/21d; 表示"月/日"组合(无年份)。用于描述每年的固定日期,如圣诞节。
md.ok() 检查是否有效(如 2月30日无效)。
month_day_last month_day_last mdl = May/last; 表示某个月的最后一天(无年份)。
year_month year_month ym = 2024y/May; 表示"年/月"组合。
ym.ok() 检查年月是否有效。
ym + months(1) 加减月份,自动处理年份进位。
year_month_day year_month_day ymd = 2024y/May/21d; 最常用的完整日期类型
ymd.ok() 检查日期是否有效(处理闰年等)。
ymd.year(), ymd.month(), ymd.day() 分别获取年、月、日字段。
sys_days{ymd} 转换为 system_clock 的时间点(基于 days 精度),便于与旧代码交互。
year_month_day_last year_month_day_last ymdl = 2024y/May/last; 表示某年某月的最后一天。
ymdl.day() 获取该月的最后一天是几号(例如 2024y/February/last 返回 29)。
year_month_weekday year_month_weekday ymwd = 2024y/May/Wednesday[3]; 表示"某年某月的第N个星期W",如2024年5月的第三个星期三。
year_month_weekday_last year_month_weekday_last ymwdl = 2024y/May/Wednesday[last]; 表示"某年某月的最后一个星期W"。
month_weekday month_weekday mwd = May/Wednesday[3]; 表示"某月的第N个星期W"(无年份)。
month_weekday_last month_weekday_last mwdl = May/Wednesday[last]; 表示"某月的最后一个星期W"(无年份)。

关键运算符与函数

日历类型之所以强大,很大程度上归功于重载的运算符。

类别 表达式 作用与说明
创建运算符 / 最重要的创建语法/ 被重载用于组合年、月、日,顺序任意,如 2024y/May/21d21d/May/2024May/21d/2024 均合法。
算术运算 +- 可以对 year_month 加减 monthsyears;对 year_month_day 加减 daysmonthsyears
比较运算 ==!=<<=>>= 所有日历类型都支持全系列的比较运算符,按日历顺序比较。
差值运算 - 两个 year_month_day 相减,返回 days 类型的时长。
流输出 << 大多数日历类型都重载了 <<,可以直接 std::cout << ymd;,输出格式为 YYYY-MM-DD

代码示例:

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <chrono>
using namespace std::chrono;
using namespace std::literals; // 用于 "y" 和 "d" 字面量
int main() 
{
    // 1. 创建日期的多种方式
    auto ymd1 = 2024y / May / 21d;          // 最直观
    auto ymd2 = 21d / May / 2024;           // 顺序可调
    auto ymd3 = year_month_day{ 2024y, May, 21d }; // 显式构造

    std::cout << "ymd1 = " << ymd1 << "\n"; // 输出: 2024-05-21
    std::cout << "ymd2 = " << ymd2 << "\n";
    std::cout << "ymd3 = " << ymd3 << "\n";

    // 2. 访问日期字段
    std::cout << "年份: " << ymd1.year() << "\n";
    std::cout << "月份: " << ymd1.month() << "\n";
    std::cout << "日: " << ymd1.day() << "\n";

    // 3. 日期算术
    auto ymd_next_month = ymd1 + months(1);
    std::cout << "下个月: " << ymd_next_month << "\n"; // 2024-06-21

    auto ymd_next_year = ymd1 + years(1);
    std::cout << "明年: " << ymd_next_year << "\n";     // 2025-05-21

    //将 year_month_day 显式转换为 sys_days,进行天数算术,再隐式转换回来 
    auto ymd_next_week = year_month_day{ sys_days{ymd1} + days(7) };
    std::cout << "一周后: " << ymd_next_week << "\n";   // 2024-05-28

    // 4. 处理特殊日期:某月最后一天
    auto last_day_of_feb_2024 = 2024y / February / last;
    std::cout << "2024年2月最后一天: " << last_day_of_feb_2024 << "\n"; // 2024-02-29 (闰年)
    std::cout << "那天是星期几? " << weekday{ last_day_of_feb_2024 } << "\n"; // 转换为 weekday

    // 5. 处理复杂规则:某月的第N个星期W
    auto third_wed_may_2024 = 2024y / May / Wednesday[3];
    std::cout << "2024年5月第三个星期三: " << third_wed_may_2024 << "\n";

    // 6. 检查日期有效性
    year_month_day invalid_date = 2024y / February / 30d;
    std::cout << "2024-02-30 有效吗? " << std::boolalpha << invalid_date.ok() << "\n"; // false

    // 7. 转换为 time_point (用于与 system_clock 交互)
    sys_days tp = sys_days{ ymd1 }; // 转换为基于 days 的时间点
    std::cout << "时间点 (自纪元天数): " << tp.time_since_epoch().count() << " 天\n";

    // 8. 两个日期相减得到天数间隔
    auto xmas = 2024y / December / 25d;
    auto days_until_xmas = sys_days{ xmas } - sys_days{ ymd1 };
    std::cout << "距离2024年圣诞节还有: " << days_until_xmas.count() << " 天\n";

    // 9. 使用 weekday 计算下一个周五
    weekday today = weekday{ ymd1 }; // 2024-05-21 是星期二
    weekday fri = Friday;
    auto days_until_fri = (fri - today).count(); // weekday 差值
    if (days_until_fri <= 0) days_until_fri += 7;
    std::cout << "下一个周五在 " << days_until_fri << " 天后\n";

    return 0;
}

结果为:

函数

时长(Duration)操作函数

这些函数用于操作 duration 对象,进行转换、取整和算术运算。

函数表达式 引入标准 简要说明
duration_cast<ToDuration>(d) C++11 将时长 d 转换为其他单位,截断取整(向零)。
floor<ToDuration>(d) C++17 将时长 d 向下取整(负无穷方向)到目标单位。
ceil<ToDuration>(d) C++17 将时长 d 向上取整(正无穷方向)到目标单位。
round<ToDuration>(d) C++17 将时长 d 四舍五入到目标单位,中间情况向偶数舍入。
abs(d) C++17 返回时长 d 的绝对值。
d1 + d2, d1 - d2 C++11 时长之间的加减法。
d * n, n * d, d / n C++11 时长与标量的乘除法。
d1 == d2, d1 < d2, 等 C++11 时长之间的比较运算符。

时间点(Time Point)操作函数

这些函数用于操作 time_point 对象,进行算术、转换和取整。

函数表达式 引入标准 简要说明
time_point_cast<ToDuration>(tp) C++11 将时间点 tp 的内部时长单位转换为另一种 duration(截断)。
floor<ToDuration>(tp) C++17 将时间点 tp 向下取整到指定的时长精度。
ceil<ToDuration>(tp) C++17 将时间点 tp 向上取整到指定的时长精度。
round<ToDuration>(tp) C++17 将时间点 tp 四舍五入到指定的时长精度。
tp + d, tp - d C++11 时间点与时长相加/减,得到新的时间点。
tp1 - tp2 C++11 两个时间点相减,得到 duration 间隔。
tp1 == tp2, tp1 < tp2, 等 C++11 时间点之间的比较运算符。
clock_cast<DestClock>(tp) C++20 将时间点在不同时钟(如 system_clockutc_clock)之间转换。

日历(Calendar)操作函数

C++20引入,用于操作日期类型,提供算术和比较功能。每个主要类型都有一组对应的运算符。

函数表达式类别 涉及类型示例 简要说明
算术运算 day + days, month + months, year + years, year_month + months, year_month_day + years/months, 等 对日历类型进行加减运算,自动处理进位(如月份加1跨年)。
比较运算 day == day, month != month, year < year, year_month_day <= year_month_day, 等 所有日历类型都支持完整的比较运算符。
差值运算 year_month_day - year_month_day 返回 days 类型,表示两个日期的天数差。
创建语法 year/month/day, day/month/year, month/day/year 重载的 / 运算符,用于直观地创建日期组合。

输入/输出(I/O)函数

这些函数用于将 <chrono 类型格式化输出到流,或从流解析。

函数表达式 引入标准 简要说明
operator<<(os, duration) C++11 输出时长(如 "5s")。
operator<<(os, sys_time)operator<<(os, utc_time), 等 C++20 输出各种时钟的时间点。
operator<<(os, day)operator<<(os, month)operator<<(os, year)operator<<(os, year_month_day), 等 C++20 输出所有日历类型。
operator<<(os, hh_mm_ss) C++20 输出时刻(如 "12:30:45.123")。
operator<<(os, sys_info)operator<<(os, local_info) C++20 输出时区信息。
from_stream(is, fmt, day)from_stream(is, fmt, year_month_day), 等 C++20 从输入流按照指定格式解析日历类型。
parse(fmt, tp) C++20 返回一个用于解析的占位符,常与 from_stream 配合。

字面量(Literals)运算符

定义在内联命名空间 std::literals::chrono_literals 中,用于便捷书写时长和年份。

字面量运算符 引入标准 返回类型/说明
operator""h C++14 返回 hours(小时时长)
operator""min C++14 返回 minutes(分钟时长)
operator""s C++14 返回 seconds(秒时长)
operator""ms C++14 返回 milliseconds(毫秒时长)
operator""us C++14 返回 microseconds(微秒时长)
operator""ns C++14 返回 nanoseconds(纳秒时长)
operator""y C++20 返回 year(年份)

测试

日期计算

以下代码需要C++20才能编译通过

Date.h

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <chrono>
#include <iostream>
#include <string>
#include< cassert >
class Date {
private:
    std::chrono::year_month_day ymd_;

public:
    // 1. 构造函数
    // 从 std::chrono::year_month_day 构造(隐式转换允许)
    Date(const std::chrono::year_month_day& ymd) : ymd_(ymd) {
        if (!ymd_.ok()) {
            throw std::invalid_argument("Invalid date");
        }
    }

    // 从年、月、日整数构造
    Date(int year, int month, int day)
        : Date(std::chrono::year{ year } / month / day) {
    }

    // 2. 静态工厂:获取当前日期
    static Date today() {
        auto now = std::chrono::system_clock::now();
        auto today_days = std::chrono::floor<std::chrono::days>(now);
        return Date{ std::chrono::year_month_day{today_days} };
    }

    // 3. 访问器
    int year() const { return static_cast<int>(ymd_.year()); }
    unsigned month() const { return static_cast<unsigned>(ymd_.month()); }
    unsigned day() const { return static_cast<unsigned>(ymd_.day()); }

    // 获取星期几 (Sunday = 0, Monday = 1, ..., Saturday = 6)
    unsigned weekday() const {
        std::chrono::weekday wd{ std::chrono::sys_days{ymd_} };
        return wd.c_encoding(); // 返回 0-6
    }

    // 判断是否为闰年
    bool isLeapYear() const { return ymd_.year().is_leap(); }

    // 4. 日期算术运算符
    // 加天数
    Date operator+(const std::chrono::days& d) const {
        auto sd = std::chrono::sys_days{ ymd_ } + d;
        return Date{ std::chrono::year_month_day{sd} };
    }

    // 减天数
    Date operator-(const std::chrono::days& d) const {
        return *this + (-d);
    }

    // 加月份
    Date operator+(const std::chrono::months& m) const {
        auto new_ymd = ymd_ + m;
        // 处理可能的无效日期(如 1月31日 + 1个月 = 2月31日,会调整到2月最后一天)
        if (!new_ymd.ok()) {
            new_ymd = new_ymd.year() / new_ymd.month() / std::chrono::last;
        }
        return Date{ new_ymd };
    }

    // 减月份
    Date operator-(const std::chrono::months& m) const {
        return *this + (-m);
    }

    // 加年份
    Date operator+(const std::chrono::years& y) const {
        auto new_ymd = ymd_ + y;
        // 处理闰年2月29日的情况
        if (!new_ymd.ok()) {
            new_ymd = new_ymd.year() / new_ymd.month() / std::chrono::last;
        }
        return Date{ new_ymd };
    }

    // 减年份
    Date operator-(const std::chrono::years& y) const {
        return *this + (-y);
    }

    // 复合赋值运算符(可选)
    Date& operator+=(const std::chrono::days& d) { return *this = *this + d; }
    Date& operator-=(const std::chrono::days& d) { return *this = *this - d; }
    Date& operator+=(const std::chrono::months& m) { return *this = *this + m; }
    Date& operator-=(const std::chrono::months& m) { return *this = *this - m; }
    Date& operator+=(const std::chrono::years& y) { return *this = *this + y; }
    Date& operator-=(const std::chrono::years& y) { return *this = *this - y; }

    // 5. 比较运算符(C++20 可以自动生成,但为清晰显式声明)
    auto operator<=>(const Date&) const = default; // C++20 三路比较,自动生成所有关系运算符

    // 6. 差值计算
    // 返回两个日期相差的天数(忽略时间部分)
    std::chrono::days daysUntil(const Date& other) const {
        return std::chrono::sys_days{ other.ymd_ } - std::chrono::sys_days{ ymd_ };
    }

    // 返回两个日期相差的完整月份数(近似,基于日历月)
    // 注意:这不是一个精确的物理时间差,而是日历上的月份差。
    int monthsUntil(const Date& other) const {
        // 简化实现:将年份差 * 12 + 月份差,再根据日进行调整
        int y1 = year(), m1 = month(), d1 = day();
        int y2 = other.year(), m2 = other.month(), d2 = other.day();

        int month_diff = (y2 - y1) * 12 + (m2 - m1);
        // 如果结束日期的日号小于开始日期的日号,则不满整月
        if (d2 < d1) {
            --month_diff;
        }
        return month_diff;
    }

    // 7. 友元输出运算符
    friend std::ostream& operator<<(std::ostream& os, const Date& date) {
        os << date.year() << '-'
            << (date.month() < 10 ? "0" : "") << date.month() << '-'
            << (date.day() < 10 ? "0" : "") << date.day();
        return os;
    }

    // 获取内部 year_month_day 的引用(用于需要直接操作底层类型的场景)
    const std::chrono::year_month_day& get_ymd() const { return ymd_; }
};

DateTest.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"

using namespace std::chrono_literals; // 用于 h, min, s, ms, us, ns, y

void testDate() 
{
    std::cout << "=== 开始测试 Date 类 ===\n";

    // 1. 构造函数与 today()
    std::cout << "1. 构造函数测试:\n";
    Date d1(2024, 5, 21);
    std::cout << "   d1: " << d1 << "\n";
    assert(d1.year() == 2024 && d1.month() == 5 && d1.day() == 21);

    auto d_today = Date::today();
    std::cout << "   Today: " << d_today << "\n";

    // 2. 无效日期抛出异常
    std::cout << "2. 无效日期测试 (2024-02-30): ";
    try {
        Date invalid(2024, 2, 30);
        std::cout << "FAILED: 未抛出异常\n";
    }
    catch (const std::invalid_argument& e) {
        std::cout << "PASSED: 捕获异常 - " << e.what() << "\n";
    }
    catch (...) {
        std::cout << "FAILED: 捕获到未知异常\n";
    }

    // 3. 闰年判断
    std::cout << "3. 闰年判断测试:\n";
    assert(Date(2024, 1, 1).isLeapYear() == true);
    assert(Date(2023, 1, 1).isLeapYear() == false);
    assert(Date(2000, 1, 1).isLeapYear() == true);
    assert(Date(1900, 1, 1).isLeapYear() == false);
    std::cout << "   所有闰年判断通过\n";

    // 4. 星期几
    std::cout << "4. 星期几测试:\n";
    // 2024-05-21 是星期二 (ISO: 2, C: 2? 需要查证,我们的weekday返回c_encoding)
    // 根据常识,2024-05-21 是星期二,在weekday中周二对应1(如果周日是0,周一是1,周二是2)
    // 但c_encoding() 周日=0,周一=1,周二=2,周三=3,周四=4,周五=5,周六=6
    assert(Date(2024, 5, 21).weekday() == 2); // 星期二
    assert(Date(2024, 5, 22).weekday() == 3); // 星期三
    assert(Date(2024, 5, 19).weekday() == 0); // 星期日
    std::cout << "   所有星期几判断通过\n";

    // 5. 日期算术
    std::cout << "5. 日期算术测试:\n";
    Date d2(2024, 5, 21);
    auto d_plus_7 = d2 + std::chrono::days(7);
    std::cout << "   " << d2 << " + 7 days = " << d_plus_7 << "\n";
    assert(d_plus_7 == Date(2024, 5, 28));

    auto d_plus_1m = d2 + std::chrono::months(1);
    std::cout << "   " << d2 << " + 1 month = " << d_plus_1m << "\n";
    assert(d_plus_1m == Date(2024, 6, 21));

    auto d_plus_1y = d2 + std::chrono::years(1);
    std::cout << "   " << d2 << " + 1 year = " << d_plus_1y << "\n";
    assert(d_plus_1y == Date(2025, 5, 21));

    // 边界测试:月末
    Date d3(2024, 1, 31);
    auto d_plus_1m_jan = d3 + std::chrono::months(1);
    std::cout << "   " << d3 << " + 1 month = " << d_plus_1m_jan << "\n";
    assert(d_plus_1m_jan == Date(2024, 2, 29)); // 2024是闰年,所以是2月29日

    Date d4(2023, 1, 31);
    auto d_plus_1m_jan_nonleap = d4 + std::chrono::months(1);
    std::cout << "   " << d4 << " + 1 month = " << d_plus_1m_jan_nonleap << "\n";
    assert(d_plus_1m_jan_nonleap == Date(2023, 2, 28)); // 2023非闰年,2月28日

    // 跨年测试
    Date d5(2024, 12, 15);
    auto d_plus_2m = d5 + std::chrono::months(2);
    std::cout << "   " << d5 << " + 2 months = " << d_plus_2m << "\n";
    assert(d_plus_2m == Date(2025, 2, 15));

    // 6. 比较运算符
    std::cout << "6. 比较运算符测试:\n";
    Date d6a(2024, 5, 21);
    Date d6b(2024, 5, 22);
    Date d6c(2024, 5, 21);
    assert(d6a == d6c);
    assert(d6a != d6b);
    assert(d6a < d6b);
    assert(d6a <= d6c);
    assert(d6b > d6a);
    assert(d6b >= d6c);
    std::cout << "   所有比较运算符通过\n";

    // 7. 差值计算
    std::cout << "7. 差值计算测试:\n";
    Date d7_start(2024, 5, 21);
    Date d7_end(2024, 12, 25);
    auto days_diff = d7_start.daysUntil(d7_end);
    std::cout << "   " << d7_start << " 到 " << d7_end << " 相差 "
        << days_diff.count() << " 天\n";
    assert(days_diff.count() == 218); // 手工验证 5月剩余10天 + 6月30 + 7月31 + 8月31 + 9月30 + 10月31 + 11月30 + 12月25 = 218

    auto months_diff = d7_start.monthsUntil(d7_end);
    std::cout << "   " << d7_start << " 到 " << d7_end << " 相差 "
        << months_diff << " 个整月\n";
    assert(months_diff == 7); // 5月21日到12月21日是7个月,到12月25日超过21日,所以仍是7个整月

    // 跨年差值
    Date d7_start2(2024, 12, 25);
    Date d7_end2(2025, 1, 10);
    auto days_diff2 = d7_start2.daysUntil(d7_end2);
    std::cout << "   " << d7_start2 << " 到 " << d7_end2 << " 相差 "
        << days_diff2.count() << " 天\n";
    assert(days_diff2.count() == 16); // 12月剩余7天 + 1月10天 = 16

    // 8. 复合赋值
    std::cout << "8. 复合赋值测试:\n";
    Date d8(2024, 5, 21);
    d8 += std::chrono::days(10);
    std::cout << "   +=10天: " << d8 << "\n";
    assert(d8 == Date(2024, 5, 31));

    d8 += std::chrono::months(1);
    std::cout << "   +=1月: " << d8 << "\n";
    assert(d8 == Date(2024, 6, 30)); // 5月31日+1月 -> 6月30日

    d8 -= std::chrono::years(1);
    std::cout << "   -=1年: " << d8 << "\n";
    assert(d8 == Date(2023, 6, 30));

    std::cout << "=== 所有测试通过 ===\n";
}

int main() 
{
    testDate();
    return 0;
}

结果为:

性能测试

本期我们以内存池为例,对比其与直接向系统new/delete的性能差距

MemoryPool.h

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#pragma once 
#include <vector>
#include <cassert>
#include <iostream>

using std::vector;
using std::cout;
using std::endl;

namespace MyMemoryPool
{
    // 内存对齐:向上取整到指定对齐值(align 必须是 2 的幂)
    constexpr inline size_t align_up(size_t n, size_t align)
    {
        return (n + (align - 1)) & ~(align - 1);
    }

    // 固定大小内存池,每个块大小相同,按页管理
    class FixedSizePool
    {
    public:
        // 构造函数:指定块大小和每页块数(默认1024)
        explicit FixedSizePool(size_t blockSize, size_t blockPerPage = 1024)
            : block_Size(adjust_block_size(blockSize)),   // 确保块至少能存一个指针并正确对齐
            block_Per_Page(blockPerPage),
            free_list(nullptr),
            total_allocations(0),
            total_deallocations(0),
            page_count(0)
        {
        }

        // 析构函数:释放所有已分配的页
        ~FixedSizePool()
        {
            for (void* page : Pages)
            {
                ::operator delete[](page);   // 全局释放,避免调用析构函数
            }
        }

        // 分配一个块
        void* allocate()
        {
            if (!free_list)                  // 空闲链表为空,需要扩充新页
            {
                expand();
            }
            Node* head = free_list;
            free_list = head->next;
            ++total_allocations;
            return head;
        }

        // 回收一个块
        void deallocate(void* p)
        {
            if (!p) return;
            Node* node = static_cast<Node*>(p);
            node->next = free_list;
            free_list = node;
            ++total_deallocations;
        }

        // 获取块大小
        size_t Get_block_Size() const { return block_Size; }
        size_t Get_block_Per_Page() const { return block_Per_Page; }

        // 打印统计信息
        void print_stats() const
        {
            std::cout << "内存池统计:" << std::endl;
            std::cout << "  总分配次数: " << total_allocations << std::endl;
            std::cout << "  总释放次数: " << total_deallocations << std::endl;
            std::cout << "  当前使用中: " << (total_allocations - total_deallocations) << std::endl;
            std::cout << "  页数量: " << page_count << std::endl;
            std::cout << "  内存使用量: " << (page_count * block_Size * block_Per_Page) << " 字节" << std::endl;
        }

    private:
        // 结点结构,用于空闲链表
        struct Node
        {
            Node* next;
        };

        // 申请新的一页内存,并将其切分为多个块链入空闲链表
        void expand()
        {
            size_t page_bytes = block_Size * block_Per_Page;
            char* page = static_cast<char*>(::operator new[](page_bytes));
            Pages.push_back(page);
            ++page_count;

            // 将当前页的所有块串联成链表
            for (size_t i = 0; i < block_Per_Page; ++i)
            {
                char* block = page + i * block_Size;
                Node* n = reinterpret_cast<Node*>(block);
                n->next = free_list;
                free_list = n;
            }
        }

        // 调整块大小:确保至少 sizeof(void*) 并正确对齐
        size_t adjust_block_size(size_t s) const
        {
            size_t min_size = sizeof(void*);
            return align_up(s < min_size ? min_size : s, alignof(void*));
        }

        Node* free_list;               // 空闲链表头
        size_t block_Size;              // 实际块大小(已调整)
        size_t block_Per_Page;           // 每页块数
        vector<void*> Pages;             // 记录所有分配过的页,用于析构时释放

        // 统计变量
        size_t total_allocations;
        size_t total_deallocations;
        size_t page_count;
    };
}

MPTest.cpp

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <chrono>
#include <vector>
#include "MemoryPool.h"

using namespace MyMemoryPool;
using namespace std::chrono;

// 通用测试模板:执行 iterations 次分配和释放,返回耗时(秒)
template <typename AllocFunc, typename DeallocFunc>
double run_test(size_t iterations, AllocFunc alloc, DeallocFunc dealloc) {
    std::vector<void*> pointers;
    pointers.reserve(iterations);

    auto start = high_resolution_clock::now();

    for (size_t i = 0; i < iterations; ++i) {
        pointers.emplace_back(alloc());
    }
    for (void* p : pointers) {
        dealloc(p);
    }

    auto end = high_resolution_clock::now();
    duration<double> elapsed = end - start;
    return elapsed.count();
}

int main() {
    const size_t BLOCK_SIZE = 256;          // 每个块 256 字节
    const size_t BLOCKS_PER_PAGE = 1024;   // 每页 1024 块
    const size_t ITERATIONS = 1000000;      // 分配/释放 100 万次

    std::cout << "===== 性能对比测试 =====\n";
    std::cout << "块大小: " << BLOCK_SIZE << " 字节\n";
    std::cout << "每页块数: " << BLOCKS_PER_PAGE << "\n";
    std::cout << "分配/释放次数: " << ITERATIONS << "\n\n";

    // 1. 内存池测试
    {
        FixedSizePool pool(BLOCK_SIZE, BLOCKS_PER_PAGE);
        double pool_time = run_test(ITERATIONS,
            [&pool]() { return pool.allocate(); },
            [&pool](void* p) { pool.deallocate(p); }
        );
        std::cout << "内存池耗时: " << pool_time << " 秒\n";
        pool.print_stats();
    }

    std::cout << "\n";

    // 2. 直接 new/delete 测试
    {
        double new_time = run_test(ITERATIONS,
            []() { return ::operator new(BLOCK_SIZE); },
            [](void* p) { ::operator delete(p); }
        );
        std::cout << "new/delete 耗时: " << new_time << " 秒\n";
    }

    return 0;
}

结果为:

本期关于C++中<chrono>库的介绍就到这里了。熟练运用它可以在时间计算、性能测试灯多方面领域大展身手!

如果你喜欢这期内容请点个赞谢谢。

封面图自取:

相关推荐
摸鱼的春哥1 小时前
春哥的Agent通关秘籍11:本地RAG实战(中上)
前端·javascript·后端
GetcharZp1 小时前
谁是OpenClaw?这个一夜爆火的“AI打工人”,正在悄悄接管你的电脑!
人工智能·后端
亓才孓1 小时前
jdk动态代理和Cglib动态代理的区别,为什么Cglib更适配SpringAOP
java·开发语言
石牌桥网管1 小时前
Go类型断言
开发语言·后端·golang
zh_xuan2 小时前
kotlin 高阶函数用法
开发语言·kotlin
FserSuN2 小时前
AI编程 - 规范驱动开发(SDD)学习
驱动开发·学习·ai编程
程序员敲代码吗3 小时前
解析Kotlin中元组的多返回值实现
android·开发语言·kotlin
Java后端的Ai之路3 小时前
【 Java】-网络协议核心知识问答(比较全)
java·开发语言·网络协议
学嵌入式的小杨同学3 小时前
嵌入式硬件开发入门:PCB 设计核心流程 + 基础元器件实战指南
vscode·后端·嵌入式硬件·架构·vim·智能硬件·pcb工艺