单例模式

 1.单例模式简介

单例模式(Singleton Pattern)是最简单的设计模式之一,其目的是保证当前程序在生命周期内仅能创建唯一一个实例,被整个进程空间共享。

使用场景:1)有些类用于提供跨模块传数据的功能,或者全局计数功能等,必须保证有且仅有唯一实例,否则会导致严重错误,无法完成一起功能;

              2)为了提高效率。系统中某些类只提供一个实例即可,比如存取配置信息的类,提供一个即可完成功能,读取、修改方便。

 

2.单例模式实现

2.1 双检查锁单例模式

#include <iostream>
#include <mutex>  // mutex

class Singleton {
public:
    ~Singleton() {
        std::cout << "~Singleton" << std::endl;
    }
    Singleton(Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;
    static Singleton* get_instance() {
        if (m_instance_ptr == nullptr) {
            std::lock_guard<std::mutex> lk(m_mutex);
            if (m_instance_ptr == nullptr) {
                m_instance_ptr = new Singleton;
            }
        }
        return m_instance_ptr;
    }

private:
    Singleton() {
        std::cout << "Singleton" << std::endl;
    }
    static Singleton* m_instance_ptr;
    static std::mutex m_mutex;
};

Singleton* Singleton::m_instance_ptr = nullptr;
std::mutex Singleton::m_mutex;

int main() {
    Singleton* instance = Singleton::get_instance();
    Singleton* instance2 = Singleton::get_instance();
    delete instance;
    return 0;
}

运行结果如下:

Singleton
~Singleton

此实现采用了双检查锁。只有首次调用只有判断指针为空的时候才加锁,避免每次调用 get_instance的方法都加锁,降低开销,减少了竞态产生。

不足之处有两点:

1)只调用new产生实例,无法自动释放实例,需要在外部delete。如果delete的时机不正确,将引起程序崩溃。

2)因为c++编译器在编译过程中会对代码进行优化,所以实际的代码执行顺序可能被打乱,另外因为CPU有一级二级缓存(cache),CPU的计算结果并不是及时更新到内存的,所以在多线程环境,不同线程间共享内存数据存在可见性问题,从而导致使用双检查锁也存在风险。

2.2 双检查锁单例模式改进

针对上述两点,改进后的代码如下:

#include <iostream>
#include <mutex>  // mutex

class Singleton {
public:
    Singleton(Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;
    static Singleton* get_instance() {
        Singleton* tmp = m_instance_ptr.load(std::memory_order_acquire);
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lk(m_mutex);
            tmp = m_instance_ptr.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton;
                m_instance_ptr.store(tmp, std::memory_order_release);
            }
        }
        return tmp;
    }

private:
    class GC {
    public:
        ~GC() {
            delete m_instance_ptr;
            m_instance_ptr = nullptr;
        }
    };

    Singleton() {
        std::cout << "Singleton" << std::endl;
    }
    ~Singleton() {
        std::cout << "~Singleton" << std::endl;
    }
    static std::atomic<Singleton*> m_instance_ptr;
    static std::mutex m_mutex;
    static GC m_gc;
};

std::atomic<Singleton*> Singleton::m_instance_ptr{nullptr};
std::mutex Singleton::m_mutex;
Singleton::GC Singleton::m_gc;

int main() {
    Singleton* instance = Singleton::get_instance();
    Singleton* instance2 = Singleton::get_instance();
//delete instance; 编译报错
return 0; }

针对1)的问题,可在Singleton类中定义一个专门用于清理指针的类GC。静态变量GC析构时,自动调用delete释放Singleton。同时,将Singleton的析构函数设置为私有,防止在外部delete。

对于2),可采用atomic_thread_fence内存栅栏,或atomic原子操作,保证内存分配和双检查锁的判断顺序。示例中采用了较为简单的原子操作。详细示例可参考https://blog.csdn.net/10km/article/details/49777749。也可采用C++11中的std::call_once和std::once_flag,确保初始化只调用一次。

 

2.3 局部静态变量单例模式

最推荐的实现方式如下:

#include <iostream>

class Singleton {
public:
    Singleton(Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(Singleton&&) = delete;
    static Singleton* get_instance() {
        static Singleton s_instanc;
        return &s_instanc;
    }

private:
    Singleton() {
        std::cout << "Singleton" << std::endl;
    }
    ~Singleton() {
        std::cout << "~Singleton" << std::endl;
    }
};


int main() {
    Singleton* instance = Singleton::get_instance();
    Singleton* instance2 = Singleton::get_instance();
    return 0;
}

这种方法又叫做 Meyers' SingletonMeyer's的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:

