【C++】特殊类设计 {不能被拷贝的类;只能在堆上创建的类;只能在栈上创建的类;不能被继承的类;单例模式:懒汉模式,饿汉模式}

一、不能被拷贝的类

设计思路:

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

C++98方案:
将拷贝构造与赋值重载只声明不定义,并且将其访问权限设置为私有即可。

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

原因:

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁止拷贝了。

  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

C++11方案:
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

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

二、只能在堆上创建的类

思路一:将构造、拷贝构造函数私有

  1. 将类的构造、拷贝构造声明成私有。
  2. 提供一个静态的成员函数,在该静态成员函数中使用new申请堆空间并调用构造函数完成堆对象的初始化,最后返回该对象的指针。
class HeapOnly
{int _val;// 把构造和拷贝构造设置成私有HeapOnly(int val = 0): _val(val){}// 一定要把拷贝构造也设为私有HeapOnly(const HeapOnly &obj);public:// 提供一个静态的成员函数,使用new申请堆空间并调用构造函数完成堆对象的创建。static HeapOnly *CreateObj(int val = 0){return new HeapOnly(val);}
};int main()
{// HeapOnly obj;HeapOnly *pobj1 = HeapOnly::CreateObj(10);// HeapOnly obj(*pobj1);return 0;
}

思路二:将析构函数私有

编译器在为类对象分配栈空间时,会先检查类的构造和析构函数的访问性。由于栈的创建和释放都需要由系统完成的,所以若是无法调用构造或者析构函数,自然会报错。如果类的析构函数是私有的,则编译器将报错。

当然为了我们能够释放动态创建的对象,我们必须提供一个公有函数,该函数的唯一功能就是删除堆对象。

  1. 将类的析构函数声明成私有。
  2. 提供一个公有的成员函数,执行delete this调用析构函数清理对象资源并释放堆空间。
class HeapOnly
{int _val;// 把析构设置成私有~HeapOnly(){cout << "~HeapOnly()" << endl;}public:HeapOnly(int val = 0): _val(val){}// 提供一个公有的成员函数,执行delete this调用析构函数清理对象资源并释放堆空间void DestroyObj(){delete this;}
};int main()
{// HeapOnly obj;HeapOnly *pobj = new HeapOnly(10);// HeapOnly obj(*pobj);// delete pobj;pobj->DestroyObj();return 0;
}

三、只能在栈上创建的类

思路:重载operator new

我们还可以将new操作符重载并设置为私有访问。

class StackOnly
{int _val;void* operator new(size_t t);
public:StackOnly(int val = 0): _val(val){}StackOnly(const StackOnly &obj): _val(obj._val){}
};int main()
{StackOnly obj(10);StackOnly obj1(obj);// StackOnly *pobj = new StackOnly(10);// StackOnly *pobj1 = new StackOnly(obj);return 0;
}

四、不能被继承的类

C++98方案:将构造函数私有

派生类中调不到基类的构造函数,则无法继承。

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

C++11方案:final关键字

final修饰类,表示该类不能被继承。

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

五、单例模式

5.1 设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的代码设计经验总结。

使用设计模式的目的:
为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

常用的设计模式:

  1. 适配器模式:对已有的类进行适配包装形成具有全新功能和性质的类,如:栈、队列、优先级队列、function包装器。
  2. 迭代器模式:几乎所有容器通用的遍历访问方式,可以封装隐藏容器的底层结构,以类似指针的使用方式访问容器中的数据。如:数组(vector)、链表(list)、哈希表(unordered_map)、树(map)的迭代器。
  3. 单例模式:接下来的内容
  4. 工厂模式:工厂模式是一种创建对象的设计模式,它通过定义一个工厂类来封装对象的创建过程,并通过调用工厂类的方法来创建对象,从而将对象的创建与使用分离。
  5. 观察者模式:观察者模式是一种对象间的一对多依赖关系,当一个对象的状态发生变化时,它的所有依赖者都会得到通知并自动更新。

单例模式:

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

5.2 饿汉模式

所谓饿汉模式,就是说不管你将来用不用,程序启动时(main函数之前)就创建一个唯一的实例对象。

