C++的特殊类设计 饥饿汉模式

目录

特殊类设计

设计一个不能被拷贝的类

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

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

设计一个不能继承的类

设计模式

单例模式

饿汉模式

饥汉模式


特殊类设计

设计一个不能被拷贝的类

C++98的设计方式:将该类的拷贝构造和赋值运算符重载函数均只声明不定义,并将它们的访问权限设置为私有

class CopyBan
{
    // ...

//设置为私有
private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};
  • 只声明不定义:本就不会使用再定义没意义,且如果有定义的话虽然设置了private,但只是为了防外部人调用的,但类内部的其它成员还可以使用拷贝构造和赋值重载

template<class T>
class SmartPtr
{
public:
    // RAII
    SmartPtr(T* ptr)
	    :_ptr(ptr)
    {}

    //...
    void Printf()
    {
	    SmartPtr<int> s1(new int(2));
	    SmartPtr s2(s1);
    }

private:

    //拷贝构造
    SmartPtr(const SmartPtr& s)
    {
    	cout << "SmartPtr(const SmartPtr& s)" << endl;
    }

    T* _ptr;
};
  • 设置为private: 只声明不设置为private,外部用户就可以在类外实现这两个函数的定义
class Example {
public:
	Example() {};

	//只声明不定义
	Example(const Example& e);//拷贝构造
	Example& operator=(const Example& e);//赋值重载
};

// 在类外部定义 拷贝构造 和 赋值重载
Example::Example(const Example& e)
{
	cout << "Example(const Example& e)\n";
}

Example& Example::operator=(const Example& e)
{
	cout << "Example& Example::operator=(const Example& e)\n";
	return *this;
}

int main()
{	// 使用类外定义的 拷贝构造 和 赋值重载
	Example e1;
	Example e2(e1);

	e2 = e1;
	return 0;
}

C++11的设计方式:使用=delete的方法,在成员函数后加上=delete,那么编译器就不会生成该成员函数的定义(不需要再使用private限制了,但仍需声明)

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

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

设计方式:将构造函数设为私有,防止用户在类外调用构造函数从而在栈上生成对象,然后再定义一个public权限的静态成员函数,该函数用于在堆上创建该类的对象并返回该对象的指针

补充:还需要将拷贝构造和赋值重载也设置为只声明不定义 + 访问权限为private 或者 =delete,防止用户调用默认的拷贝构造在栈上生成该类的对象

class HeapOnly
{
public:
    //静态成员函数:可以直接通过类名调用,而不需要类的对象实例
    static HeapOnly* CreatObj()
    {
        return new HeapOnly;//在堆上new一个HeapOnly类型的匿名对象,并返回该对象的指针   
    }
    HeapOnly(const HeapOnly& e)=delete;
    HeapOnly& operator=(const HeapOnly& e)=delete;
private:

    HeapOnly() {};//默认构造函数
 
};

int main()
{
    //HeapOnlye1;//错误,不可访问
    //HeapOnly* e2 = CreatObj();//错误,找不到该函数
    HeapOnly* e2 = HeapOnly::CreatObj();

    //HeapOnlye3(*e2);//尝试调用拷贝构造在栈上生成一个该类的对象
    return 0;
}
  • 还可以加上多参数模板,实现传递多个参数进行构造,当然构造函数也要提供合适的版本

class HeapOnly
{
public:
    template<class... Args>
    static HeapOnly* CreateObj(Args&&... args)
    {
	    return new HeapOnly(args...);
    }

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

private:

//无参构造
HeapOnly()
{}

//接收两个参数的构造函数
HeapOnly(int x, int y)
	:_x(x)
	,_y(y)
{}

};

int main()
{
	HeapOnly* ho3 = HeapOnly::CreateObj();
	HeapOnly* ho4 = HeapOnly::CreateObj(1,1);
    return 0;
}

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

设计方式:构造函数设为私有,设计一个合适的静态成员函数,拷贝构造不用=delete限制、赋值重载需要=delete限制

class StackOnly
{
public:
    template<class... Args>
    static StackOnly CreateObj(Args&&... args)
    {
        return StackOnly(args...);//用于返回在栈上创建的一个匿名对象的静态成员函数,返回类型不是StackOnly*而是StackOnly 
    }

    //只封住了赋值重载
    StackOnly& operator=(const StackOnly&) = delete;

private:
    //无参默认构造函数
    StackOnly() 
    {
        cout << "StackOnly()" << endl;
    };
    StackOnly(int x,int y)//支持两个参数的默认构造函数
        :_x(x)
        ,_y(y)
    {
        cout << "StackOnly(int x,int y)" << endl;
    };
    int _x;
    int _y;
};

