[TOC]
单例模式作为最常用的设计模式之一,用来保证一个类仅有一个实例化对象,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
1、单例模式的实现思路
单例模式单例模式的实现思路一般有如下两步:
(1)私有化类的构造函数,以防止外界创建单例类的对象;
(2)使用类的私有静态指针变量指向类的唯一实例,并用一个公有静态方法获取该实例。
根据类的唯一实例初始化的时机,可以将单例模式的实现分为:懒汉模式和饿汉模式。
懒汉模式:非常懒,不用的时候不去初始化,只在第一次被调用时才进行初始化;
饿汉模式:迫不及待,即使还没有程序调用它,其已提前初始化等待被调用
2、单线程的单例模式
我们实现一个经典的懒汉模式的单例模式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
class Singleton{
protected:
//构造函数被保护,不能被外界访问
Singleton(){cout<<"构造函数执行成功"<<endl;};
private:
//设置一个私有的静态类指针,用来保存全局唯一的实例对象
static Singleton* _instance;//静态成员变量
public:
//设置一个公有的静态成员函数,用来作为外界唯一的创建接口
static Singleton* Instance(){
if(_instance==nullptr)_instance=new Singleton();
return _instance;
};
};
// 全局变量类外声明
Singleton* Singleton::_instance=NULL;
我们实现一个经典的饿汉模式的单例模式如下:
>#include<iostream> using namespace std; class Singleton{ protected: //构造函数被保护,不能被外界访问 Singleton(){cout<<"构造函数执行成功"<<endl;}; private: //设置一个私有的静态类指针,用来保存全局唯一的实例对象 static Singleton* _instance;//静态成员变量 public: //设置一个公有的静态成员函数,用来作为外界唯一的创建接口 static Singleton* Instance(){return _instance;}; }; // 全局变量类外声明,直接实例化 Singleton* Singleton::_instance=new Singleton;
可以看到,所谓的懒汉模式和饿汉模式的区别非常容易理解。
3、多线程的单例模式
在多线程中,需要考虑共享资源的安全性,使用互斥锁
(1)经典的线程安全懒汉模式
单例模式有两种实现方法,分别是懒汉模式和饿汉模式。顾名思义,。
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
28
29
30
31
#include<iostream>
#include<pthread.h>
using namespace std;
class Singleton{
protected:
//构造函数被保护,不能被外界访问
Singleton(){
pthread_mutex_init(&lock, NULL);
cout<<"构造函数执行成功"<<endl;
};
private:
//设置一个私有的静态类指针,用来保存全局唯一的实例对象
static Singleton* _instance;//静态成员变量
static pthread_mutex_t lock;//静态锁,是由于静态函数只能访问静态成员
public:
//设置一个公有的静态成员函数,用来作为外界唯一的创建接口
static Singleton* Instance(){
if (_instance==NULL){
pthread_mutex_lock(&lock);
if (_instance==NULL){
_instance = new Singleton();
}
pthread_mutex_unlock(&lock);
}
return _instance;
};
};
// 全局变量类外声明,直接实例化
Singleton* Singleton::_instance=NULL;
pthread_mutex_t Singleton::lock;
为什么要用双检测,只检测一次不行吗?
如果只检测一次,在每次调用获取实例的方法时,都需要加锁,这将严重影响程序性能。双层检测可以有效避免这种情况,仅在第一次创建单例的时候加锁,其他时候都不再符合NULL == p的情况,直接返回已创建好的实例。
(2)局部静态变量之线程安全懒汉模式
前面的双检测锁模式,写起来不太优雅,《Effective C++》(Item 04)中的提出另一种更优雅的单例模式实现,使用函数内的局部静态对象,这种方法不用加锁和解锁操作。它不用加锁是因为在C++11之后,编译器会保证局部静态变量的线程安全性,所以不需要程序员额外为局部静态变量设置线程安全性。
1
2
3
4
5
6
7
8
9
10
11
class Singleton{
protected:
//构造函数被保护,不能被外界访问
Singleton(){cout<<"构造函数执行成功"<<endl;};
public:
//设置一个公有的静态成员函数,用来作为外界唯一的创建接口
static Singleton* Instance(){
static Singleton _instance;//静态成员变量
return &_instance;
};
};
类的访问权限
c++的访问权限(也叫访问级别):指类外和子类对类内成员的访问权限。c++成员的默认访问权限是private。
访问权限 | 类外(实例化对象) | 类内成员 | 子类成员 | 友元函数 | 友元类 |
---|---|---|---|---|---|
public | 可访问 | 可访问 | 可访问 | 可访问 | 可访问 |
protected | 不可访问 | 可访问 | 可访问 | 可访问 | 可访问 |
private | 不可访问 | 可访问 | 不可访问 | 可访问 | 可访问 |
在c++中,鼓励将所有数据声明为private,想要访问数据则通过单独定义public的成员函数来访问,public为公有的成员函数。
static关键字
static是c++的一个限定符,用来控制某个变量的存储方式和可见性。static可以修饰变量(全局变量、局部变量、类成员变量等),类成员函数、普通函数。所有的静态变量都存储在c++的全局数据区,在声明处被初始化,如果没有指明初始值就自动初始化为0,一旦初始化就直至程序结束才释放内存。使用场景如下:
静态全局变量:只能在本文件中访问,不能在其他文件中访问,即便是extern外部声明也不可以(外部可见性缩小)。 |
---|
静态局部变量在首次执行时初始化,直至程序运行结束后才释放(生命周期延长)。 |
类的静态成员变量必须在类外声明,且类的多个对象共享同一个静态成员变量。 |
类的静态成员函数只能调用静态成员变量(函数内没有this指针)。 |
类的静态成员函数
1)静态成员函数只能调用类的静态成员变量或者静态成员函数。
2)静态成员函数在类外定义时,不能加static修饰,否则出错。
3)在类外可以通过对象名或者类名来调用类的静态成员函数。
4)非静态成员函数可以任意地访问静态成员函数和静态数据成员。
内联函数inline
(问题:inline关键字的作用是什么) inline是c++的一个限定符,用来显示声明一个函数为内联函数,类中定义的函数都默认为内联函数。
内联函数可以减少函数调用的开销,提高程序执行的效率。编译器处理内联函数时,不会单独进行函数调用,而是在编译期间直接将整个函数体的代码插入调用语句处,就像整个函数体在调用处被重写了一遍一样,这个过程发生在编译期间。显然使用内联函数会使最终可执行程序的体积增加,因为内联函数是以空间换取时间。
(什么时候需要使用inline关键字) 内联函数适用于小而简单、执行很快的函数,如果一个函数非常庞大或者需要消耗大量时间,那么将其声明为内联函数虽然节省了函数调用的时间,但是却让程序体积增加了更多,这样程序执行速度很可能反而会下降。现代c++编译器提供了内联函数的保护机制,一般程序员声明的内联函数只是给编译器的建议,具体编译器是否真的按照内联函数的方式处理可能由内部算法决定。