 这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。

C++局部静态变量,只有首次使用时才进行初始化,因此这也是一种懒汉式。

3.单例模板

3.1 CRTP 奇异递归模板模式实现

#include <iostream>

template<typename T>
class Singleton {
public:
    static T* get_instance() {
        static T instance;
        return &instance;
    }

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

    virtual ~Singleton() {
        std::cout << "destructor called!" << std::endl;
    }
protected:
    Singleton() {
        std::cout << "constructor called!" << std::endl;
    }
};
/********************************************/
// Example:
// 1.friend class declaration is requiered!
// 2.constructor should be private


class DerivedSingle :public Singleton<DerivedSingle> {
    // !!!! attention!!!
    // needs to be friend in order to
    // access the private constructor/destructor
    friend class Singleton<DerivedSingle>;
public:
    DerivedSingle(const DerivedSingle&) = delete;
    DerivedSingle& operator =(const DerivedSingle&) = delete;

    DerivedSingle(DerivedSingle&&) = delete;
    DerivedSingle& operator=(DerivedSingle&&) = delete;
private:
    DerivedSingle() = default;
    ~DerivedSingle() = default;
};

int main(int argc, char* argv[]) {
    DerivedSingle* instance1 = DerivedSingle::get_instance();
    DerivedSingle* instance2 = DerivedSingle::get_instance();
    return 0;
}

以上实现一个单例的模板基类,使用方法如例子所示意,子类需要将自己作为模板参数T 传递给 Singleton<T> 模板; 同时需要将基类声明为友元,这样才能调用子类的私有构造函数。

基类模板的实现要点是:

  1. 构造函数需要是 protected,这样子类才能继承;
  2. 使用了奇异递归模板模式CRTP(Curiously recurring template pattern)
  3. get instance 方法和 2.2.3 的static local方法一个原理。
  4. 在这里基类的析构函数可以不需要 virtual ,因为子类在应用中只会用 Derived 类型,保证了析构时和构造时的类型一致

 

尝试在父类中将几个函数声明为virtual、final,避免子类中重复写。经试验发现行不通:

1)C++中限制,构造函数不可以为虚函数。因此,拷贝构造和移动拷贝构造函数无法采用此方式

2)重载等号操作符,可以声明为虚函数,也可以使用final关键字。但每个类中正规的重载等号操作符函数,是不同的函数,无法禁止继承。验证如下:

#include <iostream>

template<typename T>
class Singleton {
public:
    static T* get_instance() {
        static T instance;
        return &instance;
    }

    Singleton(const Singleton&) = delete;
    virtual Singleton& operator =(const Singleton&) final {
        std::cout << "operator =(const Singleton&)" << std::endl;
        return *this;
    }

    Singleton(Singleton&&) = delete;
    virtual Singleton& operator=(Singleton&&) final {
        std::cout << "operator=(Singleton&&)" << std::endl;
        return *this;
    }
    virtual ~Singleton() {
        std::cout << "destructor called!" << std::endl;
    }
protected:
    Singleton() {
        std::cout << "constructor called!" << std::endl;
    }
};

class DerivedSingle :public Singleton<DerivedSingle> {
    friend class Singleton<DerivedSingle>;
public:
    DerivedSingle(const DerivedSingle&) = delete;
    DerivedSingle& operator =(const DerivedSingle&) {
        std::cout << "operator =(const DerivedSingle&)" << std::endl;
        return *this;
    }
    DerivedSingle(DerivedSingle&&) = delete;
    DerivedSingle& operator=(DerivedSingle&&) {
        std::cout << "operator =(const DerivedSingle&&)" << std::endl;
        return *this;
    }
private:
    DerivedSingle() = default;
    ~DerivedSingle() = default;
};

int main(int argc, char* argv[]) {
    DerivedSingle* instance1 = DerivedSingle::get_instance();
    DerivedSingle* instance2 = DerivedSingle::get_instance();
    *instance1 = *instance1;
    *instance2 = std::move(*instance1);

    return 0;
}

输出结果:

constructor called!
operator =(const DerivedSingle&)
operator =(const DerivedSingle&&)
destructor called!

可以看到,虽然父类中将重载等号操作符函数使用了final关键字修饰,子类中的对应函数不受影响,仍可以正常工作。

做出如下修改:

#include <iostream>

template<typename T>
class Singleton {
public:
    static T* get_instance() {
        static T instance;
        return &instance;
    }

    Singleton(const Singleton&) = delete;

