C++单例模式

使用单例模式的理由

在开发过程中,很多时候一个类我们希望它只创建一个对象,比如:线程池、缓存、网络请求等。当这类对象有多个实例时,程序就可能会出现异常,比如:程序出现异常行为、得到的结果不一致等。
单例主要有这两个优点:

  • 提供了对唯一实例的受控访问。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

实现单例模式主要有以下几个关键点:

  1. 构造函数设置为 private ,这避免外部通过 new 创建实例;
  2. 通过一个静态方法或者枚举返回单例类对象;
  3. 考虑对象创建时的线程安全问题,确保单例类的对象有且仅有一个,尤其是在多线程环境下;
  4. 保单例类对象在反序列化时不会重新构建对象。
  5. 考虑是否支持延迟加载;

饿汉式

在类加载的期间,就已经将 instance 静态实例初始化好了,所以,instance 实例的创建是线程安全的。不过,这样的实现方式不支持延迟加载实例。
静态指针_instance_ptr存储在.data区,指向堆对象。如果不对堆对象手动执行析构,那么程序结束时可以释放堆内存,但却无法正确析构。解决的办法:1、使用智能指针和自定义删除器自动析构。2、使用atexit注册删除函数。

class Singleton1{
public:
    static std::shared_ptr<Singleton1> getInstance(){
        return _instance_ptr;
    }
    Singleton1(const Singleton1&)=delete;
    Singleton1(Singleton1&&) = delete;
    Singleton1& operator=(const Singleton1&) = delete;
    Singleton1& operator=(Singleton1&&) = delete;

private:
    Singleton1(){std::cout << "Singleton1()" << std::endl;}    // 私有构造
    ~Singleton1(){std::cout << "~Singleton1()" << std::endl;}
    static void destructInstance() {delete _instance_ptr;};
    static std::shared_ptr<Singleton1> _instance_ptr;
};

std::shared_ptr<Singleton1> Singleton1::_instance_ptr = std::shared_ptr<Singleton1>(new Singleton1(), destructInstance);

懒汉式

懒汉式相对于饿汉式的优势是支持延迟加载。

class Singleton2{
public:
    static std::shared_ptr<Singleton2> getInstance(){
        if(!_instance_ptr)
            _instance_ptr = std::shared_ptr<Singleton2>(
                new Singleton2(), [](Singleton2* ptr){delete ptr;});
        return _instance_ptr;
    }

    Singleton2(const Singleton2&)=delete;
    Singleton2(Singleton2&&) = delete;
    Singleton2& operator=(const Singleton2&) = delete;
    Singleton2& operator=(Singleton2&&) = delete;

private:
    Singleton2(){std::cout << "Singleton2()" << std::endl ;}    // 私有构造
    ~Singleton2() {std::cout << "~Singleton2()" << std::endl;}
    static std::shared_ptr<Singleton2> _instance_ptr;
};
std::shared_ptr<Singleton2> Singleton2::_instance_ptr = nullptr;

该种方法并不线程安全,多个线程同时调用getInstance并且同时进入if分支,那么就会创建多个实例。

double-check加锁(懒汉式)

定义在类内部的lambda表达式,视为类的一部分,可以访问私有成员。

class Singleton3
{
public:
    static std::shared_ptr<Singleton3> getInstance()
    {
        if(_instance == nullptr)
        {
            lock_guard<mutex> lock(_mtx);
                if(!_instance)
                    _instance = std::shared_ptr<Singleton3>(
                        new Singleton3(), [](Singleton3* ptr){delete ptr;});
        }
        return _instance;
    }

    Singleton3(const Singleton3&) = delete;
    Singleton3(Singleton3 &&) = delete;
    Singleton3& operator=(const Singleton3&) = delete;
    Singleton3& operator=(Singleton3 &&) = delete;
private:

    Singleton3(){std::cout << "Singleton3()"<< std::endl;}    // 私有构造
    ~Singleton3() {std::cout << "~Singleton3()"<< std::endl;}
    static mutex _mtx;
    static std::shared_ptr<Singleton3> _instance;
};
std::shared_ptr<Singleton3> Singleton3::_instance = nullptr;
std::mutex Singleton3::_mtx{};

new操作分三个阶段:

  1. 开辟空间;
  2. 构造对象;
  3. 返回指针。
    在多cpu多线程中,由于编译器指令重排或者是cpu指令重排,可能使得按照 1 -> 3 -> 2的方式进行;返回指针后,其他线程不会通过第一个if分支直接返回对象指针,由于此时对象还未构造完成,其他线程使用该指针将导致未定义行为。

使用静态局部变量(懒汉式)

在 C++11 中,静态局部变量的初始化是线程安全的。这意味着当多个线程并发访问一个函数时,如果该函数包含一个静态局部变量,只有第一个访问该变量的线程会执行初始化操作,而其他线程会等待初始化完成后再继续执行。C++11 的这一特性解决了多线程环境下静态局部变量可能导致的竞争条件问题。

静态局部变量在程序退出或作用域结束时会被自动销毁。C++ 会为静态局部变量调用析构函数,因此不需要手动管理其生命周期。在某些情况下,静态局部变量存储在 .data 段或 .bss 段中,这些变量会在程序终止时自动调用其析构函数。

静态对象存储在.data区,程序退出时无论析构函数是否私有,都可以正常析构。静态对象的生命周期由编译器和运行时环境自动管理,而与析构函数的访问权限无关。编译器有权访问类的所有成员函数,包括私有和保护的成员。私有成员的访问控制是针对于用户代码(非类内部代码)和类外部函数而言的,而不是编译器本身。

class Singleton4{
public:
    static Singleton4* getInstance(){
      static Singleton4 instance{};
      return &instance;
    }
    Singleton4(const Singleton4&) = delete;
    Singleton4(Singleton4&&) = delete;
    Singleton4& operator=(const Singleton4&) = delete;
    Singleton4& operator=(Singleton4&&) = delete;
private:
    Singleton4(){std::cout << "Singleton4()" << std::endl;}    // 私有构造
    ~Singleton4() {std::cout << "~Singleton4()" << std::endl ;}
}

使用call_once

class Singleton5
{
public:
    static Singleton5* getInstance()
    {
        std::call_once(_initFlag, [](){
             auto del = [](Singleton5* ptr){delete ptr;};
            _instance = new Singleton5();
        });
        return _instance;
    }

    Singleton5(const Singleton5&) = delete;
    Singleton5(Singleton6 &&) = delete;
    Singleton5& operator=(const Singleton5&) = delete;
    Singleton5& operator=(Singleton5&&) = delete;
private:
    Singleton5(){std::cout << "Singleton5()" << std::endl;}    // 私有构造
    ~Singleton5() {std::cout << "~Singleton5()" << std::endl ;}
    static Singleton5* _instance;
    static std::once_flag _initFlag;
};

std::once_flag Singleton5::_initFlag;
std::shared_ptr<Singleton5> Singleton5::_instance = nullptr;