目录
1. 懒汉式单例
懒汉式单例在第一次被使用的时候才会进行实例化。
1.1 单线程的懒汉式单例
#include <iostream>
using namespace std;
class Singleton {
private:
Singleton() { cout << "Construct called!" << endl; }
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
~Singleton() { cout << "Destructor called!" << endl; }
static Singleton* getInstance() {
if (nullptr == m_instance_ptr) {
m_instance_ptr = new Singleton;
}
return m_instance_ptr;
}
private:
static Singleton* m_instance_ptr;
};
Singleton* Singleton::m_instance_ptr = nullptr;
int main()
{
Singleton* instance = Singleton::getInstance();
Singleton* instance2 = Singleton::getInstance();
return 0;
}
上面这个单线程的单例问题比较多,首先会存在内存泄漏,然后再多线程环境下,可能有多个线程同时判断“if (nullptr == m_instance_ptr)”为真,然后进入下面的语句,可能导致创建了多个实例。
对于内存泄漏的问题,我们进行如下修改:
class Singleton {
private:
Singleton() { cout << "Construct called!" << endl; }
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
//Singleton退出时会自动析构gc,调用GC的析构函数,
//我们在GC的析构函数中释放m_instance_ptr
class GC {
public:
~GC()
{
if (m_instance_ptr != nullptr) {
delete m_instance_ptr;
m_instance_ptr = nullptr;
}
}
};
public:
~Singleton() { cout << "Destructor called!" << endl; }
static Singleton* getInstance() {
if (nullptr == m_instance_ptr) {
m_instance_ptr = new Singleton;
}
return m_instance_ptr;
}
private:
static Singleton* m_instance_ptr;
static GC gc;
};
Singleton* Singleton::m_instance_ptr = nullptr;
Singleton::GC Singleton::gc;
在Singleton类中增加了一个GC嵌套类,并声明了一个static类型的GC的变量。当Singleton结束的时候会调用GC类的析构函数,而在其析构函数中会使用delete释放m_instance_ptr指向的内存,由此可以避免内存泄漏。
但是该方法在多线程环境下仍然是不安全的。
1.2 多线程的懒汉式单例
在多线程情况下,需要对共享资源进行加锁,以保证其互斥访问。我们可以再结合C++2.0的智能指针,将上述代码修改一下。
#include <iostream>
#include <mutex>
#include <memory>
using namespace std;
class Singleton {
private:
Singleton() { cout << "Construct called!" << endl; }
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
typedef shared_ptr<Singleton> Ptr;
~Singleton() { cout << "Destructor called!" << endl; }
static Ptr getInstance() {
if (nullptr == m_instance_ptr) {
lock_guard<mutex> lk(m_mutex);
if (nullptr == m_instance_ptr) {
m_instance_ptr = Ptr(new Singleton);
}
}
return m_instance_ptr;
}
private:
static Ptr m_instance_ptr;
static mutex m_mutex;
};
Singleton::Ptr Singleton::m_instance_ptr = nullptr;
mutex Singleton::m_mutex;
int main()
{
Singleton::Ptr instance = Singleton::getInstance();
Singleton::Ptr instance2 = Singleton::getInstance();
}
使用智能指针shard_ptr,解决了内存泄漏的问题,使用互斥量mutex和锁管理器lock_guard实现了线程安全。
1.3 使用静态局部变量的多线程懒汉式单例
C++2.0保证了变量在初始化的时候如果并发同时进入了声明语句,并发线程会阻塞等待声明结束。
这可以保证并发线程在获取静态局部变量的时候一定是初始化过的。
#include <iostream>
using namespace std;
class Singleton {
private:
Singleton() { cout << "Construct called!" << endl; }
public:
~Singleton() { cout << "Destructor called!" << endl; }
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& getInstance() {
static Singleton instance;
return instance;
}
};
int main()
{
Singleton& instance = Singleton::getInstance();
Singleton& instance2 = Singleton::getInstance();
}
2. 饿汉式单例
饿汉式单例,在加载时就进行初始化,而不是第一次引用时才进行初始化。因为是加载时就进行初始化,因此不存在多线程下创建多个实例的可能。
#include <iostream>
using namespace std;
class Singleton {
private:
Singleton() { cout << "Construct called!" << endl; }
Singleton(Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
//Singleton退出时会自动析构gc,调用GC的析构函数,
//我们在GC的析构函数中释放m_instance_ptr
class GC {
public:
~GC()
{
if (m_instance_ptr != nullptr) {
delete m_instance_ptr;
m_instance_ptr = nullptr;
}
}
};
public:
static Singleton* getInstance() {
return m_instance_ptr;
}
~Singleton() { cout << "Destructor called!" << endl; }
private:
static Singleton* m_instance_ptr;
static GC gc;
};
Singleton* Singleton::m_instance_ptr = new Singleton;
Singleton::GC Singleton::gc;
int main()
{
Singleton* instance = Singleton::getInstance();
Singleton* instance2 = Singleton::getInstance();
return 0;
}
这里也可以使用智能指针来防止内存泄漏。
参考资料:C++ 单例模式总结与剖析