方法一:在堆区创建单例

设计思路:

  1. 私有构造、拷贝构造和析构,保证系统中该类只有一个实例;
  2. 包含一个该类的静态指针并在类外使用new创建单例,提供一个访问单例的全局访问点;
  3. 包含一个互斥锁成员,保证多线程互斥访问该单例;
  4. 提供一个用于获取全局访问点(静态指针)的静态成员函数;
  5. 包含一个静态的内部类对象,该对象析构时会顺便析构单例,自动释放。
class Singleton
{// 成员变量vector<string> _dir;// 该类的静态指针,提供一个访问单例的全局访问点static Singleton *s_ins;// 互斥锁成员,保证多线程互斥访问该单例mutex s_mtx;// 静态的内部类对象,该对象析构时会顺便析构单例,自动释放struct GC{~GC(){if (s_ins != nullptr){delete s_ins;s_ins = nullptr;}}};static GC s_gc;// 私有构造、拷贝构造和析构,保证系统中该类只有一个实例Singleton(){cout << "Singleton()" << endl;};Singleton(const Singleton &st);~Singleton(){// 单例对象的析构一般会做一些持久化操作(数据落盘)// ......cout << "~Singleton()" << endl;}public:// 提供一个静态成员函数,用于获取全局访问点(静态指针)static Singleton *GetInstance(){return s_ins;}void Add(const string &name){s_mtx.lock();_dir.push_back(name);s_mtx.unlock();}void Print(){s_mtx.lock();for (auto &name : _dir){cout << name << endl;}s_mtx.unlock();}
};// 程序启动时(main函数之前)创建
Singleton *Singleton::s_ins = new Singleton;
Singleton::GC Singleton::s_gc;int main()
{// 系统中该类只有一个实例,不允许通过任何方式实例化// Singleton st;// static Singleton st1;// Singleton* pst = new Singleton;//  Singleton st(*(Singleton::GetInstance()));// 单线程场景// Singleton::GetInstance()->Add("张三");// Singleton::GetInstance()->Add("李四");// Singleton::GetInstance()->Add("王五");// Singleton::GetInstance()->Print();// 多线程场景int n = 6;srand((unsigned int)time(nullptr));thread t1([n]() mutable{while(n--){Singleton::GetInstance()->Add("线程1:" + to_string(rand()));this_thread::sleep_for(chrono::milliseconds(10));} });thread t2([n]() mutable{while(n--){Singleton::GetInstance()->Add("线程2:" + to_string(rand()));this_thread::sleep_for(chrono::milliseconds(10));} });t1.join();t2.join();Singleton::GetInstance()->Print();
}

运行结果(多线程场景):

在这里插入图片描述


方法二:在静态区创建单例

设计思路:

  1. 私有构造、拷贝构造和析构,保证系统中该类只有一个实例;
  2. 包含一个该类的静态对象并在类外定义,提供一个访问单例的全局访问点;
  3. 包含一个互斥锁成员,保证多线程互斥访问该单例;
  4. 提供一个用于获取全局访问点(静态对象的引用)的静态成员函数;
  5. 由于单例是在静态区创建的,进程结束时,系统会自动调用单例析构释放其资源。
// 饿汉模式2
class Singleton
{// 成员变量vector<string> _dir;// 该类的静态对象,提供一个访问单例的全局访问点static Singleton s_ins;// 互斥锁成员,保证多线程互斥访问该单例mutex s_mtx;// 私有构造、拷贝构造和析构,保证系统中该类只有一个实例Singleton(){cout << "Singleton()" << endl;};Singleton(const Singleton &st);// 由于单例是在静态区创建的,进程结束时,系统会自动调用单例析构释放其资源。~Singleton(){// 单例对象的析构一般会做一些持久化操作(数据落盘)// ......cout << "~Singleton()" << endl;}public:// 提供一个静态成员函数,用于获取全局访问点(静态对象的引用)static Singleton &GetInstance(){return s_ins;}void Add(const string &name){s_mtx.lock();_dir.push_back(name);s_mtx.unlock();}void Print(){s_mtx.lock();for (auto &name : _dir){cout << name << endl;}s_mtx.unlock();}  
};// 程序启动时(main函数之前)创建
Singleton Singleton::s_ins;

