1、动机
对于系统中的某些类来说,只有一个实例化对象很重要。
- 普通类的对象,既可以叫对象,也可以叫实例化对象(实例)
- 抽象类是不可以被实例化的,那它的对象就不能叫实例化对象,只能叫对象。
如何保证一个类只有一个实例化对象且这个实例化对象易于被访问呢?
- 定义一个全局变量可以确保实例化对象随时都可以被访问,但不能防止我们实例化多个对象。
- 好的解决办法:让类自身负责保存它的唯一实例化对象。这个类可以保证没有其他实例化对象被创建,并且它可以提供一个访问该实例化对象的方法。
2、实现方式
a)在类中添加一个私有静态成员变量用于保存单例实例化对象。
b)声明一个公有静态构建方法用于获取单例实例化对象。
c)在静态方法中实现"延迟初始化"。该方法会在首次被调用时创建一个实例化对象,并将其存储在静态成员变量中。此后该方法每次被调用时都返回该实例。
d)将类的构造函数设为私有。类的静态方法仍能调用构造函数,但是其他对象不能调用。
e)检查客户端代码,将对单例的构造函数的调用替换为对其静态构建方法的调用。
3、类图
4、代码
4.1饿汉模式
// Singleton.h
#ifndef SINGLETON_H_
#define SINGLETON_H_
class Singleton
{
public:
static Singleton* GetInstance()
{
return instance_;
}
private:
Singleton(){}
private:
static Singleton* instance_;
};
#endif //SINGLETON_H_
// Singleton.cpp
#include "Singleton.h"
Singleton* Singleton::instance_ = new Singleton();
//main.cpp
#include <iostream>
#include "Singleton.h"
int main() {
Singleton *s1 = Singleton::GetInstance();
Singleton *s2 = Singleton::GetInstance();
std::cout << "s1地址: " << s1 << std::endl;
std::cout << "s2地址: " << s2 << std::endl;
return 0;
}
4.2懒汉模式
4.2.1 线程不安全懒汉模式
// Singleton.h
#ifndef SINGLETON_H_
#define SINGLETON_H_
class Singleton
{
public:
static Singleton* GetInstance()
{
if(instance_ == nullptr)
{
instance_ = new Singleton();
}
return instance_;
}
private:
Singleton(){}
private:
static Singleton* instance_;
};
#endif //SINGLETON_H_
// Singleton.cpp
#include "Singleton.h"
//静态变量instance_的初始化不能放在头文件,不然多个文件#include "Singleton.h"会出现重复定义问题
Singleton* Singleton::instance_ = nullptr;
//main.cpp
#include<iostream>
#include"Singleton.h"
int main()
{
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance();
std::cout<<"s1的地址为:"<<s1<<std::endl;
std::cout<<"s2的地址为:"<<s2<<std::endl;
return 0;
}
4.2.2 线程安全懒汉模式
// Singleton.h
#ifndef SINGLETON_H_
#define SINGLETON_H_
#include<mutex>
class Singleton
{
public:
static Singleton* GetInstance()
{
if(instance_ == nullptr)
{
//加锁保证多个线程并发调用getInstance()时只会创建一个实例
m_mutex_.lock();
if(instance_ == nullptr)
{
instance_ = new Singleton();
}
m_mutex_.unlock();
}
return instance_;
}
private:
Singleton(){}
private:
static Singleton* instance_;
static std::mutex m_mutex_;
};
#endif //SINGLETON_H_
// Singleton.cpp
#include "Singleton.h"
//静态变量instance_的初始化不能放在头文件,不然多个文件#include "Singleton.h"会出现重复定义问题
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::m_mutex_;
//main.cpp
#include<iostream>
#include "Singleton.h"
int main()
{
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance();
std::cout<<"s1的地址为:"<<s1<<std::endl;
std::cout<<"s2的地址为:"<<s2<<std::endl;
return 0;
}
4.3 Meyers’ Singleton
Meyers’ Singleton是Scott Meyers提出的C++单例的推荐写法。它将单例对象作为局部static对象定义在函数内部:
#ifndef SINGLETON_H_
#define SINGLETON_H_
class Singleton {
public:
static Singleton& GetInstance() {
static Singleton instance;
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() {}
};
#endif // SINGLETON_H_
优点:
- 解决了普通单例模式全局变量初始化依赖(C++只能保证在同一个文件中声明的static遍历初始化顺序和其遍历声明的顺序一致,但是不能保证不同文件中static遍历的初始化顺序)
缺点:
- 需要C++11支持(C++11保证static成员初始化的线程安全)
- 性能问题(同懒汉模式一样,每次调用
GetInstance()
方法时需要判断局部static变量是否已经初始化,如果没有初始化就会进行初始化,这个判断逻辑会消耗一点性能)