单例模式 Singleton Pattern

259 阅读3分钟

单例模式 Singleton Pattern

定义

保证一个类仅有一个实例, 并提供一个该实例的全局访问点

这个其实很好理解就是字面意思哈哈哈, 总算有个好理解的了

版本1

class Singleton {
public:
    static Singleton* GetInstance() {
        if (_instance == nullptr) {
            _instance = new Singleton();
        }
        return _instance;
    }

private:
    Singleton() {} // 构造 为了保证全局只有一个
    ~Singleton() {} // 避免直接被删除
    Singleton(const Singleton& clone) {} // 拷贝构造, 禁止拷贝构造
    Singleton& operator=(const Singleton&) {}
    static Singleton* _instance;
};

Singleton* Singleton::_instance = nullptr; // 静态成员需要初始化

有啥子问题?

  1. 首先说一下, _instance是通过new Singleton()生成的所以是存放在上的, 然后_instance又是static的; 当单例随着进程退出的时候, _instance的析构函数没有被调用, 所以存在内存泄漏的问题

  2. 还有线程安全问题

版本2


#include <stdlib.h>

class Singleton {
public:
    static Singleton* GetInstance() {
        if (_instance == nullptr) {
            _instance = new Singleton();
            atexit(Destructor);
        }
        return _instance;
    }
    ~Singleton() {}

private:
    static void Destructor() {
        if (nullptr != _instance) { // 静态成员变量, 是所有类对象共享的
            delete _instance;
            _instance = nullptr;
        }
    }
    Singleton();
    Singleton(const Singleton& cpy);
    Singleton& operator=(const Singleton&) {}
    static Singleton* _instance;
};

Singleton* Singleton::_instance = nullptr; // 静态成员需要初始化

没有考虑到多线程

版本3

#include <mutex>

class Singleton { // 懒汉式
public:
    static Singleton* GetInstance() {
        // std::lock_guard<std::mutex> lock(_mutex); // 3.1 切换现场
        if (_instance == nullptr) {
            std::lock_guard<std::mutex> lock(_mutex); // 3.2 
            if (_instance == nullptr) {
                // 1. 分配内存
                // 2. 调用构造函数
                // 3. 返回指针
                // 4. 多线程环境下, CPU会优化 reorder, 执行顺序会变成 1 3 2, 可能没有完成初始化就返回了, 会出现野指针
                _instance = new Singleton();
                std::atexit(Destructor);
            }
        }
        return _instance;
    }

private:
    static void Destructor() {
        if (nullptr != _instance) {
            delete _instance;
            _instance = nullptr;
        }
    }
    Singleton(){}
    Singleton(const Singleton& cpy) {}
    Singleton& operator=(const Singleton&) {}
    static Singleton* _instance;
    static std::mutex _mutex;
};

Singleton* Singleton::_instance = nullptr; // 静态成员需要初始化
std::mutex Singleton::_mutex; // 互斥锁初始化

增加双检测的时候能看好的保证线程安全和执行效率, 使用了3.1版本和没有做双检测都保证不了

但是要考虑一下多线程环境下, CPU reorder可能会导致初始化对象的时候问题, 导致产生了一个空的单例对象

版本4

#include <mutex>
#include <atomic>

class Singleton {
public:
    static Singleton* GetInstance() {
        Singleton* tmp = _instance.load(std::memory_order_relaxed);
        std::atomic_thread_fence(std::memory_order_acquire); // 获取内存屏障 // 会让new按顺序操作, 不会让new重排
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lock(_mutex);
            tmp = _instance.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton;
                std::atomic_thread_fence(std::memory_order_release); // 释放内存屏障
                _instance.store(tmp, std::memory_order_relaxed);
                std::atexit(Destructor);
            }
        }
        return tmp;
    }

private:
    static void Destructor() {
        Singleton* tmp = _instance.load(std::memory_order_relaxed);
        if (nullptr != tmp) {
            delete tmp;
        }
    }
    Singleton(){}
    Singleton(const Singleton&) {};
    Singleton& operator=(const Singleton&) {}
    static std::atomic<Singleton*> _instance;
    static std::mutex _mutex;
};

std::atomic<Singleton*> Singleton::_instance;
std::mutex Singleton::_mutex;

// g++ Singleton.cpp -o Singleton -std=c++11

这个就能解决版本3的问题了, 就不会出现这个reorder new的问题

版本5

class Singleton {
public:
    ~Singleton(){}
    static Singleton& GetInstance() {
        static Singleton instance;
        return instance;
    }
private:
    Singleton(){}
    Singleton(const Singleton&) {}
    Singleton& operator=(const Singleton&) {}
};

来来来膜拜一下大佬的代码, 这个版本就是effective作者自己写的

C++11 static的新特性: 如果当变量在初始化的时候, 并发同时进入声明语句, 并发线程将会阻塞等待初始化结束

  1. 利用静态局部变量特性, 延迟加载
  2. 利用静态全局变量特性, 系统自动回收内存, 自动调用析构函数
  3. 静态全局变量初始化时, 没有new操作带来的cpu指令reorder操作

但是这还是不够完美, 他的可拓展性太差

版本6

template<typename T>

class Singleton {

public:

    static T& GetInstance() {

        static T instance; // 这里要初始化DesignPattern, 需要调用DesignPattern构造函数, 同时会调用父类的构造函数

    }

protected:

    virtual ~Singleton() {}

    Singleton() {} // protected修饰构造函数, 才能让别人继承

    Singleton(const Singleton&) {}

    Singleton& operator=(const Singleton&) {}

};


class DesignPattern : public Singleton<DesignPattern> {

    friend class Singleton<DesignPattern>; // friend能让Singleton<T>访问到DesignPattern构造函数

public:

    ~DesignPattern() {}

private:

    DesignPattern(const DesignPattern&) {}

    DesignPattern& operator=(const DesignPattern&) {}

};

这样就解决了方案5的问题