剑指offer笔记面试题2----实现Singleton模式
题目:设计一个类,我们只能生成该类的一个实例。
解法一:单线程解法
//缺点:多线程情况下,每个线程可能创建出不同的的Singleton实例
#include <iostream>
using namespace std;
class Singleton
{
public:
static Singleton* getInstance()
{
if(m_pInstance == nullptr)
{
m_pInstance = new Singleton();
}
return m_pInstance;
}
static void destroyInstance()
{
if(m_pInstance != nullptr)
{
delete m_pInstance;
m_pInstance = nullptr;
}
}
private:
Singleton(){}
static Singleton* m_pInstance;
};
Singleton* Singleton::m_pInstance = nullptr;
// 单线程获取多次实例
void Test1(){
// 预期结果:两个实例指针指向的地址相同
Singleton* singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
Singleton* singletonObj2 = Singleton::getInstance();
cout << singletonObj2 << endl;
Singleton::destroyInstance();
}
int main(){
Test1();
return 0;
}
解法二:多线程+加锁
/*解法一是最简单,也是最普遍的实现方式。但是,这种实现方式有很多问题,比如没有考虑多线程的问题,在多线程的情况下,就可能会创建多个Singleton实例,以下是改善的版本。*/
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
static mutex m_mutex; // 互斥量
Singleton(){}
static Singleton* m_pInstance;
public:
static Singleton* getInstance(){
if(m_pInstance == nullptr){
m_mutex.lock(); // 使用C++11中的多线程库
if(m_pInstance == nullptr){ // 两次判断是否为NULL的双重检查
m_pInstance = new Singleton();
}
m_mutex.unlock();
}
return m_pInstance;
}
static void destroyInstance(){
if(m_pInstance != nullptr){
delete m_pInstance;
m_pInstance = nullptr;
}
}
};
Singleton* Singleton::m_pInstance = nullptr;
mutex Singleton::m_mutex;
void print_singleton_instance(){
Singleton *singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
}
// 多个进程获得单例
void Test1(){
// 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_singleton_instance));
}
for(auto& thr : threads){
thr.join();
}
}
int main(){
Test1();
Singleton::destroyInstance();
return 0;
}
/*此方法中进行了两次m_pInstance == nullptr的判断,使用了所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,只在m_pInstance不为nullptr时才需要加锁,同时也保证了线程安全。但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈,为此,一种新的单例模式的实现也就出现了。*/
解法三:const static型实例
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
Singleton(){}
static const Singleton* m_pInstance;
public:
static Singleton* getInstance(){
return const_cast<Singleton*>(m_pInstance); // 去掉“const”特性
// 注意!若该函数的返回值改为const static型,则此处不必进行const_cast静态转换
// 所以该函数可以改为:
/*
const static Singleton* getInstance(){
return m_pInstance;
}
*/
}
static void destroyInstance(){
if(m_pInstance != NULL){
delete m_pInstance;
m_pInstance = NULL;
}
}
};
const Singleton* Singleton::m_pInstance = new Singleton(); // 利用const只能定义一次,不能再次修改的特性,static继续保持类内只有一个实例
void print_singleton_instance(){
Singleton *singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
}
// 多个进程获得单例
void Test1(){
// 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_singleton_instance));
}
for(auto& thr : threads){
thr.join();
}
}
int main(){
Test1();
Singleton::destroyInstance();
return 0;
}
/*因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。由于上述三种实现,都要考虑到实例的销毁,关于实例的销毁,待会在分析。*
解法四:在get函数中创建并返回static临时实例的引用
//PS:该方法不能认为控制单例实例的销毁
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
Singleton(){}
public:
static Singleton* getInstance(){
static Singleton m_pInstance; // 注意,声明在该函数内
return &m_pInstance;
}
};
void print_singleton_instance(){
Singleton *singletonObj = Singleton::getInstance();
cout << singletonObj << endl;
}
// 多个进程获得单例
void Test1(){
// 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_singleton_instance));
}
for(auto& thr : threads){
thr.join();
}
}
// 单个进程获得多次实例
void Test2(){
// 预期结果,打印出相同的地址,之间换行符分隔
print_singleton_instance();
print_singleton_instance();
}
int main(){
cout << "Test1 begins: " << endl;
Test1();
cout << "Test2 begins: " << endl;
Test2();
return 0;
}
解法五:最终方案,最简&显式控制实例销毁
/*在实际项目中,特别是客户端开发,其实是不在乎这个实例的销毁的。因为,全局就这么一个变量,全局都要用,它的生命周期伴随着软件的生命周期,软件结束了,他就自然而然结束了,因为一个程序关闭之后,它会释放它占用的内存资源的,所以,也就没有所谓的内存泄漏了。
但是,有以下情况,是必须要进行实例销毁的:
在类中,有一些文件锁了,文件句柄,数据库连接等等,这些随着程序的关闭而不会立即关闭的资源,必须要在程序关闭前,进行手动释放。*/
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
class Singleton
{
private:
Singleton(){}
static Singleton* m_pInstance;
// **重点在这**
class GC // 类似Java的垃圾回收器
{
public:
~GC(){
// 可以在这里释放所有想要释放的资源,比如数据库连接,文件句柄……等等。
if(m_pInstance != NULL){
cout << "GC: will delete resource !" << endl;
delete m_pInstance;
m_pInstance = NULL;
}
};
};
// 内部类的实例
static GC gc;
public:
static Singleton* getInstance(){
return m_pInstance;
}
};
Singleton* Singleton::m_pInstance = new Singleton();
Singleton::GC Singleton::gc;
void print_instance(){
Singleton* obj1 = Singleton::getInstance();
cout << obj1 << endl;
}
// 多线程获取单例
void Test1(){
// 预期输出:相同的地址,中间可能缺失换行符,属于正常现象
vector<thread> threads;
for(int i = 0; i < 10; ++i){
threads.push_back(thread(print_instance));
}
for(auto& thr : threads){
thr.join();
}
}
// 单线程获取单例
void Test2(){
// 预期输出:相同的地址,换行符分隔
print_instance();
print_instance();
print_instance();
print_instance();
print_instance();
}
int main()
{
cout << "Test1 begins: " << endl;
cout << "预期输出:相同的地址,中间可以缺失换行(每次运行结果的排列格式通常不一样)。" << endl;
Test1();
cout << "Test2 begins: " << endl;
cout << "预期输出:相同的地址,每行一个。" << endl;
Test2();
return 0;
}
/*在程序运行结束时,系统会调用Singleton的静态成员GC的析构函数,该析构函数会进行资源的释放,而这种资源的释放方式是在程序员“不知道”的情况下进行的,而程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。
那么这种实现方式的原理是什么呢?由于程序在结束的时候,系统会自动析构所有的全局变量,系统也会析构所有类的静态成员变量,因为静态变量和全局变量在内存中,都是存储在静态存储区的,所有静态存储区的变量都会被释放。由于此处是用了一个内部GC类,而该类的作用就是用来释放资源。这种技巧在C++中是广泛存在的,参见《C++中的RAII机制》。*/
剑指offer笔记面试题2----实现Singleton模式的更多相关文章
- 【剑指offer】面试题 2. 实现 Singleton 模式
面试题 2. 实现 Singleton 模式 题目:设计一个类,我们只能生成该类的一个实例. 单例模式:确保一个类只有一个实例,并提供了一个全局访问点. Java 实现 1.饿汉模式 //饿汉模式 p ...
- 剑指offer笔记面试题1----赋值运算符函数
题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数. class CMyString{ public: CMyString(char* pData = nullptr); CMyS ...
- 剑指offer笔记面试题3----数组中重复的数字
题目一:找出数组中重复的数字.在一个长度为n的数组里的所有数字都在0~n-1的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次.请找出数组中任意一个重复的数字.例如 ...
- 剑指offer笔记面试题4----二维数组中的查找
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. 测试用例: 二维数组中包含 ...
- 剑指offer笔记面试题5----替换空格
题目:请实现一个函数,把字符串中的每个空格替换成"20%".例如,输入"We are happy."则输出"We%20are%20happy.&quo ...
- 剑指offer笔记面试题6----从未到头打印链表
题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值.链表节点定义如下: struct ListNode{ int m_nKey; ListNode* m_pNext; } 测试用例: 功能测 ...
- 剑指offer笔记面试题7----重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如,输入前序遍历序列{1, 2, 4, 7, 3, 5, 6, 8}和中序遍历序列 ...
- 剑指offer笔记面试题8----二叉树的下一个节点
题目:给定一棵二叉树和其中的一个节点,如何找出中序遍历序列的下一个节点?树中的节点除了有两个分别指向左.右子节点的指针,还有一个指向父节点的指针. 测试用例: 普通二叉树(完全二叉树,不完全二叉树). ...
- 剑指offer笔记面试题9----用两个栈实现队列
题目:用两个栈实现一个队列.队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在尾部插入节点和在队列头部删除节点的功能. 测试用例: 往空的队列里添加.删除元素. ...
随机推荐
- SpringIoc以及set注入八种基本类型的简单实现
SpringIoc以及set注入八种基本类型的简单实现 主要涉及到的技术以及API 反射.xml定义和解析.树模型 反射可以在代码运行状态下动态的创建对象以及调用方法. API:Dom4j.Class ...
- 使用three.js创建大小不随着场景变化的文字
使用three.js创建大小不随着场景变化的文字,需要以下两步: 1.将文字绘制到画布上. 2.创建着色器材质,把文字放到三维场景中. 优点: 1.跟用html实现文字相比,这些文字可以被模型遮挡,更 ...
- P1035 级数求和
题目描述 已知:S_n= 1+1/2+1/3+…+1/nSn=1+1/2+1/3+…+1/n.显然对于任意一个整数KK,当nn足够大的时候,S_nSn大于KK. 现给出一个整数KK(1 \le k ...
- php 第1讲 html介绍 html运行原理①
1. html (hypertext mark-up language )是 超文本编辑语言,主要的用处是做网页,可以在网页上显示文字.图形.动画.视频... “标记“有时候也称之为“元素” 动态网页 ...
- ios中数据存储方式
以上三种不能存储大批量数据 plist只能先取出来 里面的数据 覆盖存储 SQLLite3 数据库 纯C语言 轻量级 CoreData 基于SQLLite3 OC版本 重量级 大批量数据缓存 SQL ...
- 深入浅出-iOS程序性能优化
iOS应用是非常注重用户体验的,不光是要求界面设计合理美观,也要求各种UI的反应灵敏,我相信大家对那种一拖就卡卡卡的 TableView 应用没什么好印象.还记得12306么,那个速度,相信大家都受不 ...
- 用IDEA详解Spring中的IoC和DI(挺透彻的,点进来看看吧)
用IDEA详解Spring中的IoC和DI 一.Spring IoC的基本概念 控制反转(IoC)是一个比较抽象的概念,它主要用来消减计算机程序的耦合问题,是Spring框架的核心.依赖注入(DI)是 ...
- 自古逢秋悲寂寥,奈何今秋热成雕?Python使用Pyecharts统计全国温度Top10并绘图
秋词-刘禹锡 自古逢秋悲寂寥, 我言秋日胜春朝. 晴空一鹤排云上, 便引诗情到碧霄. 古人谈及秋天,都是悲凉寂寥,那么-.我好想回到古代的秋天啊!明明到了秋天,为什么最近的气温比夏天还热. 之前做天气 ...
- 是时候考虑让你的Spark跑在K8S上了
[摘要] Spark社区在2.3版本开始,已经可以很好的支持跑着Kubernetes上了.这样对于统一资源池,提高整体资源利用率,降低运维成本(特别是技术栈归一)有着非常大的帮助.这些趋势是一个大数据 ...
- 用C#写小工具:将圆柱面贴图映射到半球贴图
最近在写GBA的程序.GBA运行的是C的裸机代码,而中途使用的很多小工具则用C#写的,例如:图片转换到.h头文件,制作三角函数表,还有像这次介绍的将圆柱面贴图映射到半球贴图的小工具.这样的小功能,用C ...