【关于C++中----特殊类设计和单例模式】

news2025/1/10 10:45:11

文章目录

  • 一、设计一个类,不能被拷贝
    • 1.1C++98的实现方法及其弊端
    • 1.2 C++11的实现方法
  • 二、设计一个类,只能在堆上创建对象
  • 三、设计一个类,只能在栈上创建对象
  • 四、设计一个类,不能被继承
  • 五、设计一个类,只能创建一个对象(单例模式)
    • 5.1单例概念
    • 5.2饿汉模式
    • 5.3懒汉模式


一、设计一个类,不能被拷贝

1.1C++98的实现方法及其弊端

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

对此,C++98的实现方式是==将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有。==如下:

class CopyBan
{
	// ...
	private:
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
	//...
};

这样做的原因是:

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不
    能禁止拷贝了
  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写
    反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

但是,这样设计也有一些弊端。

首先,类的用户可能不会意识到该类禁止拷贝操作,从而可能会在使用时出现错误
其次,由于编译器自动生成的拷贝构造函数和拷贝赋值运算符被禁止使用,所以如果需要在代码中执行拷贝操作,就必须手动编写对应的移动构造函数和移动赋值运算符,这可能增加代码的复杂性。

1.2 C++11的实现方法

C++11及其后续版本引入了更好的解决方案,即通过将拷贝构造函数和拷贝赋值运算符声明为deleted,来明确禁止拷贝操作。此外,C++11还引入了移动构造函数和移动赋值运算符,使得对特殊类的处理更加简便和安全。如下:

class CopyBan
{
	// ...
	CopyBan(const CopyBan&)=delete;
	CopyBan& operator=(const CopyBan&)=delete;
	//...
};

这样,当禁用拷贝构造函数和拷贝赋值运算符时,编译器会在对应的调用上产生一个编译错误。这样做可以有效阻止对该类的对象进行拷贝操作


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

为了实现这个要求,首先要将构造函数放在私有成员中,防止外界随便创建对象;其次,再增加一个公有成员函数,用来专门在堆上创建一个对象并返回。如下:

class OnlyOnHeap
{
public:
	OnlyOnHeap* CreateObj()
	{
		return new OnlyOnHeap;
	}
private:
	OnlyOnHeap()
	{}
};

但是这样,在外部调用CreateObj时,却是行不通的。
原因在于,要想调用公有成员函数,首先要有一个对象。但是要创建一个对象,目前的情况来看只能通过这个函数调用。彼此矛盾。

所以,为了解决这个问题,可以将成员函数变为静态的,这样它就没有this指针了,也就可以在外部直接调用了。

如下:

class OnlyOnHeap
{
public:
	static OnlyOnHeap* CreateObj()
	{
		return new OnlyOnHeap;
	}
private:
	OnlyOnHeap()
	{}
};

int main()
{
	OnlyOnHeap* tmp = OnlyOnHeap::CreateObj();
	return 0;
}

但是这样还是存在漏洞,当用户在外部按照上述方法创建了一个在堆上的对象之后,可以直接对其解引用,然后会自动调用它自动生成的拷贝构造,这样就会创造出一个在栈上的对象。

所以,为了避免上述问题,还要禁止使用它的拷贝构造函数,如下:

class OnlyOnHeap
{
public:
	static OnlyOnHeap* CreateObj()
	{
		return new OnlyOnHeap;
	}
private:
	OnlyOnHeap()
	{}
	OnlyOnHeap(const OnlyOnHeap&) = delete;
};

还有另外一种方法:把析构函数设为私有,把构造函数设为公有,如下:

class OnlyOnHeap
{
public:
	OnlyOnHeap()
	{}
private:
	~OnlyOnHeap()
	{}
	OnlyOnHeap(const OnlyOnHeap&) = delete;
};

int main()
{
	OnlyOnHeap* tmp = new OnlyOnHeap;
	return 0;
}

