C++深入详解单例模式与特殊类设计的实现

目录
  • 单例模式
    • 什么是单例模式
    • 应用场景
    • 优缺点
    • 实现
      • 饿汉模式
      • 懒汉模式
  • 特殊类设计
    • 设计一个类只能在堆上创建对象
      • 方法一
      • 方法二
    • 只能在栈上创建对象
      • 方法一
      • 方法二
    • 一个类不能被继承
    • 最后

单例模式

什么是单例模式

单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。–大话设计模式

应用场景

保证一个类只有一个实例

  • 如Windows下的任务管理器,回收站等。
  • 日志管理,计数器等。

简而言之,你需要唯一实例时就可以考虑单例模式。这样它可以严格地控制客户怎样访问即何时访问它,即对唯一实例的受控访问。

优缺点

优点

  • 减少内存开销,因为在系统中只有一个实例。
  • 避免了频繁的创建和销毁对象,提高了性能
  • 避免对资源的多重占用,比如单例时多人只写一个日志文件,如果有多个日志文件可能导致对相同的日志文件进行写操作
  • 设置全局访问点

缺点

  • 职责过重,与单一职责存在冲突
  • 不能继承(构造方法私有)

实现

单例模式有两种实现模式,懒汉模式和饿汉模式。

注意单例模式的概念,大概就是唯一实例且有全局访问点,我们从这两点入手。

唯一实例:构造函数私有+防拷贝

拷贝构造是一种构造方式,所以需要防止,构造函数私有不让别人new。

全局访问点:给一个公共的接口

饿汉模式

简单来说,把东西一开始就做好,需要的时候直接吃。

#include <iostream>
using namespace std;
class Singleton
{
public:
	static Singleton& GetInstance()
	{
		return _instance;
	}
	int GetRandom()//由于侧重点不是随机数 所以直接返回一个30
	{
		return 30;
	}
private:
	Singleton() {}//构造函数私有
	Singleton(Singleton&) = delete;//防拷贝,被=delete修饰表明这个函数被删除,即可以只声明不实现,换言之禁用了该函数
	Singleton& operator=(const Singleton&) = delete;//防拷贝
	static Singleton _instance;
};
Singleton Singleton::_instance;//类外必须初始化,类内只是声明
int main()
{
	//1.调用
	cout<<Singleton::GetInstance().GetRandom()<<endl;
	//2.
	Singleton& s = Singleton::GetInstance();
	cout<<s.GetRandom()<<endl;
	return 0;
}

通过防拷贝和构造函数私有化之后下面的几种办法都失效了

Singleton s;//err
Singleton s1(s);//err
Singleton s1=s;//err

从上面我们可以看出饿汉模式的优缺点了,有点显而易见就是实现很简单粗暴,缺点很明显,类加载时单例对象就已经生成了,即还没有用就已经加载出来了,比如这个资源很大,又在游戏启动时加载,那就会造成游戏启动很慢。且如果有多个单例对象启动时实例化顺序不确定(不同源文件类内的单例对象实例化顺序是不确定的,懒汉模式解决了这个问题,因为懒汉模式的实例化在函数内部,可以通过调用函数的顺序来解决实例化的顺序问题)。

类加载时静态初始化解决了线程安全问题。

懒汉模式

什么时候需要就什么时候做饭,然后吃。

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		if (_instance == nullptr)
		{
			_mtx.lock();//double lock保证线程安全
			if (_instance == nullptr)//必须再次检查  不然可能另一个线程那已经new完了,这边又new违背了单例,且可能覆盖数据。
			{
				_instance = new Singleton();
			}
			_mtx.unlock();
		}
		return _instance;
	}
	int GetRandom()
	{
		return 30;
	}
	class Clear//资源回收的内部类,必须是公有的,不然外部声明报错
	{
	public:
		~Clear()
		{
			cout << "释放资源" << endl;
			delete _instance;
		}
	};
