设计模式(C++)——单例模式

单例模式

这是我平时用的比较多的模式。所谓单例模式,就是说我希望全局只有一个实例。比如说日志模块,整个系统我只希望有一个日志模块,每个模块在打印日志的时候我希望他们用到的都是同一个实例,而不是各自为政,打印到了不同的日志中。此时就需要用到单例模式。

单例模式在实现时,有懒汉模式与饿汉模式两种方式。其中懒汉模式又分线程安全与不安全两种。

1. 懒汉模式,线程不安全

懒汉模式,就是指你不调用接口,那我就永远不真正生成实例,只有到真正用的时候才会真正地生成实例。

class SingletonClass {
private:
    SingletonClass() { cout << "constructor" << endl; };
    ~SingletonClass() { cout << "destructor" << endl; }
public:
    SingletonClass(const SingletonClass&) = delete;
    SingletonClass& operator = (const SingletonClass&) = delete;
    
    static SingletonClass& getInstance() {
        static SingletonClass instance;
        return instance;
    }
};

这里,我使用的是函数内static变量的形式,也可以使用类内static成员变量:

class SingletonClass {
private:
    static SingletonClass* instance;
    SingletonClass() { cout << "constructor" << endl; };
    ~SingletonClass() { cout << "destructor" << endl; }
public:
    SingletonClass(const SingletonClass&) = delete;
    SingletonClass& operator = (const SingletonClass&) = delete;
    
    static SingletonClass& getInstance() {
        if(nullptr == instance)
            instance = new SingletonClass;
        return *instance;
    }
    static void releaseOldInstance(){
        delete instance;
    }
};
SingletonClass* SingletonClass::instance = nullptr;

后面这个示例,我改用了指针来存储实例,好处在于可以在适当的时候释放单例模式的实例,而不必使其生命周期与整个程序一样长;而且不必占用栈空间。劣势就在于,必须选择正确的释放时机(或者干脆不释放,但是我觉得没有deletenew是不完整的)。

这里注意:

两个方式中getInstance()函数的返回值,我都选择了引用的形式,而非网上很多例程中的指针形式。这是因为我认为指针形式在使用时会很让人迷惑:调用者在使用完毕后到底要不要释放指针呢? C语言没有垃圾回收机制,因此在使用指针时必须异常谨慎。所以指针形式的返回值就会要求调用者在使用时需要知道更多的信息。同时,指针形式的返回值也会带来潜在的威胁:如果调用者误操作,将指针释放了呢?而以引用形式返回,那么除非调用者故意搞事情,否则很难造成威胁。(什么?你问我他就是搞事情怎么办?这……那我还能怎么办?拖下去砍了啊!祭天!)

2. 懒汉模式,线程安全

class SingletonClass {
private:
    static SingletonClass* instance;
    static std::mutex lock;
    SingletonClass() { cout << "constructor" << endl; };
    ~SingletonClass() { cout << "destructor" << endl; }
public:
    SingletonClass(const SingletonClass&) = delete;
    SingletonClass& operator = (const SingletonClass&) = delete;
    
    static SingletonClass& getInstance() {
        if(nullptr == instance)
        {
            std::lock_guard<std::mutex> l(lock);
            if(nullptr == instance)
                instance = new SingletonClass;
        }
        return *instance;
    }
};
SingletonClass* SingletonClass::instance = nullptr;
std::mutex SingletonClass::lock;

这里使用双重检测锁机制来保证线程安全性。
这里我没有写releaseOldInstance是因为释放时机也要选择恰当,否则也不是线程安全的。如果想保证恰当的释放时机,我们可能需要用到一个新的类以及C++14中的读写锁才可以:

template<class T>
class SingletonInstancePtr;

class SingletonClass {
private:
    int x = 0;
    SingletonClass() { cout << "constructor" << endl; };
    ~SingletonClass() { cout << "destructor" << endl; }
protected:
    static SingletonClass* instance;
    static std::shared_mutex lock;
    static SingletonClass* getInstance() {
        if (nullptr == instance)
        {
            std::lock_guard<std::shared_mutex> l(lock);
            if (nullptr == instance)
                instance = new SingletonClass();
        }
        return instance;
    }
    friend class SingletonInstancePtr<SingletonClass>;
public:
    SingletonClass(const SingletonClass&) = delete;
    SingletonClass& operator = (const SingletonClass&) = delete;

    static void releaseOldInstance()
    {
        if (nullptr != instance)
        {
            std::lock_guard<std::shared_mutex> l(lock);
            if (nullptr != instance)
            {
                delete instance;
                instance = nullptr;
            }
        }
    }
    int getX() { return x; }
    int addX() { return ++x; }
};
SingletonClass* SingletonClass::instance = nullptr;
std::shared_mutex SingletonClass::lock;

template<class T>
class SingletonInstancePtr
{
private:
    T* instance = nullptr;
    std::shared_lock<std::shared_mutex> lock;
public:
    SingletonInstancePtr()
    {
        do {
            instance = T::getInstance();
            auto tmp = std::shared_lock<std::shared_mutex>(T::lock);
            if (T::instance != nullptr)
            {
                lock = std::move(tmp);
                break;
            }
        } while (true);
    }
    T* operator -> ()
    {
        return instance;
    }
};

int main()
{
    {
        SingletonInstancePtr<SingletonClass> instance;
        cout << instance->getX() << endl;
        cout << instance->addX() << endl;
    }
    SingletonClass::releaseOldInstance();
}

这里我用了一个模板类,以便于如果有多个单例模式,可以复用这个模板。
SingletonInstancePtr模板类与读写锁保证了,当有人在使用SingletonClass时,不能释放相应的资源。
当然这里还可以继续改进。这里没有设置获取锁等待的时间,可能会造成程序锁死。为了保证不卡死,可以设置等待时间,或者尝试次数等机制。

3. 饿汉模式

饿汉模式,就是在程序加载的时候,就直接初始化好,自然也就不存在线程安全不安全的问题了。

class SingletonClass {
private:
    static SingletonClass instance;
    SingletonClass() { cout << "constructor" << endl; };
    ~SingletonClass() { cout << "destructor" << endl; }

public:
    SingletonClass(const SingletonClass&) = delete;
    SingletonClass& operator = (const SingletonClass&) = delete;

    static SingletonClass& getInstance() {
        return instance;
    }
};

SingletonClass SingletonClass::instance;

这里我还是用的非指针形式。如果类占用内存过大,或者对于栈空间有要求,那么也可以像之前一样使用指针的形式,只要在初始化时,不要将instance指针初始化为nullptr,而是直接new一个实例即可。

原创文章 34 获赞 41 访问量 5927

猜你喜欢

转载自blog.csdn.net/qq_44844115/article/details/106138337