题目:设计一个类,我们只能生成该类的一个实例。

解法一:单线程解法

	//缺点:多线程情况下,每个线程可能创建出不同的的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模式的更多相关文章

  1. 【剑指offer】面试题 2. 实现 Singleton 模式

    面试题 2. 实现 Singleton 模式 题目:设计一个类,我们只能生成该类的一个实例. 单例模式:确保一个类只有一个实例,并提供了一个全局访问点. Java 实现 1.饿汉模式 //饿汉模式 p ...

  2. 剑指offer笔记面试题1----赋值运算符函数

    题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数. class CMyString{ public: CMyString(char* pData = nullptr); CMyS ...

  3. 剑指offer笔记面试题3----数组中重复的数字

    题目一:找出数组中重复的数字.在一个长度为n的数组里的所有数字都在0~n-1的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次.请找出数组中任意一个重复的数字.例如 ...

  4. 剑指offer笔记面试题4----二维数组中的查找

    题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. 测试用例: 二维数组中包含 ...

  5. 剑指offer笔记面试题5----替换空格

    题目:请实现一个函数,把字符串中的每个空格替换成"20%".例如,输入"We are happy."则输出"We%20are%20happy.&quo ...

  6. 剑指offer笔记面试题6----从未到头打印链表

    题目:输入一个链表的头结点,从尾到头反过来打印出每个结点的值.链表节点定义如下: struct ListNode{ int m_nKey; ListNode* m_pNext; } 测试用例: 功能测 ...

  7. 剑指offer笔记面试题7----重建二叉树

    题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如,输入前序遍历序列{1, 2, 4, 7, 3, 5, 6, 8}和中序遍历序列 ...

  8. 剑指offer笔记面试题8----二叉树的下一个节点

    题目:给定一棵二叉树和其中的一个节点,如何找出中序遍历序列的下一个节点?树中的节点除了有两个分别指向左.右子节点的指针,还有一个指向父节点的指针. 测试用例: 普通二叉树(完全二叉树,不完全二叉树). ...

  9. 剑指offer笔记面试题9----用两个栈实现队列

    题目:用两个栈实现一个队列.队列的声明如下,请实现它的两个函数appendTail和deleteHead,分别完成在尾部插入节点和在队列头部删除节点的功能. 测试用例: 往空的队列里添加.删除元素. ...

随机推荐

  1. Mysql查询语句之排序查询

    语法: /* select 查询列表 from 表 [where 筛选条件] order by 排序列表 [asc/desc] */ ①asc为升序,desc为降序,且默认为升序 ②order by子 ...

  2. Caffe 图像分类

      本文主要描述如何使用 CAFFE 进行图像分类. 开发环境要求:windows 10 64位.Visual Studio 2017..NET framework 4.6.1     分类 在一个项 ...

  3. 软件测试必须掌握的抓包工具Wireshark,你会了么?

    作为软件测试工程师,大家在工作中肯定经常会用到各种抓包工具来辅助测试,比如浏览器自带的抓包工具-F12,方便又快捷:比如时下特别流行的Fiddler工具,使用各种web和APP测试的各种场景的抓包分析 ...

  4. 痞子衡嵌入式:恩智浦i.MX RTxxx系列MCU启动那些事(1)- Boot简介

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RTxxx系列MCU的BootROM功能简介. 截止目前为止i.MX RTxxx系列已公布的芯片仅有一款i.MXRT60 ...

  5. 微信浏览器跳转浏览器下载app解决方案

    新版本微信浏览器中,已禁用下载APP应用,只支持打开微信合作商APP下载,所以无法通过微信浏览器直接下载APP应用.列举微信浏览器下载APP的种解决方案: 方案:通过Url 跳转到手机默认浏览器,或者 ...

  6. 使用WireMock进行更好的集成测试

    无论您是遵循传统的测试金字塔还是采用诸如"测试蜂窝"这样的较新方法,都应该在开发过程中的某个时候开始编写集成测试用例. 您可以编写不同类型的集成测试.从持久性测试开始,您可以检查组 ...

  7. Java多线程——锁

    Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...

  8. #在windows上使用ngix重定向目录访问远程服务器文件详细实例

    为了在开发环境保持于生产环境相同的访问远程服务器文件资源的目录配置,需要在开发环境(windows)在远程文件服务器使用nignx重定向文件目录,因为网上的资料大都是copy的,解释比较笼统,也没有具 ...

  9. 转:ETL讲解(很详细!!!)

    ETL讲解(很详细!!!) ETL是将业务系统的数据经过抽取.清洗转换之后加载到数据仓库的过程,目的是将企业中的分散.零乱.标准不统一的数据整合到一起,为企业的决策提供分析依据. ETL是BI项目重要 ...

  10. 程序计数器(PC)、堆栈指针(SP)与函数调用过程

    PC(program counter)是CPU中用于存放下一条指令地址的寄存器,SP为堆栈指针.下面将介绍函数调用过程中CPU对PC和SP这两个寄存器的操作. 假设有如下函数Fun Fun() { … ...