运行结果:同上

饿汉模式的缺点:

  1. 由于单例对象是在main函数之前创建的,如果单例对象很大,很复杂,其创建和初始化所占用的时间较多。会拖慢程序的启动速度。
  2. 如果当前进程暂时不需要使用该单例对象,而饿汉模式在启动时创建单例占用了空间和时间资源。
  3. 如果具有依赖关系的两个单例都是饿汉模式,需要先创建单例1再创建单例2。饿汉模式无法控制其创建和初始化顺序。

提示:饿汉模式的全局访问点除了定义静态指针还可以直接定义成静态对象。如果是静态对象,进程在退出时会自动调用其析构函数。


5.3 懒汉模式

如果单例对象的构造十分耗时或者占用很多资源,比如加载插件、 初始化网络连接、读取文件等等。而且有可能程序运行时不会用到该对象,如果也在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

所谓懒汉模式,就是在任意程序模块第一次访问单例时实例化对象。

方法一:在堆区创建单例

设计思路:

  1. 私有构造、拷贝构造和析构,保证系统中该类只有一个实例;
  2. 包含一个该类的静态指针并在类外初始化为nullptr,提供一个访问单例的全局访问点;
  3. 包含一个静态互斥锁并在类外定义,保证多线程互斥地创建和访问该单例;
  4. 提供一个静态成员函数,用于首次调用创建单例(注意双检查加锁)和获取全局访问点(静态指针);
  5. 包含一个静态的内部类对象,该对象析构时会顺便析构单例,自动释放。
// 懒汉模式
class Singleton
{// 成员变量vector<string> _dir;// 该类的静态指针,提供一个访问单例的全局访问点static Singleton *s_ins;// 静态互斥锁,保证多线程互斥地创建和访问该单例static mutex s_mtx;// 静态的内部类对象,该对象析构时会顺便析构单例,自动释放struct GC{~GC(){if (s_ins != nullptr){delete s_ins;s_ins = nullptr;}}};static GC gc;// 私有构造、拷贝构造和析构,保证系统中该类只有一个实例Singleton(){cout << "Singleton()" << endl;};Singleton(const Singleton &st);~Singleton(){// 单例对象的析构一般会做一些持久化操作(数据落盘)// ......cout << "~Singleton()" << endl;}
public:static Singleton *GetInstance(){// 懒汉模式:在第一次访问实例时创建// 双检查加锁if (s_ins == nullptr) // 第一道检查:提高效率,不需要每次获取单例都加锁解锁{s_mtx.lock();if (s_ins == nullptr) // 第二道检查:保证线程安全和只new一次{s_ins = new Singleton;}s_mtx.unlock();}return s_ins;}void Add(const string &name){s_mtx.lock();_dir.push_back(name);s_mtx.unlock();}void Print(){s_mtx.lock();for (auto &name : _dir){cout << name << endl;}s_mtx.unlock();}// 一般单例对象的生命周期随进程,系统会在进程退出时释放其内存,不需要中途析构单例对象// 不过在一些特殊场景下,可能需要进行显示手动释放static void DelInstance(){s_mtx.lock();if (s_ins != nullptr){delete s_ins;s_ins = nullptr;}s_mtx.unlock();}
};// 静态成员要在类外定义
Singleton *Singleton::s_ins = nullptr;
mutex Singleton::s_mtx;
Singleton::GC Singleton::gc;

运行结果(多线程场景):

在这里插入图片描述


方法二:在静态区创建单例(C++11)

设计思路:

