**标题:如何在 C++17 中实现线程安全的单例模式?**

正文:

在多线程环境下,单例模式(Singleton)需要保证实例只创建一次,并且在所有线程之间共享同一个实例。C++17 提供了多种工具可以帮助我们实现线程安全的单例,下面介绍两种常见且优雅的实现方式:std::call_onceconstexpr 本地静态变量。


1. 使用 std::call_oncestd::once_flag

#include <mutex>
#include <iostream>

class Singleton {
public:
    static Singleton& getInstance() {
        std::call_once(initFlag, [](){ instance.reset(new Singleton()); });
        return *instance;
    }

    void doWork() { std::cout << "Doing work\n"; }

private:
    Singleton() { std::cout << "Singleton ctor\n"; }
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static std::unique_ptr <Singleton> instance;
    static std::once_flag initFlag;
};

std::unique_ptr <Singleton> Singleton::instance;
std::once_flag Singleton::initFlag;

优点

  • 只在第一次访问时创建实例,后续访问不再有同步开销。
  • std::call_once 通过 once_flag 确保线程安全,且跨平台表现一致。

缺点

  • 需要手动管理 std::unique_ptr,如果不小心会导致生命周期问题。

2. 使用局部静态变量(C++11 及以后)

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance; // C++11 之后已保证线程安全
        return instance;
    }

    void doWork() { std::cout << "Doing work\n"; }

private:
    Singleton() { std::cout << "Singleton ctor\n"; }
    ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};

优点

  • 代码简洁,编译器负责实例化和销毁。
  • 自 C++11 起,局部静态变量的初始化是线程安全的。

缺点

  • 在极少数情况下(如多进程共享同一可执行文件的内存映射),可能会导致多个实例。
  • 对于具有显式销毁时机需求的场景,需自行实现。

3. 延迟销毁(懒销毁)与 std::shared_ptr

如果你希望在程序结束时自动销毁单例,或者允许多次调用 getInstance 后自动释放,可以结合 std::shared_ptr

class Singleton {
public:
    static std::shared_ptr <Singleton> getInstance() {
        static std::shared_ptr <Singleton> instance(new Singleton(),
            [](Singleton* p){ delete p; });
        return instance;
    }
    // ...
private:
    Singleton() {}
};

此实现让单例在最后一个引用销毁时自动释放,适用于需要显式资源管理的场景。


4. 小结

  • 推荐:在大多数项目中,使用局部静态变量(C++11 以上)最简单、最安全;若需要显式控制实例生命周期,可改用 std::call_once + unique_ptr
  • 注意:单例类的构造函数应为私有,拷贝构造和赋值运算符禁止。若单例中持有线程、文件句柄等资源,确保在析构时正确释放。
  • 性能:第一次实例化时会有一次原子操作或锁,后续访问几乎无同步开销。

通过以上两种方式,你可以在 C++17 环境下实现一个线程安全且易于维护的单例模式。祝你编码愉快!

发表评论