Singleton(单例模式)也有叫单件模式,是创建型模式的一种,也是最简单最常用的模式之一。
注意设计模式的学习并不能像学习语法或者公式那样死记硬背,因为各种模式是人们在工程实践中总结出来的,一定要带着应用场景去研究。或者说设计模式是一种思想,在面向对象的程序设计(Java、C++等)里讨论设计模式的比较多,C语言编程谈论设计模式比较少见,但是C语言里就不能实现面向对象的程序设计吗?显然不是,我们可以使用结构体仍然可以实现,只不过像Java和C++它们的语言特性就是为了面向对象而设计的。
单例模式,它的最主要特征是只有一个实例。在什么情况下使用呢?比如,我想定义一个全局变量,将这个变量的值从一个类传到另外一个类,当然定义一个全局变量是可以的,但是如果代码里采用大量的全局变量,你自己都会觉得这样是不是太low了,是的显然破坏了C++等面向对象语言的结构性。所以,你可能会想到把这样变量用一个类包起来,但是问题来了,你怎么去访问这个类里面的变量,你访问的时候就需要实例化,然后给那个变量赋值,再然后在另外的一个类里获取值,又要实例化,显然两次实例化,赋值和获取值并不是同一个实例,显然并不能达到目的;你会不会想能不能把第一次实例化那个对象保存起来,访问的时候就用这个保存下来的对象访问,用什么方式保存呢?全局的?又回到原点了。。。这样的问题前辈们肯定都尝试过,所以才总结成现在的单例模式。
那么单例模式到底实现了什么功能?举例:
#include "stdafx.h"
#include<iostream>
using namespace std;
int x = 0;
class A {
public:
int a;
};
class B {
public:
B() {
cout << "BBB" << endl;
}
static int b;
B & operator=(const B & b)
{
this->b = b.b;
}
};
int B::b = 0;//类里的静态变量需要在外面定义
int main()
{
A a1;
a1.a = 10;
//A的成员变量的a的值赋给x
x = a1.a;
//B类的成员变量b来接收x变量的值
B b1;
b1.b = x;
B b2;
int ret = b2.b;
cout << "结果" << ret << endl;
return 0;
}
这就是上面low的方法,x为全局变量。
下面就用经典的单列模式写法:
//#pragma once
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
#include <iostream>
using namespace std;
/*********************1.经典定义方法*****************************/
class CSingleton
{
public:
static CSingleton* Instance()
{
if (_instance == NULL)
{
_instance = new CSingleton();
}
return _instance;
}
private:
CSingleton(){}
static CSingleton* _instance;
};
CSingleton* CSingleton::_instance = NULL;
/*********************1.经典定义方法*****************************/
#endif //~_SINGLETON_H_
注意几点:
1.构造函数需要定义成private或者protected,是因为类外想要实例化时会提示错误,只能以下面的方式:
补充:CSingleton sgn; 因为实例化的时候自动调用构造函数,但是构造函数为私有或者保护类型,因此会报错。
CSingleton* sgn1 = CSingleton::Instance();
CSingleton* sgn2 = CSingleton::Instance();
可以单步调试,第一次会调用_instance = new CSingleton();第二次直接跳过,直接返回_instance,这样保证两次访问的地址是相同的。
2.CSingleton* Instance()需要定义为static,否则类外直接调用会报错。
3.CSingleton* _instance也要声明成static,否则在类外不能定义(在类外,主要是为了程序一启动就对_instance初始化为NULL)
补充:静态成员属于类,并不属于类的对象,它可以理解为类里的全局变量。
另外,静态成员变量需要在类外定义(当前文件里),否则无法访问,会报错“error LNK2001: 无法解析的外部符号”;
这样磕磕绊绊达到了和“全局变量一样”效果的类,这就相当于造了一个模子,使用的时候,就把想定义的变量,加到这个类里面,用这唯一的一个实例(事实上用_instance指针去访问)。面向对象语言就是这样,看起来比全局变量优雅吧。
懒汉式和饿汉式
懒汉式:就是上面这种,为啥叫懒汉式,是说这种单例模式太懒了,程序员调用的时候才实例化。
而下面的方式是饿汉式:还没等程序员要呢,就自己先实例化了,是不是很饥渴。
#pragma once
#include<iostream>
class CSingleton
{
public:
static CSingleton* Instance()
{
return _instance;
}
private:
CSingleton() {}
static CSingleton* _instance;
};
CSingleton* CSingleton::_instance = new CSingleton();
程序启动的时候就CSingleton* CSingleton::_instance = new CSingleton();实例化了,每次调用直接返回 _instance即可。
特点与选择:
由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。
在访问量较小时,采用懒汉实现。这是以时间换空间。
【参考】https://www.cnblogs.com/guxuanqing/p/5876873.html
其他知识点:
1.多线程的单列模式处理:参考:https://www.cnblogs.com/dupengcheng/p/7205527.html
这篇博客有个地方,我觉得有个地方:
这个锁应该放在if语句外面,要不然两个线程还是会实例化两次。
2.单列模式的析构处理:参考:https://www.cnblogs.com/sixbeauty/p/3790693.html