**题目:** C++17实现线程安全的懒汉式单例模式

正文:

在现代 C++ 中,单例模式仍然是实现全局唯一实例的常用手段。若不考虑线程安全,最简单的实现方式是使用静态局部变量。自 C++11 起,编译器对静态局部变量的初始化已保证是线程安全的,因而可以直接利用这一特性实现懒汉式单例。以下示例展示了在 C++17 环境下的完整实现,并结合了 std::call_once 进一步说明多种实现思路。

#include <iostream>
#include <mutex>
#include <memory>
#include <thread>
#include <vector>

// 1. 传统懒汉式单例(C++11 之后线程安全)
class Singleton1 {
public:
    // 禁止拷贝构造和赋值
    Singleton1(const Singleton1&) = delete;
    Singleton1& operator=(const Singleton1&) = delete;

    // 获取实例的唯一入口
    static Singleton1& Instance() {
        static Singleton1 instance;   // 静态局部,线程安全
        return instance;
    }

    void DoSomething() const {
        std::cout << "Singleton1 instance address: " << this << std::endl;
    }

private:
    Singleton1() = default;
    ~Singleton1() = default;
};

// 2. 使用 std::call_once 实现线程安全的单例
class Singleton2 {
public:
    Singleton2(const Singleton2&) = delete;
    Singleton2& operator=(const Singleton2&) = delete;

    static Singleton2& Instance() {
        std::call_once(initFlag_, []() {
            instance_.reset(new Singleton2());
        });
        return *instance_;
    }

    void DoSomething() const {
        std::cout << "Singleton2 instance address: " << this << std::endl;
    }

private:
    Singleton2() = default;
    ~Singleton2() = default;

    static std::unique_ptr <Singleton2> instance_;
    static std::once_flag initFlag_;
};

std::unique_ptr <Singleton2> Singleton2::instance_;
std::once_flag Singleton2::initFlag_;

// 3. Meyer's Singleton(利用函数内部静态变量)
class Singleton3 {
public:
    static Singleton3& Get() {
        static Singleton3 instance;
        return instance;
    }

    void DoSomething() const {
        std::cout << "Singleton3 instance address: " << this << std::endl;
    }

private:
    Singleton3() = default;
    ~Singleton3() = default;
};

int main() {
    // 多线程环境下测试单例
    auto worker = [](auto& obj, int id) {
        obj.DoSomething();
        std::cout << "Thread " << id << " finished.\n";
    };

    std::vector<std::thread> threads;
    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(worker, std::ref(Singleton1::Instance()), i);
        threads.emplace_back(worker, std::ref(Singleton2::Instance()), i + 5);
        threads.emplace_back(worker, std::ref(Singleton3::Get()), i + 10);
    }

    for (auto& th : threads) th.join();

    return 0;
}

关键点解析

  1. 静态局部变量

    • C++11 规定在多线程环境下首次进入函数时对静态局部变量的初始化是互斥的。Singleton1::Instance() 的实现最为简洁,且不需要额外的锁或 std::once_flag
  2. std::call_once

    • Singleton2 中,我们手动控制实例的创建时机。std::call_once 确保 lambda 表达式只会被调用一次,适合需要更复杂初始化逻辑或想显式控制实例生命周期的场景。
  3. 内存管理

    • Singleton2 使用 std::unique_ptr 管理实例,保证在程序结束时自动析构。若使用裸指针,必须手动删除,容易导致泄漏。
  4. 拷贝与赋值禁止

    • 为防止复制单例导致多实例,使用 delete 关键字显式删除拷贝构造和赋值操作。
  5. 多线程测试

    • main 中启动 15 个线程,分别访问三种实现,观察实例地址是否相同,从而验证单例的正确性。

小结

  • C++11 之后,最推荐的实现方式是利用静态局部变量的线程安全特性(如 Singleton1)。
  • 若需要更细粒度控制实例创建时机,可使用 std::call_once(如 Singleton2)。
  • 无论何种实现,都应禁止拷贝与赋值,确保全局唯一。

通过上述实现,你可以在自己的项目中轻松引入线程安全的单例模式,并根据需求选择最合适的实现方式。

发表评论