【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.hqwc.cn/news/209421.html

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

相关文章

echarts的横向柱状图文字省略,鼠标移入显示内容 vue3

效果图 文字省略 提示 如果是在x轴上的&#xff0c;就在x轴上添加triggerEvent: true,如果是y轴就在y轴添加&#xff0c;我是在y轴上添加的 并且自定义的方法&#xff08;我取名为extension&#xff09; // echarts 横向省略文字 鼠标移入显示内容 export const extension…

三柱汉诺塔

题目描述 汉诺塔是约19世纪末&#xff0c;在欧州的商店中出售一种智力玩具。它的结构如下图所示&#xff1a; 在一个平板上立有三根铁针&#xff0c;分别记为A, B, C。开始时&#xff0c;铁针 A 上依次叠放着从大到小 n 个圆盘&#xff0c;游戏的目标就是将 A 上的 n 个圆盘…

分布式篇---第一篇

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

状态设计模式是什么?什么是 State 状态设计模式?Python 状态设计模式示例代码

什么是 State 状态设计模式&#xff1f; 状态设计模式是一种行为型设计模式&#xff0c;它允许一个对象在其内部状态发生改变时改变其行为&#xff0c;使其看起来好像改变了其类。状态模式主要解决的问题是&#xff1a;当一个对象的行为取决于它的状态&#xff0c;并且在运行时…

CQ 社区版 V2.6.0 发布 | SQL闪回、权限看板、新增数据源人大金仓等

前言 HELLO&#xff0c;大家好&#xff0c;又到了 CloudQuery 社区版发版时间&#xff01;本次更新版本为 v2.6.0&#xff0c;亮点多多&#xff0c;我们直入主题一起来看&#xff01; 一、本期亮点 新增 3 种数据源支持 V2.6.0&#xff0c;新增三种国产数据源支持&#xff…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《交直流配电网中柔性软开关接入的规划-运行协同优化方法》

这个标题涉及到交直流配电网中柔性软开关接入的规划-运行协同优化方法。下面是对这个标题各部分的详细解读&#xff1a; 交直流配电网&#xff1a; 这指的是一个电力系统&#xff0c;同时包含交流和直流电力传输的元素。这样的系统可能结合了传统的交流电力传输和近年来兴起的直…

加工车间污水处理设备有哪些

在加工车间中&#xff0c;污水处理设备是至关重要的一部分。它们的功能是将污水进行处理&#xff0c;确保其达到符合环保标准的水质要求。以下是一些常见的加工车间污水处理设备&#xff1a; 1.初级沉淀池&#xff1a;初级沉淀池是最基本的污水处理设备之一。它通过重力作用将…

基于51单片机的病床呼叫系统设计

**单片机设计介绍&#xff0c; 基于51单片机的病床呼叫系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于51单片机的病床呼叫系统是一种用于医疗机构的设备&#xff0c;旨在提供快速、可靠的病人呼叫和监控功能。以下是…

苹果手机内存满了怎么清理?这里有你想要的答案!

手机内存不足是一个比较普遍的现象。由于现在手机应用程序的功能越来越强大&#xff0c;所以占用的内存也越来越大。同时用户会在手机中存储大量的数据&#xff0c;如照片、视频、文档等&#xff0c;这些都会占用大量的手机空间。那么&#xff0c;苹果手机内存满了怎么清理&…

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

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

NLP学习

参考&#xff1a;NLP发展之路I - 从词袋模型到Transformer - 知乎 (zhihu.com) NLP大致的发展历史。从最开始的词袋模型&#xff0c;到RNN&#xff0c;到Transformers和BERT&#xff0c;再到ChatGPT&#xff0c;NLP经历了一段不断精进的发展道路。数据驱动和不断完善的端到端的…