private:
	static Clear _cle;
	Singleton() {}
	Singleton(Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	static Singleton* _instance;
	static mutex _mtx;//线程安全
};
Singleton* Singleton::_instance = nullptr;
mutex Singleton::_mtx;
Singleton::Clear _cle;
int main()
{
	//1.
	cout<<Singleton::GetInstance()->GetRandom()<<endl;
	//2/
	static Singleton* s = Singleton::GetInstance();
	cout << s->GetRandom() << endl;
	//不能用左值引用接收的原因  返回值是右值 得用右值引用接收
	//为什么函数返回值是右值  因为返回值是借助临时变量返回的拿不到地址
	//如果返回值是左值引用就能用左值引用接收
	return 0;
}

懒汉模式的优缺点也很明显,优点是什么时候第一次用就什么时候实例化,此外可以通过调用函数解决多个单例对象实例化的顺序问题,缺点就是写起来复杂,要考虑线程安全和内存泄露方面的问题。懒汉采用的是指针也更好回收资源(饿汉采用的是对象)

懒汉可能因为多线程丢数据,线程加锁保证在多线程环境下一定只有一个线程去new对象,只创建出一个单例对象,加锁可能导致频繁切换上下文,double lock解决。

特殊类设计

我们一般会通过构造函数,拷贝构造和赋值重载创建对象,现在把这几种方法全禁用了,然后自己再写一个外部可调用的接口来自定义类的创建方式。

当然也有别的办法,这种经常可以作为一种通解思路。

下面的禁用采用C++11的delete关键字实现,作用是禁止编译器生成默认的函数版本,即有声明但无实现。

设计一个类只能在堆上创建对象

简单来说只能通过new来创建对象。

方法一

构造方法禁用,然后给一个接口让外部调用。

构造方法的禁用=构造函数私有+禁用拷贝构造+禁用赋值重载

class HeapOnly
{
public:
	static HeapOnly* GetInstance()
	{
		return new HeapOnly;
	}
	void Test()
	{
		cout << "I am Test" << endl;
	}
private:
	HeapOnly() {}
	HeapOnly(HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;
};
int main()
{
	HeapOnly* ho = HeapOnly::GetInstance();
	ho->Test();
	delete ho;
	return 0;
}

方法二

析构函数私有

对象建立在栈上,是由编译器分配空间的,编译器管理对象的生命周期,对象使用完后编译器会检查这个对象所有的非静态函数,包括析构函数,当编译器发现析构函数不能访问后就不能回收这块空间,所以编译器无法为其分配空间,编译器检查到这种情况也会报错。

析构函数私有的方法不建议使用,因为在类外无法使用delete释放空间,容易造成内存泄漏。

class HeapOnly
{
public:
	void Test()
	{
		cout << "I am Test" << endl;
	}
private:
	~HeapOnly();
};
int main()
{
	//HeapOnly ho_stack;//err
	HeapOnly* ho = new HeapOnly;
	ho->Test();
	return 0;
}

只能在栈上创建对象

方法一

不能使用new --> 重载operator new即可。

这种存在一个缺陷,就是仍然可以在静态区创建对象

方法二

将构造函数设为私有再自定义一个接口

这里不用禁用构造函数,拷贝构造,赋值重载,因为对于下面的场景来说,匿名对象的拷贝构造更符合场景,编译器会选拷贝构造来进行构造,所以如果我们禁用了拷贝构造会报错,因为我们的delete关键字是有声明无实现,并不是真的把这个函数连带声明删除了。所以StackOnly()一看有我们自己写的拷贝构造的声明就会去匹配这个拷贝构造,我们自己写的拷贝构造又没有实现拷贝功能就会报错。

理解这个和编译链接知识有关,编译器看到声明就去匹配了,而不是一定要看到函数实现才匹配,链接时候才会去找实现,当发现有声明无实现时就很容易导致链接错误。

一个类不能被继承

父类构造函数私有即可,构造时先构造父类再构造子类,父类构造不出不能继承。

最后

关于单例模式,可以说是只能创建一个对象的实现。

还有个小问题,为什么不用全局变量代替单例模式,全局定义一个唯一的变量不就行了,合理吧[doge],理论上可以,但是非常不建议,可以自行查找资料"为什么不建议使用全局变量"。

全局变量的使用可能带来很多问题,而且很容易造成链接、重定义等错误,如果多人协作时有人再给这个变量起个别名,那维护代码时代价就太大了,这还只是全局变量的一个小缺点。

牛客代码规范评分里也不建议用全局变量,当然写题时代码没那么长,几个全局变量倒时不打紧。

.h不能包含定义,不然多个cpp去包含就会有链接错误,尽量把定义和声明分离开来

【82】【Cherno C++】【中字】C++的单例模式_哔哩哔哩_bilibili

Github代码汇总

到此这篇关于C++深入详解单例模式与特殊类设计的实现的文章就介绍到这了,更多相关C++单例模式内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++ 超详细深入分析单例模式

