析构函数中获取对象创建时间需在构造时用std::chrono::steady_clock::now()保存到const time_point成员,析构时计算差值;避免system_clock、析构中I/O及多线程日志竞争,推荐模板基类+spdlog等线程安全日志。

析构函数里怎么拿到对象创建时间
不能靠全局时钟差值——std::chrono::steady_clock::now() 在构造和析构时各调一次,但必须把构造时刻存下来。最直接的办法是在构造函数里记录时间戳,用成员变量保存。
常见错误是误用 std::chrono::system_clock:它可能受系统时间调整影响,导致时长为负或跳变;必须用 std::chrono::steady_clock,它是单调递增的。
- 声明一个私有成员:
const std::chrono::time_point<:chrono::steady_clock> m_created_at;</:chrono::steady_clock> - 在构造函数初始化列表中赋值:
MyClass() : m_created_at(std::chrono::steady_clock::now()) {} - 析构函数里计算差值:
auto duration = std::chrono::steady_clock::now() - m_created_at;
记录时长该打日志还是写文件
析构函数里做 I/O 有风险:如果对象在静态存储期(比如全局对象、单例)析构阶段,std::cout 或文件流可能已关闭,写入会失败甚至 crash。更稳妥的是用线程安全的日志库(如 spdlog),或只往内存 buffer 写,延后 flush。
简单场景下,可先格式化成字符串再输出,避免流状态依赖:
立即学习“C++免费学习笔记(深入)”;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(); std::fprintf(stderr, "[DEBUG] %s lived %lld ms\n", typeid(*this).name(), ms);
- 别用
std::cout,它不是析构安全的 - 避免在析构中打开新文件——文件句柄资源可能已释放
- 如果用
spdlog,确保 logger 在 main() 结束前未被销毁
多线程环境下要注意什么
对象可能在任意线程中构造/析构,而 std::chrono::steady_clock::now() 是线程安全的,没问题;但日志输出本身不是原子操作,多个对象同时析构可能造成日志乱序或截断。
- 加锁不是好办法——析构函数里锁竞争容易死锁
- 改用无锁日志缓冲区,或让日志库内部处理并发(如
spdlog::stdout_logger_mt) - 避免在析构中触发复杂逻辑(比如网络上报、磁盘 sync)
继承体系下如何保证子类也自动记录
基类析构函数无法“感知”子类类型,但可以靠虚函数 + 模板技巧把记录逻辑下沉到基类,又不破坏多态。最简方案是让基类带模板参数记录类型名:
template<typename T>
class LifetimeTracker {
protected:
const std::chrono::time_point<std::chrono::steady_clock> m_created_at;
LifetimeTracker() : m_created_at(std::chrono::steady_clock::now()) {}
virtual ~LifetimeTracker() {
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - m_created_at).count();
std::fprintf(stderr, "[LIFE] %s: %lld ms\n", typeid(T).name(), ms);
}
};
// 使用:class MyClass : public LifetimeTracker<MyClass> { ... };
- 不能用
typeid(*this).name()在基类析构里——此时虚表已被部分销毁,结果不可靠 - 模板参数
T在编译期绑定,安全 - 若子类有自定义析构逻辑,需显式调用
~LifetimeTracker()或确保基类析构被调用
实际最难的不是计时,是确保日志能稳定落地——尤其在程序退出过程中,静态对象析构顺序不可控,一不小心就静默丢数据。


















