定义

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。

结构

  • 单例模式包含如下角色:
  • Singleton:单例

Singleton

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

单例模式一:懒汉式(线程不安全,使用时创建)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class QSingleton
{
public:
static QSingleton* instance()
{
if (m_pInstance == NULL)
m_pInstance = new QSingleton();
return m_pInstance;
}

static void Release()
{
if (m_pInstance != NULL)
{
delete m_pInstance;
m_pInstance = NULL;
}
}
private:
QSingleton(){}
QSingleton(const QSingleton&){}
QSingleton& operator==(const QSingleton&){}
private:
static QSingleton* m_pInstance;
};
// 静态成员变量需要在类体的外面进行初始化
QSingleton* QSingleton::m_pInstance = NULL;

缺点:

  1. 每次都得判断m_pInstance是否为空,增加了程序开销,而饿汉模式没有此问题。
  2. 需要手动调用Release函数释放静态成员变量分配内存,上面的饿汉模式也有此问题。针对此问题我们可以通过智能指针来避免。
  3. 不是线程安全的,要想在多线程环境下安全使用,就需要在程序一开始处,其他线程还未创建时,调用一次instance函数,但这样就抛弃了懒汉模式延迟加载的优点。饿汉模式因为在程序一开始就创建了对象,因此是线程安全的。

单例模式二:饿汉式(线程安全,main之前创建,用不用都提前创建)
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class QSingleton
{
public:
static QSingleton* instance()
{
return m_pInstance;
}

static void Release()
{
if (m_pInstance != NULL)
{
delete m_pInstance;
m_pInstance = NULL;
}
}
QSingleton(){}

private:
QSingleton(const QSingleton&){}
QSingleton& operator==(const QSingleton&){}
private:
static QSingleton* m_pInstance;
};

// 直接初始化静态成员变量
QSingleton* QSingleton::m_pInstance = new QSingleton;

缺点:
1.内存消耗

单例模式三:通过智能指针管理(线程安全 通过加锁保证了m_pInstance创建的唯一性)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class QSingleton
{
public:
static QSharedPointer<QSingleton>& instance()
{
QMutexLocker mutexLocker(&m_Mutex);
if (m_pInstance.isNull())
{
m_pInstance = QSharedPointer<QSingleton>(new QSingleton());
}
return m_instance;
}
private:
QSingleton(){}
QSingleton(const QSingleton&){}
QSingleton& operator==(const QSingleton&){}
private:
static QMutex m_Mutex;
static QSharedPointer<QSingleton> m_pInstance;
};

QMutex QSingleton::m_Mutex;
QSharedPointer<QSingleton> QSingleton::m_pInstance;

缺点:
通过智能指针来管理成员变量,保证了在程序退出时,自动释放内存,通过加锁保证了m_pInstance创建的唯一性,但是因为程序每次调用instance就需要先加锁,大大增加了程序开销

改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class QSingleton
{
public:
static QSharedPointer<QSingleton>& instance()
{
if (m_pInstance.isNull())
{
QMutexLocker mutexLocker(&m_Mutex);
if (m_pInstance.isNull())
m_pInstance = QSharedPointer<QSingleton>(new QSingleton());
}
return m_pInstance;
}
private:
QSingleton(){}
QSingleton(const QSingleton&){}
QSingleton& operator==(const QSingleton&){}
private:
static QMutex m_Mutex;
static QSharedPointer<QSingleton> m_pInstance;
};

QMutex QSingleton::m_Mutex;
QSharedPointer<QSingleton> QSingleton::m_pInstance;

单例模式四:Meyers模式(线程不安全)
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
class QSingleton
{
public:
static QSingleton& instance()
{
static QSingleton qinstance;
return qinstance;
}
private:
QSingleton(){}
QSingleton(const QSingleton&){}
QSingleton& operator==(const QSingleton&){}
};

总结

  • 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。
  • 单例模式只包含一个单例角色:在单例类的内部实现只生成一个实例,同时它提供一个静态的工厂方法,让客户可以使用它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有。
  • 单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例类拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法。该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。
  • 单例模式的主要优点在于提供了对唯一实例的受控访问并可以节约系统资源;其主要缺点在于因为缺少抽象层而难以扩展,且单例类职责过重。
  • 单例模式适用情况包括:系统只需要一个实例对象;客户调用类的单个实例只允许使用一个公共访问点。

参考资料:https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/singleton.html