但是这种方法,导致不能在外部使用delete释放对象,因为不能在外部调用析构函数。
所有,需要增加一个成员函数,用它来间接调用析构函数,如下:

class OnlyOnHeap
{
public:
	OnlyOnHeap()
	{}
	void Destroy()
	{
		this->~OnlyOnHeap();
	}
private:
	~OnlyOnHeap()
	{}
	OnlyOnHeap(const OnlyOnHeap&) = delete;
};

int main()
{
	OnlyOnHeap* tmp = new OnlyOnHeap;
	OnlyOnHeap::Destroy();
	return 0;
}

三、设计一个类,只能在栈上创建对象

同上将构造函数私有化,然后设计静态方法创建对象返回即可,如下:

class OnlyOnStack
{
public:
	static OnlyOnStack CreateObj()
	{
		return OnlyOnStack();
	}
private:
	OnlyOnStack(){}
};

int main()
{
	OnlyOnStack obj = OnlyOnStack::CreateObj();
	return 0;
}

但是这种方法还是可以创建静态的对象的,所以这个要求的实现还是有缺陷的,做不到百分百的符合要求。


四、设计一个类,不能被继承

在C++98中,可以通过将类的构造函数声明为私有来防止其他类继承该类。由于派生类需要调用基类的构造函数来完成对象的构造,而私有构造函数无法在派生类中直接访问,因此无法创建继承自该类的对象。如下:

class CannotBeInherited 
{
private:
    CannotBeInherited() {} // 私有构造函数

    friend class SomeOtherClass; // 允许某些类访问私有构造函数
};

在C++11中,可以使用 ​final​关键字来声明一个类,表示该类不能被继承。如下:

class CannotBeInherited final 
{
    // 类定义
};

使用 ​final​关键字修饰类后,任何试图从此类派生的尝试都会导致编译错误。

需要注意的是,在C++11中还可以通过将基类的析构函数声明为虚函数,并将其设为纯虚函数(​= 0​),从而使得该类成为一个抽象类,无法直接实例化或继承。这种方式一般适用于需要通过派生类来实现多态性和覆盖虚函数的情况。如下:

class CannotBeInheritedAbstract 
{
public:
    virtual ~CannotBeInheritedAbstract() = 0;
};

CannotBeInheritedAbstract::~CannotBeInheritedAbstract() {}

五、设计一个类,只能创建一个对象(单例模式)

5.1单例概念

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
它有两种实现模式:饿汉模式和懒汉模式。

5.2饿汉模式

饿汉模式指的是一开始就创建对象。
既然要求只能有一个全局的对象,就毫无疑问要把构造函数设为私有
然后设置一个静态私有的对象,设置一个静态公有的成员函数,以便间接对对象进行操作。如下:

class InfoSingleton
{
public:
	static InfoSingleton& GetInstance()
	{
		return _sins;
	}
	void Insert(string name, int n)
	{
		_info[name] = n;
	}
	void Print() const
	{
		for (auto e : _info)
		{
			cout << e.first << ": " << e.second << endl;
		}
	}
private:
	InfoSingleton() {}
	map<string, int> _info;
private:
	static InfoSingleton _sins;
};

InfoSingleton InfoSingleton::_sins;

int main()
{
	InfoSingleton::GetInstance().Insert("张三", 100);
	InfoSingleton& info = InfoSingleton::GetInstance();
	info.Insert("李四", 200);
	info.Print();
	return 0;
}

结果如下:
在这里插入图片描述
但是,这并不是真正的单例,因为上述代码中还可以使用拷贝构造(默认生成)。所以,还需要用其他方法使拷贝构造禁止使用。如下:

class InfoSingleton
{
public:
	static InfoSingleton& GetInstance()
	{
		return _sins;
	}
	void Insert(string name, int n)
	{
		_info[name] = n;
	}
	void Print() const
	{
		for (auto e : _info)
		{
			cout << e.first << ": " << e.second << endl;
		}
	}
private:
	InfoSingleton() {}
	map<string, int> _info;
	InfoSingleton(const InfoSingleton& info) = delete;
	InfoSingleton& operator=(const InfoSingleton& info) = delete;
private:
	static InfoSingleton _sins;
};

