跳转至

CallOnceInit - 懒加载初始化

CallOnceInit<T> 是线程安全的懒加载模板,资源只在首次访问时初始化一次。我们用它来缓存那些获取开销大但不会变化的数据——比如 CPU 型号、内存大小这些系统信息。

基本用法

继承 CallOnceInit<资源类型>,然后重写 init_resources() 方法实现初始化逻辑:

#include "base/helpers/once_init.hpp"

class CPUInfoCache : public cf::CallOnceInit<CPUInfo> {
protected:
    bool init_resources() override {
        // 执行实际的初始化
        resource.model = queryModel();
        resource.arch = queryArch();
        resource.manufacturer = queryManufacturer();
        return true;  // 返回 true 表示初始化成功
    }

    bool force_do_reinit() override {
        // 强制重新初始化时的逻辑
        return init_resources();
    }
};

首次调用 get_resources() 时,init_resources() 会被自动调用。后续调用直接返回缓存的资源,不会再执行初始化。

使用示例

创建全局或静态实例,然后通过 get_resources() 访问:

static CPUInfoCache g_cpu_cache;

void print_cpu_info() {
    // 首次调用会执行初始化
    auto& info = g_cpu_cache.get_resources();

    std::cout << "CPU: " << info.model << std::endl;
    std::cout << "架构: " << info.arch << std::endl;

    // 后续调用返回缓存,不会重复查询
    auto& info2 = g_cpu_cache.get_resources();
    assert(&info == &info2);  // 同一个对象
}

重新初始化

如果需要强制刷新缓存,调用 force_reinit()

// 强制重新初始化
g_cpu_cache.force_reinit();

带参数的版本可以在重新初始化时传入新参数:

class ConfigCache : public cf::CallOnceInit<Config> {
protected:
    bool init_resources() override {
        resource = load_default_config();
        return true;
    }

    bool force_do_reinit() override {
        // 使用新参数重新加载
        resource = load_config(new_config_path);
        return true;
    }

private:
    std::string new_config_path;
};

// 使用
config.force_reinit("/etc/app/new_config.json");

⚠️ force_reinit() 不是线程安全的。如果可能和 get_resources() 并发调用,需要自己加锁保护。

常见场景

缓存系统信息

class SystemInfoCache : public cf::CallOnceInit<SystemInfo> {
protected:
    bool init_resources() override {
        resource.cpu_count = std::thread::hardware_concurrency();
        resource.total_memory = get_total_memory();
        return true;
    }
};

SystemInfoCache sys_info;
auto& info = sys_info.get_resources();
std::cout << "CPU 核心数: " << info.cpu_count << std::endl;

延迟加载配置

class ConfigCache : public cf::CallOnceInit<Config> {
protected:
    bool init_resources() override {
        resource = load_config_file("/etc/app/config.json");
        return resource.valid();
    }

    bool force_do_reinit() override {
        // 配置文件可能被外部修改,支持重新加载
        resource = load_config_file("/etc/app/config.json");
        return resource.valid();
    }
};

ConfigCache config;
if (config.get_resources().debug_mode) {
    // ...
}

线程安全保证

get_resources() 是线程安全的。多个线程同时首次调用时,只有一个线程会执行 init_resources(),其他线程会等待完成:

// 以下代码是安全的
std::thread t1([&]() { auto& info = cache.get_resources(); });
std::thread t2([&]() { auto& info = cache.get_resources(); });

t1.join();
t2.join();
// init_resources() 只被执行一次

force_reinit() 不是线程安全的。如果需要在线程间重新初始化,必须加锁:

std::mutex cache_mutex;
std::lock_guard<std::mutex> lock(cache_mutex);
cache.force_reinit();  // 现在安全了

注意事项

如果 init_resources() 返回 false,下次调用 get_resources() 会重试。这使得初始化失败可以恢复,但也意味着失败的初始化会被反复尝试——如果失败是不可恢复的,最好在初始化逻辑里处理。

另外,CallOnceInit 没有虚析构函数,这不是 bug,而是有意为之。我们不需要多态删除,避免虚函数开销。

相关文档