    virtual T& operator =(const T&) final {
        std::cout << "operator =(const Singleton&)" << std::endl;
        return *this;
    }

    Singleton(Singleton&&) = delete;
    virtual T& operator=(T&&) final {
        std::cout << "operator=(Singleton&&)" << std::endl;
        return *this;
    }
    virtual ~Singleton() {
        std::cout << "destructor called!" << std::endl;
    }
protected:
    Singleton() {
        std::cout << "constructor called!" << std::endl;
    }
};

class DerivedSingle :public Singleton<DerivedSingle> {
    friend class Singleton<DerivedSingle>;
public:
    DerivedSingle(const DerivedSingle&) = delete;

    DerivedSingle(DerivedSingle&&) = delete;

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

int main(int argc, char* argv[]) {
    DerivedSingle* instance1 = DerivedSingle::get_instance();
    DerivedSingle* instance2 = DerivedSingle::get_instance();

    return 0;
}

编译器报错:

错误    C2282    “DerivedSingle::operator =”不能重写“Singleton<DerivedSingle>::operator =”
错误    C3248    “Singleton<DerivedSingle>::operator =”: 声明为“final”的函数无法被“DerivedSingle::operator =”重写

即,通过在父类中禁止子类声明重载等号操作符函数和重载移动等号操作符函数的行为,将引起编译错误,导致子类不可用。此结论可作为 https://www.cnblogs.com/yajiu/articles/15435277.html 的补充

至此,通过在父类中添加修饰,避免子类重复声明的各种尝试均告失败。

 

可通过定义宏的方式,减少重复工作,代码如下:

#include <iostream>

#define DELETEDEFAULTFUNCTION(DerivedSingle)      friend class Singleton<DerivedSingle>;\
    DerivedSingle(const DerivedSingle&) = delete;\
    DerivedSingle& operator =(const DerivedSingle&) = delete;\
    DerivedSingle(DerivedSingle&&) = delete;\
    DerivedSingle& operator=(DerivedSingle&&) = delete;\


template<typename T>
class Singleton {
public:
    static T* get_instance() {
        static T instance;
        return &instance;
    }

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

    virtual ~Singleton() {
        std::cout << "destructor called!" << std::endl;
    }
protected:
    Singleton() {
        std::cout << "constructor called!" << std::endl;
    }
};

class DerivedSingle :public Singleton<DerivedSingle> {
    DELETEDEFAULTFUNCTION(DerivedSingle)
private:
    DerivedSingle() = default;
    ~DerivedSingle() = default;
};

int main(int argc, char* argv[]) {
    DerivedSingle* instance1 = DerivedSingle::get_instance();
    DerivedSingle* instance2 = DerivedSingle::get_instance();

    return 0;
}

此代码,经过简单调整,即可作为用于实际的生产环境中。

 

3.2 不需要在子类声明友元的实现方法

在 stackoverflow上, 有大神给出了不需要在子类中声明友元的方法,在这里一并放出;精髓在于使用一个代理类 token,子类构造函数需要传递token类才能构造,但是把 token保护其起来, 然后子类的构造函数就可以是公有的了,这个子类只有 Derived(token)的这样的构造函数,这样用户就无法自己定义一个类的实例了,起到控制其唯一性的作用。代码如下。

#include <iostream>

template<typename T>
class Singleton {
public:
    static T* get_instance() noexcept(std::is_nothrow_constructible<T>::value) {
        static T instance{ token() };
        return &instance;
    }
    virtual ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton& operator =(const Singleton&) = delete;
protected:
    struct token {}; // helper class
    Singleton() noexcept = default;
};


/********************************************/
// Example:
// constructor should be public because protected `token` control the access


class DerivedSingle :public Singleton<DerivedSingle> {
public:
    DerivedSingle(token) {
        std::cout << "destructor called!" << std::endl;
    }

    ~DerivedSingle() {
        std::cout << "constructor called!" << std::endl;
    }
    DerivedSingle(const DerivedSingle&) = delete;
    DerivedSingle& operator =(const DerivedSingle&) = delete;

    DerivedSingle(DerivedSingle&&) = delete;
    DerivedSingle& operator=(DerivedSingle&&) = delete;
};

int main(int argc, char* argv[]) {
    DerivedSingle* instance1 = DerivedSingle::get_instance();
    DerivedSingle* instance2 = DerivedSingle::get_instance();
    return 0;
}

 

模板部分实现来源:https://www.cnblogs.com/sunchaothu/p/10389842.html

posted @ 2021-11-03 00:15  亚九  阅读(121)  评论(0编辑  收藏  举报