饿汉模式的缺点:

  • 程序启动时间延长:因为在类加载时就创建了实例,所以会增加程序的启动时间。这可能在大型程序中产生明显的启动延迟,特别是当实例的初始化需要较长时间时。
  • 内存浪费:在整个程序执行期间,该实例一直存在于内存中,即使在一些情况下没有被使用。这可能导致内存的浪费,特别是如果该实例占用较大的资源或数据。
  • 懒加载无效:饿汉模式无法实现懒加载(延迟加载),即只在需要的时候才创建实例。如果实例的创建和初始化对资源消耗较大,但有些情况下并不需要使用该实例,就会造成资源浪费。
  • 不支持动态配置:饿汉模式在程序运行时无法动态地改变实例的创建和初始化逻辑。如果需要根据运行时的需求来动态配置实例,就无法满足这个需求。

5.3懒汉模式

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
跟饿汉模式不同在于,它不是一开始就创建对象,而是在第一次获取单例对象时创建对象。在main函数之后才会创建,不会影响启动速度。如下:

class InfoSingleton
{
public:
	static InfoSingleton& GetInstance()
	{
		//第一次获取单例对象的时候创建对象
		if (_sins == nullptr)
		{
			_sins = new InfoSingleton;
		}
		return *_sins;
	}
	void Insert(string name, int n)
	{
		_info[name] = n;
	}
	void Print() const
	{
		for (auto e : _info)
		{
			cout << e.first << ": " << e.second << endl;
		}
	}
private:
	InfoSingleton() {}
	map<string, int> _info;
	InfoSingleton(const InfoSingleton& info) = delete;
	InfoSingleton& operator=(const InfoSingleton& info) = delete;
private:
	static InfoSingleton* _sins;
};

但是上面的代码还是存在线程安全的问题,当多个县城一起调用GetInstance时,会有风险。所以,应该对上面的代码加锁保证安全。如下:

class InfoSingleton
{
public:
	static InfoSingleton& GetInstance()
	{//双检查加锁,针对第一次创建对象,避免每次都加锁
		if (_sins == nullptr)
		{
			//第一次获取单例对象的时候创建对象
			std::lock_guard<mutex> lock(_smtx);
			if (_sins == nullptr)
			{
				_sins = new InfoSingleton;
			}
		}
		return *_sins;
	}
	// 实现一个内嵌垃圾回收类
	class CGarbo 
	{
	public:
		CGarbo()
		{
			if (Singleton::m_pInstance)
			delete Singleton::m_pInstance;
		}
	};
	// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
	static CGarbo Garbo;
	void Insert(string name, int n)
	{
		_info[name] = n;
	}
	void Print() const
	{
		for (auto e : _info)
		{
			cout << e.first << ": " << e.second << endl;
		}
	}
private:
	InfoSingleton() {}
	map<string, int> _info;
	InfoSingleton(const InfoSingleton& info) = delete;
	InfoSingleton& operator=(const InfoSingleton& info) = delete;
private:
	static InfoSingleton* _sins;
	static mutex _smtx;
};
InfoSingleton* InfoSingleton::_sins= nullptr;
InfoSingleton::CGarbo Garbo;
mutex InfoSingleton::_smtx;

本篇完!青山不改,绿水长流!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/760598.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java】理解java传参总是采用值传递/按值调用

参考《Java核心技术卷Ⅰ》 按值调用 Java所有的方法传参都是按值调用&#xff0c;方法得到的是所有参数值的一个拷贝。 方法参数的类型 方法参数共有两种类型 基本数据类型&#xff08;数字、布尔值&#xff09;对象引用 一个内部方法不可能基本数据类型&#xff0c;但能通…

Qt6 Qt Quick UI Prototype学习QML第一篇