int main()
{
    StackOnly ho1 = StackOnly::CreateObj();
    StackOnly ho2 = StackOnly::CreateObj(1, 1);

    StackOnly* ho3 = new StackOnly(ho1);//没有=delete拷贝构造,就可以使用系统提供的默认拷贝构造
    //通过反汇编可以看到是先new在堆上分配了一个8字节大小的内存,然后将调用默认拷贝构造生成的匿名对象放入该内存中,最后返回该对象的地址给ho3
    return 0;
}
  • new + 拷贝构造也可以在堆上创建对象,所以我们可以直接重写一个new,并=delete该new
//重载一个类专属的operator new,此时再去new StackOnly就不会去调用全局的operator new
void* operator new(size_t n) = delete;

设计一个不能继承的类

C++98的设计方式:构造函数私有化,派生类中调不到基类的构造函数。则无法继承

class NonInherit
{
public:
    static NonInherit GetInstance()
    {
        return NonInherit();
     }
private:

    NonInherit()
     {}
};

C++11的设计方式:final关键字,final修饰一个类,表示该类不能被继承

class A  final
{
    // ....
};

设计模式

基本概念:设计模式是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结,使用设计模式的目的是为了代码的可重用性、让代码更容易被他人理解、保证代码可靠性,设计模式使得代码编写真正工程化

单例模式

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

饿汉模式

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

namespace hunger 
{
	//饿汉:执行main之前就创建出一个对象
	class Singleton
	{
	public:
		static Singleton* GetInstance()
		{
			return &_sint;//返回静态成员对象的地址
		}

		void Print()
		{
			cout << _x << _y << endl;

			for (auto& e : _vstr)
			{
				cout << e << " ";
			}
			cout << endl;
		}

		//修改数据
		void AddStr(const string& s)
		{
			_vstr.push_back(s);
		}

		//将拷贝构造和赋值重载禁掉,防止用户*指针然后调用拷贝构造新建对象
		Singleton(Singleton const&) = delete;
		Singleton& operator=(Singleton const&) = delete;

	private:
		//构造函数:数据被放入数组中存放
		Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
			:_x(x)
			,_y(y)
			,_vstr(vstr)
		{}

		// 想让一些数据,当前程序只有一份,可以把这些数据放到一个类里,然后再把这个类设计成单例,这样数据就只有一份了
		int _x;
		int _y;
		vector<string> _vstr;

		//类的静态成员对象,属于该类实例化出的所有对象,存在于静态区,在类中声明,使用时受类域限制(加类名)
		static Singleton _sint;

	};

	Singleton Singleton::_sint(1, 1, { "陕西","四川" });
}

int main()
{
	hunger::Singleton::GetInstance()->Print();
	hunger::Singleton::GetInstance()->AddStr("甘肃");
	hunger::Singleton::GetInstance()->Print();
	hunger::Singleton::GetInstance()->AddStr("甘肃");
	hunger::Singleton::GetInstance()->Print();
	return 0;
}

缺点1:影响程序启动速度,若单例对象数据过多,构造静态成员对象的成本变高,导致迟迟进不了main函数(长时间不登录微信,拉取消息时很慢,可以通过多线程解决,比如用于拉取群聊消息的单例是一个线程,用于拉去单个用户消息的单例是一个线程,那么拉取群聊消息的单例的初始化速度缓慢不会影响拉取单个用户消息的单例的初始化速度)

缺点2:多个单例类有初始化启动依赖关系,饿汉无法控制(A和B两个单例,若要求A先初始化,B再初始化,饿汉无法保证)

缺点3:无法处理异常,在饿汉模式中,如果单例对象在实例化时抛出异常,整个类加载过程都会失败

饥汉模式

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

namespace lazy
{
	class Singleton
	{
	public:
		static Singleton* GetInstance()
		{
			// 第一次调用时,创建单例对象,第二次时_psint就不为空,进入该函数也只是返回_psint
			//存在线程安全问题,需要加锁
			if (_psint == nullptr)
			{
				_psint = new Singleton;//可能需要释放
			}

			return _psint;
		}

		static void DelInstance()
		{
			//释放 + 置空
			if (_psint)
			{
				delete _psint;
				_psint = nullptr;
			}
		}

		void Print()
		{
			cout << _x << endl;
			cout << _y << endl;

			for (auto& e : _vstr)
			{
				cout << e << " ";
			}
			cout << endl;
		}

		void AddStr(const string& s)
		{
			_vstr.push_back(s);
		}

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

	private:
		Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
			:_x(x)
			, _y(y)
			, _vstr(vstr)
		{}