    目录 不能被拷贝的类 C++98 C++11 只能在堆上创建对象的类 只能在栈上创建对象的类 不能被继承的类 C++98 C++11 只能创建一个对象的类(单例模式) 设计模式 单例模式 饿汉模式 懒汉模式 不能被拷贝的类 拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可. C++98 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可. class CopyBan { //... p

  • C++设计模式之单例模式详解

    目录 单例模式:就是只有一个实例. 单例模式又分为两种基本的情形:饿汉式和懒汉式 如下是懒汉式单例类 小结: 继续看单例模式 总结 单例模式:就是只有一个实例. singleton pattern单例模式:确保某一个类在程序运行中只能生成一个实例,并提供一个访问它的全局访问点.这个类称为单例类.如一个工程中,数据库访问对象只有一个,电脑的鼠标只能连接一个,操作系统只能有一个窗口管理器等,这时可以考虑使用单例模式. 众所周知,c++中,类对象被创建时,编译系统为对象分配内存空间,并自动调用构造函数

  • C++单例模式为何要实例化一个对象不全部使用static

    C++的单例模式为什么不直接全部使用static,而是非要实例化一个对象? 通过getInstance()函数获取单例对象,这种模式的关键之处不是在于强迫你用函数来获取对象.关键之处是让static对象定义在函数内部,变成局部static变量. 看下这种实现方式的经典demo: class Singleton { public: static Singleton& getInstance() { static Singleton inst; return inst; } Singleton(co

  • C++单例模式的几种实现方法详解

    目录 局部静态变量方式 静态成员变量指针方式 智能指针方式 辅助类智能指针单例模式 通用的单例模板类 总结 局部静态变量方式 //通过静态成员变量实现单例 //懒汉式 class Single2 { private: Single2() { } Single2(const Single2 &) = delete; Single2 &operator=(const Single2 &) = delete; public: static Single2 &GetInst() {

  • C++实现单例模式的自动释放

    单例模式是为了确保某个类只能创建一个对象而设计的.当一个程序的某个类型只允许有一个实例的时候使用. 一般采用动态分配的方式来生成单例对象,这个时候C++程序员就需要考虑内存回收的问题了,所以为了避免在使用单例模式时忘记回收资源而造成内存泄漏的问题,在实现单例模式的时候就使其可以自动被回收. 不带自动释放的单例模式的实现与销毁 我们先来复习一下没有自动回收机制的单例模式的实现和销毁. 单例模式的实现: 将构造函数私有化 在类中定义一个静态的指向本类型的指针变量 定义一个返回值为该类的指针的静态成员

  • C++线程安全的单例模式讲解

    废话不多说,常用的代码积淀下来. 一.懒汉模式 即第一次调用该类实例的时候才产生一个新的该类实例,并在以后仅返回此实例. 需要用锁,来保证其线程安全性:原因:多个线程可能进入判断是否已经存在实例的if语句,从而non thread safety. 使用double-check来保证thread safety.但是如果处理大量数据时,该锁才成为严重的性能瓶颈. 1.静态成员实例的懒汉模式: class Singleton { private: static Singleton* m_instanc

  • C++ 单例模式的几种实现方式研究

    单例模式 单例模式,可以说设计模式中最常应用的一种模式了,据说也是面试官最喜欢的题目.但是如果没有学过设计模式的人,可能不会想到要去应用单例模式,面对单例模式适用的情况,可能会优先考虑使用全局或者静态变量的方式,这样比较简单,也是没学过设计模式的人所能想到的最简单的方式了. 一般情况下,我们建立的一些类是属于工具性质的,基本不用存储太多的跟自身有关的数据,在这种情况下,每次都去new一个对象,即增加了开销,也使得代码更加臃肿.其实,我们只需要一个实例对象就可以.如果采用全局或者静态变量的方式,会

  • 详解C++实现线程安全的单例模式

    在某些应用环境下面,一个类只允许有一个实例,这就是著名的单例模式.单例模式分为懒汉模式,跟饿汉模式两种. 首先给出饿汉模式的实现 正解: template <class T> class singleton { protected: singleton(){}; private: singleton(const singleton&){};//禁止拷贝 singleton& operator=(const singleton&){};//禁止赋值 static T* m

  • C++实现单例模式的方法

    目录 饿汉模式 懒汉模式 锁 + 智能指针 局部静态变量 总结 饿汉模式 类实例化就会占用内存,浪费资源,效率高,不存在线程安全问题. class Singleton{ Singleton() { } static Singleton* m_instance_ptr; public: static Singleton* get_instance() { return m_instance_ptr; } }; Singleton* Singleton::m_instance_ptr = new S

  • C++深入详解单例模式与特殊类设计的实现

    目录 单例模式 什么是单例模式 应用场景 优缺点 实现 饿汉模式 懒汉模式 特殊类设计 设计一个类只能在堆上创建对象 方法一 方法二 只能在栈上创建对象 方法一 方法二 一个类不能被继承 最后 单例模式 什么是单例模式 单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点.–大话设计模式 应用场景 保证一个类只有一个实例 如Windows下的任务管理器,回收站等. 日志管理,计数器等. 简而言之,你需要唯一实例时就可以考虑单例模式.这样它可以严格地控制客户怎样访问

  • 详解C++中String类模拟实现以及深拷贝浅拷贝

    详解C++中String类模拟实现以及深拷贝浅拷贝 在C语言中/C++中,字符串是一个应用很广泛的类型,也是很基础的类型,C语言并没有直接处理字符串的操作而是采用字符指针和字符串数组进行操作,而在C++中标准库为我们封装了一个字符串的类供我们使用,使用需要#inlcude <string>头文件.我们也可以自己模拟实现一个简单的String类. 在模拟实现String类的过程中,不可避免的会遇到深拷贝浅拷贝的问题,下面就深拷贝浅拷贝做一个简介.所谓深拷贝浅拷贝,简单来说就是浅拷贝只是简单的将值

  • 详解C++的String类的字符串分割实现

    详解C++的String类的字符串分割实现 功能需求,输入一个字符串"1-2-3"切割出"1"."2"."3".在Java下直接用String的split函数就可以了.c++下String没有直接提供这个函数,需要自己写. 网上给出的解决方案是这里的三种方法.但我是通过JNI访问的,在里面用这些vector可能不中,自己封装了个,仅供参考: String recogScop = "01-02-03"; co

  • 详解如何获取C#类中发生数据变化的属性信息

    一.前言# 在平时的开发中,当用户修改数据时,一直没有很好的办法来记录具体修改了那些信息,只能暂时采用将类序列化成 json 字符串,然后全塞入到日志中的方式,此时如果我们想要知道用户具体改变了哪几个字段的值的话就很困难了.因此,趁着这个假期,就来解决这个一直遗留的小问题,本篇文章记录了我目前实现的方法,如果你有不同于文中所列出的方案的话,欢迎指出. 代码仓储地址:https://github.com/Lanesra712/ingos-common/tree/master/sample/csha

  • 详解thinkphp的Auth类认证

    RBAC是按节点进行认证的,如果要控制比节点更细的权限就有点困难了,比如页面上面的操作按钮, 我想判断用户权限来显示这个按钮, 如果没有权限就不会显示这个按钮: 再比如我想按积分进行权限认证, 积分在0-100时能干什么, 在101-200时能干什么. 这些权限认证用RABC都很困难. 下面介绍 Auth权限认证, 它几乎是全能的, 除了能进行节点认证, 上面说的RABC很难认证的两种情况,它都能实现. Auth权限认证是按规则进行认证.我先说说它的原理. 在数据库中我们有 规则表(think_

  • 详解Java中String类的各种用法

    目录 一.创建字符串 二.字符.字节与字符串的转换 1.字符与字符串的转换 2.字节与字符串的转换 三.字符串的比较 1.字符串常量池 2.字符串内容比较 四.字符串查找 五.字符串替换 六.字符串拆分 七.字符串截取 八.String类中其它的常用方法 九.StringBuffer 和 StringBuilder 1.StringBuilder与StringBuffer的区别 2.StringBuilder与StringBuffer常用的方法 十.对字符串引用的理解 一.创建字符串 创建字符串

  • 详解C#中Helper类的使用

    目录 使用背景 使用方法 1.引用CSRedisCore 2.增加helper类代码 3.使用 4.说明 结语 使用背景 项目中用户频繁访问数据库会导致程序的卡顿,甚至堵塞.使用缓存可以有效的降低用户访问数据库的频次,有效的减少并发的压力.保护后端真实的服务器. 对于开发人员需要方便调用,所以本文提供了helper类对缓存有了封装.分了三个Cache,SystemCache,RedisCache(默认缓存,系统缓存,Redis缓存).话不多说,开撸! 使用方法 1.引用CSRedisCore 可

  • 详解Java中Period类的使用方法

    目录 简介 Duration和Period 创建方法 通过时间单位创建 通过LocalDate创建 解析方法 比较方法 增减方法 转换单位 取值方法 简介 本文用示例介绍java的Period的用法. Duration和Period 说明 Duration类通过秒和纳秒相结合来描述一个时间量,最高精度是纳秒.时间量可以为正也可以为负,比如1天(86400秒0纳秒).-1天(-86400秒0纳秒).1年(31556952秒0纳秒).1毫秒(0秒1000000纳秒)等. Period类通过年.月.日

  • 详解Java中Duration类的使用方法

    目录 简介 Duration和Period 创建方法 通过时间单位创建 通过LocalDateTime或LocalTime 通过已有的Duration 解析方法 用法说明 详解 比较方法 增减方法 转换单位 取值方法 简介 本文用示例介绍java的Duration的用法. Duration和Period 说明 Duration类通过秒和纳秒相结合来描述一个时间量,最高精度是纳秒.时间量可以为正也可以为负,比如1天(86400秒0纳秒).-1天(-86400秒0纳秒).1年(31556952秒0纳

  • 详解Qt使用QImage类实现图像基本操作

    目录 一.项目介绍 二.项目基本配置 三.UI界面设计 四.主程序实现 4.1 widget.h头文件 4.2 widget.cpp源文件 五.效果演示 一.项目介绍 利用QImage类实现对图像的基本操作,包括图像显示.图像缩放.图像旋转等. 二.项目基本配置 新建一个Qt案例,项目名称为“ImageTest”,基类选择“QWidget”,点击选中创建UI界面复选框,完成项目创建. 三.UI界面设计 UI界面布局如下: 界面中创建了5个控件,其名称和类型如下: 序号 名称 类型 属性 ① Ch

随机推荐