Qt6 Qt Quick UI原型学习QML第一篇 开始创建项目Qt Quick UI原型简介.qmlproject文件举例Window平台小例子运行效果QML语法 了解语法 开始创建项目 创建一个具有QML入口点的Qt Quick 2 UI项目。要使用它&#xff0c;您需要设置一个QML运行时环境&#xff0c;例如gmlscene。 仅当…

学C的第二十六天【指针的进阶(二)】

相关代码gitee自取&#xff1a;C语言学习日记: 加油努力 (gitee.com) 接上期&#xff1a; 学C的第二十五天【指针的进阶&#xff08;一&#xff09;】_高高的胖子的博客-CSDN博客 6 . 函数指针数组 &#xff08;1&#xff09;. 含义&#xff1a; 函数指针数组 是一个数组&a…

CSDN 周赛 63 期

CSDN 周赛 63 期 参赛体验63期周赛题目题目名称:小玉家的电费题目名称:收件邮箱题目名称:饿龙咆哮-逃离城堡题目名称:寻找宝藏山小结参赛体验 这次是最近难得的四个编程题的比赛了,可惜出了一个骗分题。 然后,今天早上进入考试,还好几次,连进都进不去 嗯,最后是开着…

开源代码分享(7)—考虑电动汽车可调度潜力的充电站两阶段市场投标策略(附matlab代码)

[1]詹祥澎,杨军,韩思宁等.考虑电动汽车可调度潜力的充电站两阶段市场投标策略[J].电力系统自动化,2021,45(10):86-96. 摘要&#xff1a;在电力市场环境下,充电站优化投标策略能降低电力成本&#xff0c;甚至通过售电获取收益。文中考 虑了电动汽车成为柔性储荷资源的潜力&#…

android 下载源码 一路踩坑

python 从 2.0 升级到3.0 从官网下载 pyhon3.0 安装器,然后更改配置 # Setting PATH for Python 3.8# The original version is saved in .bash_profile.pysaveexport PATH"/Library/Frameworks/Python.framework/Versions/3.11/bin:$PATH"alias python"/Libr…

h5真机调试之ios和Android和vconsole

目录 1&#xff1a;h5真机调试之Android01&#xff1a;安卓端 小米11开启开发者模式02&#xff1a;Android edge 2&#xff1a;h5真机调试之ios1&#xff1a;iOS Safari 3&#xff1a;真机链接本地项目 之 Android &#xff08; 重点 &#xff09;3-1 vconsole的安装与使用 (…

计算机服务器中了360后缀勒索病毒怎么办,勒索病毒解密数据恢复

计算机服务器被360后缀勒索病毒攻击&#xff0c;会给企业的正常生产运转带来极大麻烦&#xff0c;最近&#xff0c;我们收到很多企业的求助&#xff0c;企业的服务器被360后缀勒索病毒攻击。360后缀勒索病毒是BeijingCrypt勒索家族的一种病毒&#xff0c;该病毒通过远程桌面攻击…

57、mysql执行计划怎么看

mysql执行计划怎么看 执行计划就是sql的执行查询的顺序&#xff0c;以及如何使用索引查询&#xff0c;返回的结果集的行数 EXPLAIN SELECT * from A where X? and Y?1、id: 是一个有顺序的编号&#xff0c;是查询的顺序号&#xff0c;有几个 select 就显示几行。id的顺序是…

[java安全]CommonsCollections1(LazyMap)

文章目录 【java安全】CommonsCollections1(LazyMap)前言LazyMap如何创建LazyMap对象&#xff1f;如何调用LazyMap的get()方法&#xff1f;如何触发AnnotationInvocationHandler#invoke()方法&#xff1f;POC总结参考 【java安全】CommonsCollections1(LazyMap) 前言 前面我们…

索尼ILCE-7SM3覆盖部分恢复案例