		~Singleton()
		{
			// 把数据写到文件
			cout << "~Singleton()" << endl;
		}


		int _x;
		int _y;
		vector<string> _vstr;
		static Singleton* _psint;

		// 内部类,用于防止用户忘记显示调用DelInstance释放对象
		//实例化一个静态内部类成员对象(全局生命周期),当程序结束时就会调用GC类对象的析构进而调用DelInstance
		class GC
		{
		public:
			~GC()
			{
				Singleton::DelInstance();
			}
		};
		static GC gc;//不实例化GC类对象,该类没用不会调用该类的析构函数
	};

	// 两个静态成员对象在类外的定义
	Singleton* Singleton::_psint = nullptr;
	Singleton::GC Singleton::gc;//什么都不做
}

int main()
{
	lazy::Singleton::GetInstance()->Print();
	lazy::Singleton::GetInstance()->AddStr("甘肃");
	lazy::Singleton::GetInstance()->Print();
	lazy::Singleton::GetInstance()->AddStr("甘肃");
	lazy::Singleton::GetInstance()->Print();

	//lazy::Singleton::DelInstance();//显示调用DelInstance可以释放,不显示也可以(注释和非注释两次运行试一试即可)
	return 0;
}

补充:也可以选择不用上述的内部类和指针的情况(需要注意显示调用和释放),而是在GetInstance中定义一个局部的静态成员对象,该成员会在第一次调用GetInstance函数时构造初始化一次且仅有一次(局部静态成员对象只能被初始化一次),但只有在C++11及之后才能使用

static Singleton* GetInstance()
{
	// 局部的静态对象,第一次调用函数时构造初始化
	// C++11及之后这样写才可以
	// C++11之前无法保证这里的构造初始化是线程安全
	static Singleton _sinst;
	return &_sinst;
}

C/C++ 中 static 的用法全局变量与局部变量 | 菜鸟教程 (runoob.com) 

~over~

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UDS服务——RequestTransferExit(0x37)

诊断协议那些事儿 诊断协议那些事儿专栏系列文章,本文介绍RequestTransferExit(0x37)—— 请求传输退出,用于终止数据传输的(上传/下载)。通过阅读本文,希望能对你有所帮助。 文章目录 诊断协议那些事儿请求传输退出服务介绍一、服务请求报文定义transferRequestParame…

[SAP ABAP] 删除内表数据

1.利用索引删除数据 语法格式 DELETE <itab> INDEX <idx>. <itab>&#xff1a;代表内表 <idx>&#xff1a;代表索引值 删除内表<itab>中的第<idx>条记录 示例1 lt_student内表中存在3条数据记录 我们使用如下指令删除内表中的第一条数…

AIGC-Animate Anyone阿里的图像到视频 角色合成的框架-论文解读

Animate Anyone: Consistent and Controllable Image-to-Video Synthesis for Character Animation 论文:https://arxiv.org/pdf/2311.17117 网页:https://humanaigc.github.io/animate-anyone/ MOTIVATION 角色动画的目标是将静态图像转换成逼真的视频&#xff0c;这在在线零…

爬虫逆向实战(41)-某花顺登陆(Cookie、MD5、SHA256)

一、数据接口分析 主页地址&#xff1a;某花顺 1、抓包 通过抓包可以发现在登陆时&#xff0c;网站首先请求了pwdRangeCalcRegular.json、getGS两个接口&#xff0c;接着请求dologinreturnjson2进行登陆&#xff0c;但是此接口会返回请先完成滑块验证码校验的响应。然后网站…

C/C++ - 编码规范(USNA版)

[IC210] Resources/C Programming Guide and Tips 所有提交的评分作业&#xff08;作业、项目、实验、考试&#xff09;都必须使用本风格指南。本指南的目的不是限制你的编程&#xff0c;而是为你的程序建立统一的风格格式。 * 这将有助于你调试和维护程序。 * 有助于他人&am…

什么是慢查询——Java全栈知识(26)

1、什么是慢查询 慢查询&#xff1a;也就是接口压测响应时间过长&#xff0c;页面加载时间过长的查询 原因可能如下&#xff1a; 1、聚合查询 2、多表查询 3、单表数据量过大 4、深度分页查询&#xff08;limit&#xff09; 如何定位慢查询&#xff1f; 1、Skywalking 我们…

FPGA学习网站推荐

FPGA学习网站推荐 本文首发于公众号&#xff1a;FPGA开源工坊 引言 FPGA的学习主要分为以下两部分 语法领域内知识 做FPGA开发肯定要首先去学习相应的编程语言&#xff0c;FPGA开发目前在国内采用最多的就是使用Verilog做开发&#xff0c;其次还有一些遗留下来的项目会采用…

