所谓的单例模式,就是设计一个类,它在整个程序中只能有一个该类的实例存在,这就是单例模式。
C++实现单例模式的一般方法是将构造函数,拷贝构造函数以及赋值运算符函数声明成private,从而禁止他人创建实例。否则如果上面三者不为私有,那么他人就可以调用上面的三个函数来创建实例,就没法实现单例模式。但是我们总归是要创建一个类的,我们可以提供一个public的静态方法来帮助我们获得这个类唯一的一个实例化对象。
单例模式一般有两种实现模式
在这篇文章中,我们暂且只来讨论饿汉模式
饿汉模式的对象在类产生时候就创建了,一直到程序结束才会去释放。即作为一个单例类实例,它的生存周期和我们的程序一样长。因此该实例对象需要存储在全局数据区,所以肯定需要使用static来修饰,因为类内部的static成员是不属于每个对象的,而是属于整个类的。在加载类的时候,我们的实例对象就产生了。所以对于饿汉模式而言,是线程安全的,因为在线程创建之前实例已经被创建好了。
下面我们可以来模式实现一个饿汉模式的单例类
singleton.hpp
#pragma once
#include
using namespace std;
class Singleton{
private:
Singleton(){
cout << "创建了一个单例对象" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
~Singleton(){
//析构函数我们也需要声明成private的
//因为我们想要这个实例在程序运行的整个过程中都存在
//所以我们不允许实例自己主动调用析构函数释放对象
cout << "销毁了一个单例对象" << endl;
}
static Singleton instance; //这是我们的单例对象,注意这是一个类对象,下面会更改这个类型
public:
static Singleton* getInstance();
};
//下面这个静态成员变量在类加载的时候就已经初始化好了
Singleton Singleton::instance;
Singleton* Singleton::getInstance(){
return &instance;
}
test.cc
#include "singleton.hpp"
int main(){
cout << "Now we get the instance" << endl;
Singleton* instance1 = Singleton::getInstance();
Singleton* instance2 = Singleton::getInstance();
Singleton* instance3 = Singleton::getInstance();
cout << "Now we destroy the instance" << endl;
return 0;
}
我们来看下运行结果
由此可见,对于我们的程序,在我们输出Now we get the instance这句话的时候,也就是我们即将获取到这个类对象的实例的时候,在这之前这个单例类的实例在加载类的时候已经被创建好了,且我们调用了三个getInstance来获取实例,也并没有因此而多创建更多的实例,因此它是一个单例类。在程序结束的时候,这个唯一的实例才会被销毁。
上面我们在singleton.hpp 中实现的实例是一个类对象,现在我们看看来把它换成类对象的指针会怎么样
singleton.hpp
#pragma once
#include
using namespace std;
class Singleton{
private:
Singleton(){
cout << "创建了一个单例对象" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
~Singleton(){
//析构函数我们也需要声明成private的
//因为我们想要这个实例在程序运行的整个过程中都存在
//所以我们不允许实例自己主动调用析构函数释放对象
cout << "销毁了一个单例对象" << endl;
}
static Singleton* instance; //这是我们的单例对象,它是一个类对象的指针
public:
static Singleton* getInstance();
};
//下面这个静态成员变量在类加载的时候就已经初始化好了
Singleton* Singleton::instance = new Singleton();
Singleton* Singleton::getInstance(){
return instance; //这里就是直接返回instance了而不是返回&instance
}
测试程序不变,我们再来看一下程序运行结果
这是怎么回事???程序居然没有调用析构函数??那么会有什么后果?没有释放占有的资源,导致内存泄漏!!
这是为什么呢?因此此时全局数据区中,存储的并不是一个实例对象,而是一个实例对象的指针,它是一个地址而已,我们真正占有资源的实例对象是存储在堆中的。这样的声明方法可以减小全局数据区的占用量,把一大堆单例对象放在了堆中,但我们需要主动地去调用delete释放申请的资源。我们想要手动调用delete 直接释放该实例是不可能的,因为它的析构函数是私有的,调不到析构函数(析构函数是私有也是我们要求的)。
delete Singleton::getInstance; //这编译不过,调不到析构函数
singleton.hpp
#pragma once
#include
using namespace std;
class Singleton{
private:
Singleton(){
cout << "创建了一个单例对象" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
~Singleton(){
//析构函数我们也需要声明成private的
//因为我们想要这个实例在程序运行的整个过程中都存在
//所以我们不允许实例自己主动调用析构函数释放对象
cout << "销毁了一个单例对象" << endl;
}
static Singleton* instance; //这是我们的单例对象,它是一个类对象的指针
public:
static Singleton* getInstance();
static void deleteInstance(); //用来销毁实例
};
//下面这个静态成员变量在类加载的时候就已经初始化好了
Singleton* Singleton::instance = new Singleton();
Singleton* Singleton::getInstance(){
return instance;
}
void Singleton::deleteInstance(){
delete instance;
}
test.cc
#include "singleton.hpp"
int main(){
cout << "Now we get the instance" << endl;
Singleton* instance1 = Singleton::getInstance();
Singleton* instance2 = Singleton::getInstance();
Singleton* instance3 = Singleton::getInstance();
cout << "Now we destroy the instance" << endl;
Singleton::deleteInstance();
return 0;
}
程序运行结果如下图
但是这就让很多程序员不爽了,因为他们(哈哈当然还有我)总是会忘记手动去调用函数来释放资源。于是我们想到了,可不可以有一个自动地能够释放资源的函数。
singleton.hpp
#pragma once
#include
using namespace std;
class Singleton{
private:
Singleton(){
cout << "创建了一个单例对象" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
~Singleton(){
//析构函数我们也需要声明成private的
//因为我们想要这个实例在程序运行的整个过程中都存在
//所以我们不允许实例自己主动调用析构函数释放对象
cout << "销毁了一个单例对象" << endl;
}
static Singleton* instance; //这是我们的单例对象,它是一个类对象的指针
public:
static Singleton* getInstance();
private:
//定义一个内部类
class Garbo{
public:
Garbo(){}
~Garbo(){
if(instance != NULL){
delete instance;
instance = NULL;
}
}
};
//定义一个内部类的静态对象
//当该对象销毁的时候,调用析构函数顺便销毁我们的单例对象
static Garbo _garbo;
};
//下面这个静态成员变量在类加载的时候就已经初始化好了
Singleton* Singleton::instance = new Singleton();
Singleton::Garbo Singleton::_garbo; //还需要初始化一个垃圾清理的静态成员变量
Singleton* Singleton::getInstance(){
return instance;
}
test.cc
#include "singleton.hpp"
int main(){
cout << "Now we get the instance" << endl;
Singleton* instance1 = Singleton::getInstance();
Singleton* instance2 = Singleton::getInstance();
Singleton* instance3 = Singleton::getInstance();
cout << "Now we destroy the instance" << endl;
return 0;
}
程序运行结果如下
我们成功地销毁了对象,而且还没有手动去释放!Perfect!当然了,我们想到,为什么不尝试用一用智能指针呢?智能指针不就是为了能够让我们不需要手动释放资源而设计的么,它会自动去释放资源啊?关于智能指针的问题,不懂的朋友去看一看我的另一篇博客: C++中的智能指针。现在我们尝试着用智能指针试试创建一个单例类。
singleton.hpp
#pragma once
#include
#include
using namespace std;
class Singleton{
private:
Singleton(){
cout << "创建了一个单例对象" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
~Singleton(){
//析构函数我们也需要声明成private的
//因为我们想要这个实例在程序运行的整个过程中都存在
//所以我们不允许实例自己主动调用析构函数释放对象
cout << "销毁了一个单例对象" << endl;
}
static shared_ptr instance; //这是我们的单例对象,它是一个类对象的指针
public:
static shared_ptr getInstance();
};
//下面这个静态成员变量在类加载的时候就已经初始化好了
shared_ptr Singleton::instance(new Singleton());
shared_ptr Singleton::getInstance(){
return instance;
}
test.cc
#include "singleton.hpp"
int main(){
cout << "Now we get the instance" << endl;
shared_ptr instance1 = Singleton::getInstance();
shared_ptr instance2 = Singleton::getInstance();
shared_ptr instance3 = Singleton::getInstance();
cout << "Now we destroy the instance" << endl;
return 0;
}
你会发现它甚至连编译都过不了,原因是shared_ptr无法访问私有化的析构函数。但是我们又需要析构函数是私有的,这就矛盾起来了(为什么希望析构函数是私有的如上注释)。因此,我们就需要用到shared_ptr可以指定删除器的特点,自定义删除器。
singleton.hpp
#pragma once
#include
#include
using namespace std;
class Singleton{
private:
Singleton(){
cout << "创建了一个单例对象" << endl;
}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
~Singleton(){
//析构函数我们也需要声明成private的
//因为我们想要这个实例在程序运行的整个过程中都存在
//所以我们不允许实例自己主动调用析构函数释放对象
cout << "销毁了一个单例对象" << endl;
}
static void DestroyInstance(Singleton*); //自定义一个释放实例的函数
static shared_ptr instance; //这是我们的单例对象,它是一个类对象的指针
public:
static shared_ptr getInstance();
};
//下面这个静态成员变量在类加载的时候就已经初始化好了
shared_ptr Singleton::instance(new Singleton(),Singleton::DestroyInstance);
shared_ptr Singleton::getInstance(){
return instance;
}
void Singleton::DestroyInstance(Singleton*){
cout << "在自定义函数中释放实例" << endl;
}
test.cc
#include "singleton.hpp"
int main(){
cout << "Now we get the instance" << endl;
shared_ptr instance1 = Singleton::getInstance();
shared_ptr instance2 = Singleton::getInstance();
shared_ptr instance3 = Singleton::getInstance();
cout << "Now we destroy the instance" << endl;
return 0;
}
程序的运行结果如下
好了,饿汉模式的单例类讲完了,因为单例模式在程序一开始就初始化好实例,所以后续不再需要考虑线程安全的问题,因此它适用于线程比较多的程序中,以空间换取时间,提高了效率。
但在懒汉模式中,情况就不一样了,因为它是在使用时才创建实例,在第一次调用getInstance()的时候,才创建实例对象。如果有多个线程,同时调用了getInstance()获取实例,那么可能就会产生多个实例,那么这就不是我们的单例模式了。因此我们需要做一点事情,才能够避免这种情况的发生。可以看看我的下一篇文章单例模式的懒汉模式