【C++】设计模式-单例模式

这篇具有很好参考价值的文章主要介绍了【C++】设计模式-单例模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、单例模式

单例模式的三个要点

针对上述三要点的解决方案

常用的两类单例模式

 二、懒汉模式实现

1.基本实现

2.锁+静态成员析构单例

3.双层检查锁定优化

4.双层检查锁定+智能指针

三、饿汉模式实现

1.基础实现

2.嵌套内部类解决内存泄漏

3.智能指针解决内存泄漏 


一、单例模式

单例模式(Singleton Pattern)是 一种属于创建型设计模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。(即它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。)

单例模式的三个要点

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

针对上述三要点的解决方案

1)私有化构造函数:这样外界就无法自由地创建类对象,进而阻止了多个实例的产生。

2)类定义中含有该类的唯一静态私有对象:静态变量存放在全局存储区,且是唯一的,供所有对象使用。

3)用公有的静态函数来获取该实例:提供了访问接口。

常用的两类单例模式

1)懒汉模式:在使用类对象(单例实例)时才会去创建它,不然就不创建。

2)饿汉模式:单例实例在类装载时构建,有可能全局都没使用过,但它占用了空间,就像等着发救济粮的饿汉提前排好队等吃的一样。


 二、懒汉模式实现

1.基本实现

//singleton.h
#pragma once
#include <iostream>
using namespace std;

class Singleton
{
public:
	//公共接口获取唯一实例
	static Singleton* getInstance()
	{
		if (m_instance == nullptr)
		{
			cout << "创建实例" << endl;
			m_instance = new Singleton;
		}
		return m_instance;
	}
private:
	//构造私有
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}  
	//Singleton()=default;


	~Singleton()
	{
		cout << "调用析构函数" << endl;
	} 
	//~Singleton() = default;

	//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	//静态私有对象
	static Singleton* m_instance;
};

Singleton* Singleton::m_instance = nullptr; //初始化
#include "singleton.h"

int main()
{
	Singleton* instance1 = Singleton::getInstance();
	Singleton* instance2 = Singleton::getInstance();
	return 0;

}

 执行结果:

【C++】设计模式-单例模式,c++,设计模式,单例模式

由上述结果可知,的确只创建了一个实例。

但同时暴露了两个问题:①线程安全;②内存泄漏

①线程安全:在多线程场景下,可能多个线程进行new操作,需要加锁进行限制,保证只进行一次new操作。

【C++】设计模式-单例模式,c++,设计模式,单例模式

#include "singleton.h"

int main()
{
	thread t1([] {Singleton* s1 = Singleton::getInstance();});
	thread t2([] {Singleton* s2 = Singleton::getInstance();});

	t1.join();
	t2.join();
	return 0;

}

 【C++】设计模式-单例模式,c++,设计模式,单例模式

 ②内存泄漏:new在堆上的资源在程序结束时,需要通过delete进行释放。上面并没有调用析构函数执行delete操作。

2.锁+静态成员析构单例

#include <iostream>
#include <mutex>
using namespace std;

//锁+静态成员析构单例
class Singleton
{
public:
	static Singleton* getInstance()
	{
		m_mutex.lock();//上锁
		if (m_instance == nullptr)
		{
			cout << "创建实例" << endl;
			m_instance = new Singleton;
		}
		m_mutex.unlock();//解锁
		return m_instance;
	}
private:
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}  
	//Singleton()=default;

	~Singleton()
	{
		cout << "调用析构函数" << endl;
	} 
	//~Singleton() = default;

	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	class FreeInstace
	{
	public:
		FreeInstace()=default;
		~FreeInstace()
		{
			if (Singleton::m_instance != nullptr)
			{
				delete Singleton::m_instance;
				cout << "单例销毁" << endl;

			}
		}
	};
private:
	//静态私有对象
	static Singleton* m_instance;
	static FreeInstace m_freeinstance;
	static mutex m_mutex;
};

Singleton* Singleton::m_instance = nullptr; //初始化
Singleton::FreeInstace Singleton::m_freeinstance;
mutex Singleton::m_mutex;

该方案的缺点在于对Singleton的每次访问都需要获取一个锁,锁导致速度慢,效率低。但实际上,我们只需要一个锁,初始化m_instance时(即确定m_instance指向时),这应该只在第一次调用实例时发生。如果在程序运行的过程中调用了n次instance,则只在第一次调用时需要锁。当你知道n - 1个锁是不必要的,为什么还要为n个锁的获取买单呢?

3.双层检查锁定优化