  1. 私有构造、拷贝构造和析构,保证系统中该类只有一个实例;
  2. 提供一个静态成员函数,用于首次调用创建单例(创建静态局部对象)和获取全局访问点(静态对象的指针);
  3. 包含一个互斥锁成员,保证多线程互斥访问该单例;
  4. 由于单例是在静态区创建的,进程结束时,系统会自动调用单例析构释放其资源。
// 懒汉模式2
class Singleton
{// 成员变量vector<string> _dir;// 互斥锁成员,保证多线程互斥访问该单例mutex s_mtx;// 私有构造、拷贝构造和析构,保证系统中该类只有一个实例Singleton(){cout << "Singleton()" << endl;};Singleton(const Singleton &st);~Singleton(){// 单例对象的析构一般会做一些持久化操作(数据落盘)// ......cout << "~Singleton()" << endl;}public:static Singleton *GetInstance(){// C++11之前,这里不能保证初始化静态对象的线程安全问题// C++11之后,这里可以保证初始化静态对象的线程安全问题static Singleton s_ins; //首次调用时创建局部静态对象return &s_ins;}void Add(const string &name){s_mtx.lock();_dir.push_back(name);s_mtx.unlock();}void Print(){s_mtx.lock();for (auto &name : _dir){cout << name << endl;}s_mtx.unlock();}
};

运行结果:同上

懒汉模式模式完美解决了饿汉模式的问题,就是相对复杂一些。

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

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

相关文章

FDG6306P PowerTrench® MOSFET P沟道 特点及其应用详解

关于PowerTrench MOSFET&#xff1f; 它是一种MOS场效应晶体管&#xff0c;可以提高系统效率和功率密度。该技术采用了屏蔽栅极技术&#xff0c;可以减少开关损耗和导通损耗&#xff0c;从而提高了系统效率。此外&#xff0c;PowerTrench MOSFET还具有低导通电阻和高开关速度的…

前端js语音朗读文本

<!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>语音朗读</title></head><body>&l…

如何满足BMW EDI项目的PKT需求?

近期宝马BMW&#xff08;以下简称BMW&#xff09;在其部分供应商之间试点推进PKT项目&#xff0c;BMW为什么要启动 PKT 计划呢&#xff1f; 业务系统全面升级统一全球所有宝马工厂的流程 宝马内部的物流供货流程 近期BMW PKT需求主要针对其内部物流供货流程展开&#xff1a; …

嵌入式开发--赛普拉斯cypress的铁电存储器FM25CL64B

嵌入式开发–赛普拉斯cypress的铁电存储器FM25CL64B 简介 FM25CL64B是赛普拉斯cypress出品的一款铁电存储器&#xff0c;这种存储器最大的优势是可以像RAM一样随机存储&#xff0c;和按字节写入&#xff0c;也可以像ROM一样掉电仍然可以保存数据&#xff0c;是一种相当优秀的…

Redis 持久化机制

client Redis[内存] --> 内存数据、磁盘数据----> 磁盘&#xff0c;Redis官方提供了两种不同的持久化方案将内存中的数据存储在硬盘中&#xff1a; 快照&#xff08;Snapshot&#xff09; AOF只追加日志文件。 1、快照&#xff08;Snapshot&#xff09; 1、快照的特点…

如何用CHAT解释文章含义?

问CHAT&#xff1a;解释“ 本身乐善好施&#xff0c;令名远近共钦&#xff0c;待等二十左右&#xff0c;定有高亲可攀&#xff1b;而且四德俱备&#xff0c;帮夫之缘亦有。主持家事不紊&#xff0c;上下亦无闲言。但四十交进&#xff0c;家内谨防口舌&#xff0c;须安家堂&…

分布式篇---第一篇

系列文章目录 文章目录 系列文章目录前言一、分布式幂等性如何设计?二、简单一次完整的 HTTP 请求所经历的步骤?三、说说你对分布式事务的了解前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,…

非遗之光:十八数藏柏松数字保护的璀璨之路

随着数字技术的崛起&#xff0c;非物质文化遗产的保护进入了一个新的纪元。在这个时代的先锋中&#xff0c;十八数藏以其对传统工艺的数字保护而独领风骚。这是一条璀璨之路&#xff0c;通过数字技术的应用&#xff0c;为传统工艺注入了新的活力。 十八数藏柏松将数字创新融入传…

软件包管理器yum和git

目录 一、Linux软件包管理器yum 1、Linux下的软件安装方法 2、了解yum 1、实际例子引入 2、yum 3、查找软件包 4、安装软件包 5、卸载软件 二、git 一、Linux软件包管理器yum 1、Linux下的软件安装方法 1、在Linux下安装软件&#xff0c;一个通常的办法是下载到程序的源…

经典百搭女童加绒卫衣,看的见的时尚

经典版型套头卫衣 宽松百搭不挑人穿 单穿内搭都可以 胸口处有精美的小熊印花 面料是复合柔软奥利绒 暖和又不显臃肿哦&#xff01;&#xff01;

Jenkins+Maven+Gitlab+Tomcat 自动化构建打包、部署

JenkinsMavenGitlabTomcat 自动化构建打包、部署 1、环境需求 本帖针对的是Linux环境&#xff0c;Windows或其他系统也可借鉴。具体只讲述Jenkins配置以及整个流程的实现。 1.JDK&#xff08;或JRE&#xff09;及Java环境变量配置&#xff0c;我用的是JDK1.8.0_144&#xff0…

排序算法--快速排序

实现逻辑 ① 从数列中挑出一个元素&#xff0c;称为 “基准”&#xff08;pivot&#xff09;&#xff0c; ② 重新排序数列&#xff0c;所有元素比基准值小的摆放在基准前面&#xff0c;所有元素比基准值大的摆在基准的后面&#xff08;相同的数可以到任一边&#xff09;。在这…

2023年度openGauss标杆应用实践案例征集

标杆应用实践案例征集 2023 openGauss 数据库作为企业IT系统的核心组成部分&#xff0c;是数字基础设施建设的关键&#xff0c;是实现数据安全稳定的保障。openGauss顺应开源发展趋势&#xff0c;强化核心技术突破&#xff0c;着力打造自主根社区&#xff0c;携手产业伙伴共同…

【开源】基于JAVA的高校实验室管理系统

项目编号&#xff1a; S 015 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S015&#xff0c;文末获取源码。} 项目编号&#xff1a;S015&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 实验室类型模块2.2 实验室模块2.3 实…

PTA-用天平找小球

三个球A、B、C&#xff0c;大小形状相同且其中有一个球与其他球重量不同。要求找出这个不一样的球。 输入格式&#xff1a; 输入在一行中给出3个正整数&#xff0c;顺序对应球A、B、C的重量。 输出格式&#xff1a; 在一行中输出唯一的那个不一样的球。 输入样例&#xff…

内衣洗衣机哪些品牌质量好实惠?小型洗衣机全自动

现在洗内衣内裤也是一件较麻烦的事情了&#xff0c;在清洗过程中还要用热水杀菌&#xff0c;还要确保洗衣液是否有冲洗干净&#xff0c;还要防止细菌的滋生等等&#xff0c;所以入手一款小型的烘洗全套的内衣洗衣机是非常有必要的&#xff0c;专门的内衣洗衣机可以最大程度减少…

重磅!2023年两院院士增选名单公布

中国科学院 关于公布2023年中国科学院院士增选当选院士名单的公告 根据《中国科学院院士章程》《中国科学院院士增选工作实施办法&#xff08;试行&#xff09;》等规定&#xff0c;2023年中国科学院选举产生了59名中国科学院院士。 现予公布。 中国科学院 2023年11月22日…

设计模式——结构型模式

结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。 由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更…

机器学习算法(1)——简单线性回归

一、说明 在在这篇文章中&#xff0c;我们将学习我们的第一个机器学习算法&#xff0c;称为简单线性回归。这是一个重要的算法&#xff0c;因为当您可能正在学习第一个神经网络&#xff08;称为人工神经网络&#xff09;时&#xff0c;在此算法中学习的技术也适用于深度学习。我…

毕业设计ASP.NET 1400动漫公司网站【程序源码+文档+调试运行】

摘要 本系统将实现一个动漫公司网站&#xff0c;包括前台用户模块和后台管理员模块。前台用户模块主要包括最新动漫、注册登录、公司简介、公司新闻、动漫中心、联系我们和会员中心等功能。后台管理员模块包括用户管理、公司简介管理、公司新闻管理、动漫类别管理、动漫管理、…