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

文章目录

  • 一、设计一个类,不能被拷贝
    • 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.hqwc.cn/news/20993.html

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

相关文章

Python微实践 - 诗意书香,宋风雅韵

诗意书香&#xff0c;宋风雅韵&#xff0c;宋代的文人们或婉约&#xff0c;或豪放&#xff0c;为后世留下了不朽的文学遗产 —— 宋词。宋词本质上是用于合乐的歌词&#xff0c;词人在填词时用的曲调名即为词牌。各位读者在中学时期一定对“水调歌头”、“念奴娇”这些词牌名耳…

音视频绕不开的话题之WebRTC

什么是WebRTC&#xff1f; 闲来无事&#xff0c;我们今天探讨下音视频绕不开的一个话题&#xff1a;WebRTC。WebRTC之于音视频行业&#xff0c;无异于FFMpeg&#xff0c;可以说WebRTC的开源&#xff0c;让音视频行业大跨步进入发展快车道。 WebRTC是一个支持实时音视频通信的开…

软件高效自动化部署:华为云部署服务CodeArts Deploy

随着互联网、数字化的发展&#xff0c;公司机构与各类企业往往需要进行大量频繁的软件部署&#xff0c;部署设备类型多样&#xff0c;如&#xff1a;本地机器、云上裸金属服务器、云上虚拟机与容器等。 面对多种部署模式、分布式复杂运行环境&#xff0c;该如何用最短时间、高…

初识stm32

1、什么是单片机&#xff1f; 单片机&#xff08;Single-Chip Microcomputer&#xff09;是一种集成电路芯片&#xff0c;把具有数据处理能力的中央处 理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功 能&#xff08;可能还包括显示驱动电路、…

建议收藏 | 可视化ETL平台--Kettle

Kettle的介绍 学习目标 知道什么是ETL及Kettel是开源的ETL工具 了解kettle环境的安装流程 1.ETL介绍 ETL&#xff08;Extract-Transform-Load的缩写&#xff0c;即数据抽取、转换、装载的过程&#xff09;&#xff0c;对于企业或行业应用来说&#xff0c;我们经常会遇到各种…

python中的生成器(generator)

一、生成器 生成器是 Python 中非常有用的一种数据类型&#xff0c;它可以让你在 Python 中更加高效地处理大量数据。生成器可以让你一次生成一个值&#xff0c;而不是一次生成一个序列&#xff0c;这样可以节省内存并提高性能 二、实现generator的两种方式 python中的gener…

LeetCode·每日一题·2544. 交替数字和·模拟

作者&#xff1a;小迅 链接&#xff1a;https://leetcode.cn/problems/alternating-digit-sum/solutions/2341276/mo-ni-zhu-shi-chao-ji-xiang-xi-by-xun-ge-7fjq/ 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 著作权归作者所有。商业转载请联系作者获得授权&#…

QT使用同一按钮实现打开/关闭新窗口

QT使用同一按钮实现【打开/关闭】新窗口&#xff0c;实现方案如下&#xff1a; 使用一个全局状态变量记录窗口打开状态通过该状态实现新窗口的show和close 实现代码如下&#xff1a; #include "mainwindow.h" #include "ui_mainwindow.h" #include "…

C++ | 反向迭代器

目录 前言 一、基本框架 二、起始位置和结束位置 三、反向迭代器的自增与自减 四、反向迭代器的判断 五、list类的修改 六、单独设计反向迭代器类的意义 前言 反向迭代器实际上与我们前面的stack、queue、priority一样&#xff0c;都是适配器&#xff1b;我们可以通过正向…

信道编码---RS编码与译码原理

本文介绍了RS编码以及译码的原理。 本文的内容基本上都来自刘梦欣的《基于FPGA的RS编译码研究与设计》&#xff0c;大家可以通过知网找到这篇文章&#xff0c;链接在下面。对RS码的原理讲解非常清楚&#xff0c;如果要看的话可以结合第2和第3部分一起看更好懂。我的整理也是比较…

【采用有限元法技术计算固有频率和欧拉屈曲荷载】使用有限元法的柱子的固有频率和屈曲荷载(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Java----使用eureka进行注册连接(微服务简单实现)

当采用微服务架构时&#xff0c;各个业务流程被逐一分解&#xff0c;虽说是分解&#xff0c;但还是要进行连接的&#xff0c;最简单的就是使用http请求&#xff0c;将他们联系起来&#xff0c;通过给容器注入restTemplate&#xff0c;然后使用内置的方法进行请求&#xff0c;但…