static Singleton* getInstance()
	{
		if (m_instance == nullptr)
		{
			m_mutex.lock();//上锁
			if (m_instance == nullptr)
			{
				cout << "创建实例" << endl;
				m_instance = new Singleton;
			}
			m_mutex.unlock();//解锁
		}
		return m_instance;
	}

双层检查锁定的关键是观察到大多数对instance的调用将看到m_instance是非空的,因此不会尝试初始化它。因此,它尝试获取锁之前测试m_instance是否为空。只有当测试成功(即m_instance尚未初始化)时,才会获得锁,然后再次执行测试以确保m_instance仍然为空(因此称为双重检查锁定)。第二个测试是必要的,因为,正如上面描述的情况在m_instance第一次被测试到获得锁的时间之间,有可能发生另一个线程初始化m_instance的情况

使用双层检查锁定将已经初始化的对象的直接返回。可以使代码性能会大大加快。但它们没有考虑到一个更基本的问题,即确保在双层检查锁定期间执行的机器指令以可接受的顺序执行。

m_instance = new Singleton;

这个语句导致三件事发生:
步骤1:分配内存来保存Singleton对象。
步骤2:在分配的内存中构造一个单例对象。
步骤3:使m_instance 指向已分配的内存。
最重要的是观察到编译器不受约束,会按照这个顺序执行这些步骤!

特别是,编译器有时允许交换步骤2和步骤3。所以可能导致访问到未初始化的对象的引用。

解决方案:可以参考如下链接C++完美单例模式 - 简书

4.双层检查锁定+智能指针

针对内存泄漏问题,除了可以方法2介绍的使用静态成员在程序结束时,销毁成员是调用析构进行delete,还可以使用智能指针,头文件引用<memory>

class Singleton
{
public:
	static shared_ptr<Singleton> getInstance()
	{
		if (m_instance == nullptr)
		{
			m_mutex.lock();//上锁
			if (m_instance == nullptr)
			{
				cout << "创建实例" << endl;
				m_instance.reset( new Singleton(), destoryInstance);
			}
			m_mutex.unlock();//解锁
		}
		return m_instance;
	}
	static void destoryInstance(Singleton* x) 
	{
		cout << "自定义释放实例" << endl;
		delete x;
	}
private:
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}
	//Singleton()=default;

	~Singleton()
	{
		cout << "调用析构函数" << endl;
	}
	//~Singleton() = default;

	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	//静态私有对象
	static  shared_ptr<Singleton> m_instance;
	static mutex m_mutex;
};

shared_ptr<Singleton> Singleton::m_instance = nullptr; //初始化
mutex Singleton::m_mutex;

应用智能指针后,在程序结束时,它自动进行资源的释放,解决了内存泄漏的问题。


三、饿汉模式实现

饿汉和懒汉的差别就在于,饿汉提前进行了创建。

1.基础实现

class Singleton
{
public:
	//公共接口获取唯一实例
	static Singleton* getInstance()
	{
		return m_instance;
	}
private:
	//构造私有
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}  
	//Singleton()=default;


	~Singleton()
	{
		cout << "调用析构函数" << endl;
	} 
	//~Singleton() = default;

	//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	//静态私有对象
	static Singleton* m_instance;
};

Singleton* Singleton::m_instance = new Singleton; //初始化

所以main还没开始,实例就已经构建完毕。获取实例的函数也不需要进行判空操作,因此也就不用双重检测锁来保证线程安全了,它本身已经是线程安全状态了。

但是内存泄漏的问题还是要解决的。文章来源地址https://www.toymoban.com/news/detail-591470.html

2.嵌套内部类解决内存泄漏

class Singleton
{
public:
	//公共接口获取唯一实例
	static Singleton* getInstance()
	{
		return m_instance;
	}
private:
	//构造私有
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}
	//Singleton()=default;


	~Singleton()
	{
		cout << "调用析构函数" << endl;
	}
	//~Singleton() = default;

	//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	class FreeInstace
	{
	public:
		FreeInstace()=default;
		~FreeInstace()
		{
			if (Singleton::m_instance != nullptr)
			{
				delete Singleton::m_instance;
				cout << "单例销毁" << endl;

			}
		}
	};
private:
	//静态私有对象
	static Singleton* m_instance;
	static FreeInstace m_freeinstance;
};

Singleton* Singleton::m_instance = new Singleton; //初始化
Singleton::FreeInstace Singleton::m_freeinstance;

3.智能指针解决内存泄漏 

class Singleton
{
public:
	//公共接口获取唯一实例
	static shared_ptr<Singleton> getInstance()
	{
		return m_instance;
	}