ILCE-7SM3&#xff0c;算是索尼的经典之作&#xff0c;目前市场占用率相当高。下边这个案例就是格式化后又拍摄了不少素材导致覆盖产生的典型情况。 故障存储:120G SD卡 故障现象: 拍摄素材后备份数据&#xff0c;设备交付其他人使用&#xff0c;结果发现少备份一条比较大的…

【grasshopper】【犀流堂】【算法】Anemone雨水径流模拟-笔记

文章目录 Anemone雨水路径模拟available options可用选项grasshopper面切线几何原理 案例1&#xff1a;surface地形1. 拾取地形曲面surface2. 曲面上根据divide surface划分点points3.将曲面上的划分点用surface closest point投影到曲面上4.align plane旋转平面x轴与世界Z夹角…

git 工具使用--分支管理

git 工具使用–分支管理 文章目录 git 工具使用--分支管理理解分支创建分支切换分支合并分支删除分支合并冲突分支管理策略分支策略bug分支删除临时分支总结 理解分支 分支管理是Git的杀手级功能之一。分支&#xff1a;就是科幻中的平行宇宙&#xff0c;当你正在电脑面前学习C…

7.15-7.16枚举题目精讲

枚举 A - TogetherB - Fractions Again? A - Together 题目描述 题意分析 题意&#xff1a;给出n个数&#xff0c;可以对每一个数做三种操作a&#xff0c;a1,a-1&#xff0c;求所有操作完成后出现次数最多的数的个数。 分析&#xff1a;对于每个数枚举三种操作后的值&#xf…

神经网络初识-以MINST数据集和CIFAR10数据集为例

文章目录 1 什么是神经网络1.1 神经元模型1.2 感知机1.3 多层神经网络1.4 为什么要使用神经网络 2 全连接神经网络2.1 简介2.2 梯度下降2.2 反向传播2.3 代码实现 3 卷积神经网络3.1 简介3.2 代码实现 总结 1 什么是神经网络 人工神经网络&#xff08;artificial neural netwo…

【论文精读】Vis-MVSNet: Visibility-aware Multi-view Stereo Network

今天属于是重读经典了&#xff0c;这是一篇发表在BMVC2020上的文章&#xff0c;试图解决MVS中可见性的问题。该文章最近在拓展之后被发表在了IJCV上。本文的解读是基于扩展之后的IJCV版本&#xff0c;期刊的版本内容更加详细一点。 文章链接&#xff1a;BMVC2020版本和IJCV版本…

【电子学会】2023年05月图形化四级 -- 绘制同心圆

绘制同心圆 1. 准备工作 &#xff08;1&#xff09;保留小猫角色&#xff1b; &#xff08;2&#xff09;背景为默认白色。 2. 功能实现 &#xff08;1&#xff09;点击绿旗&#xff0c;小猫询问“请问绘制几个同心圆&#xff1f;”&#xff1b; &#xff08;2&#xff09;…

044、TiDB特性_PlacementPolicy

Placement Rules in SQL之前 跨地域部署的集群&#xff0c;无法本地访问无法根据业务隔离资源难以按照业务登记配置资源和副本数 Placement Rules in SQL之后 跨地域部署的集群&#xff0c;支持本地访问根据业务隔离资源按照业务等级配置资源和副本数 配置 labels 设置 Ti…

这个怎么弄?电脑没有d3dx9_43.dll?

在使用某些电脑软件或游戏时&#xff0c;遇到这样的提示&#xff1a;找不到d3dx9_43.dll&#xff0c;无法继续执行代码。这个问题比较常见&#xff0c;很多人不知道该怎么解决。如果你也遇到这个问题&#xff0c;不要着急。本文将详细介绍如何解决找不到d3dx9_43.dll无法继续执…

计算机网络基础第四章

一、网络层概述 1.1 网络层功能概述 主要任务是把分组从源端传到目的端&#xff0c;为分组交换网上的不同主机提供通信服务。网络层传输单位是数据报。 功能一&#xff1a;路由选择与分组转发&#xff08;最佳路径&#xff09;功能二&#xff1a;异构网络互联功能三&#xf…