一句话来说就是,某个类只能有一个实例,提供一个全局的访问点。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。
单例的种类有哪些懒汉式和饿汉式,它们之间有什么区别?
懒汉式:指全局的单例实例在第一次被使用时构建。
饿汉式:全局的单例实例在类装载(ClassLoader)时构建。(饿汉式单例性能优于懒汉式单例)
懒汉式与饿汉式区别:
- #include
- #include
- #include
-
- /// 加锁的懒汉式实现 //
-
- class Singleton
- {
-
- public:
- // 获取单实例对象
- static Singleton* GetInstance();
-
- //释放单实例,进程退出时调用
- static void deleteInstance();
-
- // 打印实例地址
- void Print();
-
- private:
- // 将其构造和析构成为私有的, 禁止外部构造和析构
- Singleton();
- ~Singleton();
-
- // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
- Singleton(const Singleton& signal);
- const Singleton& operator=(const Singleton& signal);
-
- private:
- // 唯一单实例对象指针
- static Singleton* m_SingleInstance;
- static std::mutex m_Mutex;
- };
-
- //初始化静态成员变量
- Singleton* Singleton::m_SingleInstance = nullptr;
- std::mutex Singleton::m_Mutex;
-
- // 注意:不能返回指针的引用,否则存在外部被修改的风险!
- Singleton* Singleton::GetInstance()
- {
-
- // 这里使用了两个 if 判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
- // 避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
- if (m_SingleInstance == nullptr)
- {
- std::unique_lock
lock(m_Mutex) ; // 加锁 - if (m_SingleInstance == nullptr)
- {
- volatile auto temp = new (std::nothrow) Singleton();
- m_SingleInstance = temp;
- }
- }
-
- return m_SingleInstance;
- }
-
- void Singleton::deleteInstance()
- {
- std::unique_lock
lock(m_Mutex) ; // 加锁 - if (m_SingleInstance)
- {
- delete m_SingleInstance;
- m_SingleInstance = nullptr;
- }
- }
-
- void Singleton::Print()
- {
- std::cout << "我的实例内存地址是:" << this << std::endl;
- }
-
- Singleton::Singleton()
- {
- std::cout << "构造函数" << std::endl;
- }
-
- Singleton::~Singleton()
- {
- std::cout << "析构函数" << std::endl;
- }
-
- int main()
- {
- Singleton* pSingleton1 = Singleton::GetInstance();
- pSingleton1->Print();
-
- Singleton* pSingleton2 = Singleton::GetInstance();
- pSingleton2->Print();
- }
- #include
- #include
- #include
-
- class Singleton {
-
- public:
-
- static std::shared_ptr
GetInstance() ; -
- void Print() {
- std::cout << "我的实例内存地址是:" << this << std::endl;
- }
-
- ~Singleton() {
- std::cout << "__PRETTY_FUNCTION__" << std::endl;
- }
-
- private:
-
- Singleton() {
- std::cout << "__PRETTY_FUNCTION__" << std::endl;
- }
- };
-
- static std::shared_ptr
singleton = nullptr; - static std::mutex singletonMutex;
-
- std::shared_ptr
Singleton::GetInstance() { - if (singleton == nullptr) {
- std::unique_lock
lock(singletonMutex) ; - if (singleton == nullptr) {
- singleton = std::shared_ptr
(new Singleton()); - }
- }
- return singleton;
- }
-
- int main()
- {
- std::shared_ptr
pSingleton1 = Singleton::GetInstance(); - pSingleton1->Print();
-
- std::shared_ptr
pSingleton2 = Singleton::GetInstance(); - pSingleton2->Print();
- }
- #include
- #include
- #include
-
- /// 内部静态变量的懒汉实现 //
-
- class Singleton
- {
-
- public:
- // 获取单实例对象
- static Singleton& GetInstance();
-
- // 打印实例地址
- void Print();
-
- private:
- // 禁止外部构造
- Singleton();
-
- // 禁止外部析构
- ~Singleton();
-
- // 禁止外部拷贝构造
- Singleton(const Singleton& single) = delete;
-
- // 禁止外部赋值操作
- const Singleton& operator=(const Singleton& single) = delete;
- };
-
- Singleton& Singleton::GetInstance()
- {
- /**
- * 局部静态特性的方式实现单实例。
- * 静态局部变量只在当前函数内有效,其他函数无法访问。
- * 静态局部变量只在第一次被调用的时候初始化,也存储在静态存储区,生命周期从第一次被初始化起至程序结束止。
- */
- static Singleton single;
- return single;
- }
-
- void Singleton::Print()
- {
- std::cout << "我的实例内存地址是:" << this << std::endl;
- }
-
- Singleton::Singleton()
- {
- std::cout << "构造函数" << std::endl;
- }
-
- Singleton::~Singleton()
- {
- std::cout << "析构函数" << std::endl;
- }
-
-
- int main()
- {
- Singleton& pSingleton1 = Singleton::GetInstance();
- pSingleton1.Print();
-
- Singleton& pSingleton2 = Singleton::GetInstance();
- pSingleton2.Print();
- }
注:静态局部变量的懒汉单例(C++11线程安全)
- #include
- #include
- #include
- // 饿汉实现 /
-
- class Singleton
- {
- public:
- // 获取单实例
- static Singleton* GetInstance();
-
- // 释放单实例,进程退出时调用
- static void deleteInstance();
-
- // 打印实例地址
- void Print();
-
- private:
- // 将其构造和析构成为私有的, 禁止外部构造和析构
- Singleton();
- ~Singleton();
-
- // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
- Singleton(const Singleton& signal);
- const Singleton& operator=(const Singleton& signal);
-
- private:
- // 唯一单实例对象指针
- static Singleton* g_pSingleton;
- };
-
- // 代码一运行就初始化创建实例 ,本身就线程安全
- Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton();
-
- Singleton* Singleton::GetInstance()
- {
- return g_pSingleton;
- }
-
- void Singleton::deleteInstance()
- {
- if (g_pSingleton)
- {
- delete g_pSingleton;
- g_pSingleton = nullptr;
- }
- }
-
- void Singleton::Print()
- {
- std::cout << "我的实例内存地址是:" << this << std::endl;
- }
-
- Singleton::Singleton()
- {
- std::cout << "构造函数" << std::endl;
- }
-
- Singleton::~Singleton()
- {
- std::cout << "析构函数" << std::endl;
- }
-
- int main()
- {
- Singleton* pSingleton1 = Singleton::GetInstance();
- pSingleton1->Print();
-
- Singleton* pSingleton2 = Singleton::GetInstance();
- pSingleton2->Print();
- }
使用 C++11 std::call_once 实现单例(C++11线程安全)
- #include
- #include
- #include
-
- class Singleton {
- public:
- static std::shared_ptr
GetInstance() ; -
- void Print() {
- std::cout << "我的实例内存地址是:" << this << std::endl;
- }
-
- ~Singleton() {
- std::cout << "__PRETTY_FUNCTION__" << std::endl;
- }
-
- private:
- Singleton() {
- std::cout << "__PRETTY_FUNCTION__" << std::endl;
- }
- };
-
- static std::shared_ptr
singleton = nullptr; - static std::once_flag singletonFlag;
-
- std::shared_ptr
Singleton::GetInstance() { - std::call_once(singletonFlag, [&] {
- singleton = std::shared_ptr
(new Singleton()); - });
- return singleton;
- }
-
- int main()
- {
- std::shared_ptr
pSingleton1 = Singleton::GetInstance(); - pSingleton1->Print();
-
- std::shared_ptr
pSingleton2 = Singleton::GetInstance(); - pSingleton2->Print();
- }
懒汉式的线程安全问题:
线程安全的问题,当多线程获取单例时有可能引发竞态条件:第一个线程在if中判断 m_pInstance是空的,于是开始实例化单例;同时第2个线程也尝试获取单例,这个时候判断m_pInstance还是空的,于是也开始实例化单例;这样就会实例化出两个对象,这就是线程安全问题的由来; 解决办法:加锁。内存泄漏. 注意到类中只负责new出对象,却没有负责delete对象,因此只有构造函数被调用,析构函数却没有被调用;因此会导致内存泄漏。解决办法: 使用共享指针;由于懒汉模式存在多线程的安全问题,因此会衍生出线程安全的懒汉式其中一种方式就是使用智能指针和线程锁另外一种方式是使用局部静态变量。这种方法又叫做 Meyers' SingletonMeyer's的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。这是最推荐的一种单例实现方式:通过局部静态变量的特性保证了线程安全 , 不需要使用共享指针,代码简洁;注意在使用的时候需要声明单例的引用 Single& 才能获取对象。当然,C++11 后面出现std::call_once 就是解决这个问题,但是没有magic static 方便,所以推荐magic static的使用方法 。
2.5 万字详解:23 种设计模式 - 知乎 (zhihu.com)
【C++】C++ 单例模式总结(5种单例实现方法)_单例模式c++实现-CSDN博客
设计模式——单例模式(懒汉式与饿汉式)详解_懒汉式饿汉式是啥设计模式啊?-CSDN博客