	static void destoryInstance(Singleton* x) {
		cout << "自定义释放实例" << endl;
		delete x;
	}
private:
	//构造私有
	Singleton()
	{
		cout << "调用构造函数" << endl;
	}  
	//Singleton()=default;


	~Singleton()
	{
		cout << "调用析构函数" << endl;
	} 
	//~Singleton() = default;

	//禁用拷贝构造和赋值运算符(=delete 为C++11新标准)
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

private:
	//静态私有对象
	static shared_ptr<Singleton> m_instance;
};

shared_ptr<Singleton>  Singleton::m_instance ( new Singleton, destoryInstance); //初始化

到了这里,关于【C++】设计模式-单例模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C++设计模式创建型之单例模式

    一、概述         单例模式也称单态模式,是一种创建型模式,用于创建只能产生一个对象实例的类。例如,项目中只存在一个声音管理系统、一个配置系统、一个文件管理系统、一个日志系统等,甚至如果吧整个Windows操作系统看成一个项目,那么其中只存在一个任务管理

    2024年02月14日
    浏览(47)
  • 【C++】特殊类设计(单例模式)

    设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。 使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 根本原因是为了代码复用,增加可维护性。 设计模式的例子:迭代器模式 拷贝一共就只有两个场景,一

    2023年04月22日
    浏览(53)
  • C++特殊类设计(单例模式)

    C++98 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。 原因: 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义

    2024年01月19日
    浏览(44)
  • 【C++】特殊类设计+单例模式+类型转换

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。   目录 一、设计一个类,不能被拷贝 1、C++98 2、C++11 二、设计一个类,只能在堆上创建对象 1、将构造设为私有 2、将析构设为

    2024年02月06日
    浏览(52)
  • 【C++高阶(八)】单例模式&特殊类的设计

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:C++从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习C++   🔝🔝 在实际场景中,总会遇见一些特殊情况, 比如设计一个类,只能在堆上开辟空间, 亦或者是设计一个类只能实例化一个对象 在实际需求的场景

    2024年02月04日
    浏览(46)
  • C++面试:单例模式、工厂模式等简单的设计模式 & 创建型、结构型、行为型设计模式的应用技巧

            理解和能够实现基本的设计模式是非常重要的。这里,我们将探讨两种常见的设计模式:单例模式和工厂模式,并提供一些面试准备的建议。 目录 单例模式 (Singleton Pattern) 工厂模式 (Factory Pattern) 面试准备  1. 理解设计模式的基本概念 2. 掌握实现细节 3. 讨论优缺

    2024年02月01日
    浏览(66)
  • C++中特殊类的设计与单例模式的简易实现

    对于这种特殊类的设计我们一般都是优先考虑私有构造函数。 然后对于一些特殊要求就直接通过静态成员函数的实现来完成。  这里选择禁掉拷贝构造函数和拷贝函数是为了防止将已创建的对象去拷贝构造新的对象。  这里如果没有禁掉operator new和operator delete的话就会导致以

    2024年01月18日
    浏览(44)
  • 从C语言到C++_37(特殊类设计和C++类型转换)单例模式

    目录 1. 特殊类设计 1.1 不能被拷贝的类 1.2 只能在堆上创建的类 1.3 只能在栈上创建的类 1.4 不能被继承的类 1.5 只能创建一个对象的类(单例模式)(重点) 1.5.1 饿汉模式 1.5.2 懒汉模式 2. 类型转换 2.1 static_cast 2.2 reinterpret_cast 2.3 const_cast 2.4 dynamic_cast 3. RTTI(了解)和类型转换常见面

    2024年02月10日
    浏览(46)
  • 懒汉单例设计模式与饿汉单例设计模式

    单例模式即一个类确保只有一个对象,主要用于避免浪费内存 1 .饿汉单例设计模式 :拿到对象时,对象就早已经创建好了 写法: 把类的构造器私有 在类中自己创建一个对象,并赋值到一个变量 定义一个静态方法,返回自己创建的这个对象 2. 懒汉单例设计模式 :第一次拿到对象时

    2024年02月21日
    浏览(54)
  • 【设计模式】单例设计模式

    目录 1、前言 2、基本语法 2.1、懒汉式单例 2.2、饿汉式单例 2.3、双重检验锁单例模式 2.4、静态内部类单例模式 2.5、枚举单例模式 2.6、ThreadLocal单例模式 2.7、注册单例模式 3、使用场景 4、使用示例 5、常见问题 5、总结 单例模式是一种设计模式,它确保一个类只能创建一个实

    2024年02月09日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包