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

解法一:单线程解法

	//缺点:多线程情况下,每个线程可能创建出不同的的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. Python 中国大学排名定向爬虫

    代码来自于中国大学Mooc北京理工大学Pythont教学团队:https://www.icourse163.org/learn/BIT-1001870001#/learn/content?type=d ...

  2. Linux 7开机自启项查看并设置

      在Linux6中查看及设置开机自启信息是使用chkconfig命令,Linux7中此命令已经被替代,接下来我们就来研究下Linux7中的区别所在. chkconfig --list Note: T ...

  3. day 36 初始前端 html语言

    参考博客https://www.cnblogs.com/majj/p/9056951.html进行学习 html标签 特征: .空白折叠现象 .对空格和换行不敏感 .标签要严格封闭 p标签的嵌套 多注 ...

  4. IdentityServer4 保护.net framework webapi

    一.IS4服务器配置 1.新建一个Asp.net  Core MVC程序,模板选择 Empty 2.Nuget添加 IdentityServer4,我这里添加的是2.5.3 3.添加Config文件, ...

  5. EFK教程(4) - ElasticSearch集群TLS加密通讯

    基于TLS实现ElasticSearch集群加密通讯 作者:"发颠的小狼",欢迎转载 目录 ▪ 用途 ▪ ES节点信息 ▪ Step1. 关闭服务 ▪ Step2. 创建CA证书 ...

  6. PHP安全之道3:常见漏洞和攻防

    第一篇 SQL注入 安全配置和编程安全并不是万全之法,攻击者往往可以通过对漏洞的试探找到新的突破口,甚至0days. 下面总结以下常见漏洞,在日常开发维护工作中可以留意. *聊聊老朋友:SQL注入漏洞 ...

  7. js前端数据验证JS工具

    var regexEnum = { intege : "^-?[1-9]\\d*$", // 整数 intege1 : "^[1-9]\\d*$", // 正整 ...

  8. fsockopen与HTTP 1.1/HTTP 1.0

    在前面的例子中,HTTP请求信息头有些指定了 HTTP 1.1,有些指定了 HTTP/1.0,有些又没有指定,那么他们之间有什么区别呢? 关于HTTP 1.1与HTTP 1.0的一些基本情况,可以参考 ...

  9. Java基础IO类之字符串流(查字符串中的单词数量)与管道流

    一.字符串流 定义:字符串流(StringReader),以一个字符为数据源,来构造一个字符流. 作用:在Web开发中,我们经常要从服务器上获取数据,数据返回的格式通常一个字符串(XML.JSON), ...

  10. “洞察千里”,华为云HiLens如何让无人车智行天下

    作者:华为云 Rosie 随着人工智能的普及和渗透,"无人"的场景越来越丰富,无人超市.无人车.无人机等已经融入我们的生活. 乘着这股热浪,华为云携手上海交通大学学生创新中心举办了 ...