C++系列-String(二)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” #define _CRT_SECURE_NO_WARNINGS #include<string> #include<iostream> #include<list> #include<algorithm> using namespace std; void test_string…

【pytorch05】索引与切片

索引 a[0,0]第0张图片的第0个通道 a[0,0,2,4]第0张图片&#xff0c;第0个通道&#xff0c;第2行&#xff0c;第4列的像素点&#xff0c;dimension为0的标量 选择前/后N张图片 a[:2,:1,:,:].shape前两张图片&#xff0c;第1个通道上的所有图片的数据 a[:2,1:,:,:].shape前两张…

初识 SpringMVC,运行配置第一个Spring MVC 程序

1. 初识 SpringMVC&#xff0c;运行配置第一个Spring MVC 程序 文章目录 1. 初识 SpringMVC&#xff0c;运行配置第一个Spring MVC 程序1.1 什么是 MVC 2. Spring MVC 概述2.1 Spring MVC 的作用&#xff1a; 3. 运行配置第一个 Spring MVC 程序3.1 第一步&#xff1a;创建Mave…

PyCharm连接gitlab

遇到PyCharm不支持特定GitLab服务器版本的问题时&#xff0c;使用命令行工具&#xff08;如Git&#xff09;来连接和操作远程GitLab仓库是一种常见且有效的方法。以下是使用命令行连接远程GitLab仓库的基本步骤&#xff1a; 准备工作 确保已安装Git&#xff1a;首先&#xff0…

Bandzip:打破压缩界限,文件管理更高效

名人说&#xff1a;&#xff1a;一点浩然气&#xff0c;千里快哉风。 ——苏轼 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、软件介绍1、Bandzip2、核心特点 二、下载安装1、下载2、安装 三、使用方法 很高兴…

SQL找出所有员工当前薪水salary情况

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 有一个薪水表…

康奈尔大学之论文审稿模型Reviewer2及我司七月对其的实现(含PeerRead)

前言 自从我司于23年7月开始涉足论文审稿领域之后「截止到24年6月份&#xff0c;我司的七月论文审稿GPT已经迭代到了第五版&#xff0c;详见此文的8.1 七月论文审稿GPT(从第1版到第5版)」&#xff0c;在业界的影响力越来越大&#xff0c;所以身边朋友如发现业界有相似的工作&a…

示例:推荐一个应用Adorner做的通知和提示消息对话框

一、目的&#xff1a;在开发过程中&#xff0c;增加一些提示消息可以很好的提高用户体验&#xff0c;下面介绍一个用于增加提示消息的库 二、效果如下 可以看到右侧顶端弹出提示消息&#xff0c;消息间隔3s自动退出 三、环境 VS2022 Net7 四、使用方式 安装nuget包&#xff…

SLAM Paper Reading和代码解析

最近对VINS、LIO-SAM等重新进行了Paper Reading和代码解析。这两篇paper和代码大约在三年前就读过&#xff0c;如今重新读起来&#xff0c;仍觉得十分经典&#xff0c;对SLAM算法研发具有十分重要的借鉴和指导意义。重新来读&#xff0c;对其中的一些关键计算过程也获得了更新清…

【QT】

通信服务端实现 widget.h文件 #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QTcpServer>//服务器类 #include <QMessageBox>//消息 #include <QTcpServer> #include <QList> #include <QTcpSocket> QT_BEGIN_NAMESPAC…

一、企业级架构设计-archimate基础概念

目录 一、标准 二、实现工具 1、Archimate 1、Archimate 基本概念 1、通用元模型 2、结构关系 3、依赖关系 1、服务关系 2、访问关系 3、影响关系 1、影响方式 2、概念 3、关系线 4、案例 4、关联关系 4、动态、节点和其他关系 1、时间或因果关系 2、信息流 …

JavaScript的学习之旅之初始JS

目录 一、认识三个常见的js代码 二、js写入的第二种方式 三、js里内外部文件 一、认识三个常见的js代码 <script>//写入js位置的第一个地方// 控制浏览器弹出一个警告框alert("这是一个警告");// 在计算机页面输入一个内容&#xff08;写入body中&#xff…

运算放大器(运放)低通滤波反相放大器电路和积分器电路

低通滤波反相放大器电路 运放积分器电路请访问下行链接 运算放大器(运放)积分器电路 设计目标 输入ViMin输入ViMax输出VoMin输出VoMaxBW&#xff1a;fp电源Vee电源Vcc–0.1V0.1V–2V2V2kHz–2.5V2.5V 设计说明 这款可调式低通反相放大器电路可将信号电平放大 26dB 或 20V/…