【C++】特殊类设计

embedded/2025/2/3 1:16:08/

在这里插入图片描述

目录

  • 一、请设计一个类,不能被拷贝
  • 二、请设计一个类,只能在堆上创建对象
  • 三、请设计一个类,只能在栈上创建对象
  • 四、请设计一个类,不能被继承
  • 五、请设计一个类,只能创建一个对象(单例模式)
    • 5.1 饿汉模式
    • 5.2 懒汉模式
  • 结尾

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

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

  1. C++98

    私有 + 只声明不定义

    • 私有:若只声明不定义并且在没有私有的情况下,用户可以在外面进行定义
    • 只声明不定义:不声明,操作系统会生成默认的拷贝构造和拷贝复制函数,定义了就不能防止拷贝了。
  2. C++11

    在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

class BanCopy
{
public:// 默认构造函数BanCopy(){}// C++11 默认成员函数后跟上 = delete// 拷贝构造BanCopy(const BanCopy& bc) = delete;// 拷贝赋值BanCopy& operator=(const BanCopy& bc) = delete;private:// C++ 98 私有 + 只声明不定义// 若只声明不定义并且在没有私有的情况下,用户可以在外面进行定义/*BanCopy(const BanCopy& bc);BanCopy operator=(const BanCopy& bc);*/
};int main()
{BanCopy bc1;BanCopy bc2;BanCopy bc3(bc1);bc2 = bc1;return 0;
}

在这里插入图片描述

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

实现方式1:

  1. 构造函数只声明不定义并私有化,拷贝构造函数只声明不定义并私有化,防止通过拷贝构造在栈上创建对象。
  2. 定义一个静态函数,用来提供在堆上创建对象。
class OnlyHeap
{
public:static OnlyHeap* CreateObject(){return new OnlyHeap;}private:// 默认构造OnlyHeap(){}// 拷贝构造OnlyHeap(const OnlyHeap& oh){}
};int main()
{OnlyHeap oh1;OnlyHeap* oh2 = OnlyHeap::CreateObject();OnlyHeap oh3(*oh2);return 0;
}

在这里插入图片描述

实现方式2:

析构函数只声明不定义并私有化,析构函数是私有的,那么在对象离开其作用域时,编译器试图调用析构函数时会遇到问题,因为它不能从外部访问私有成员。

class OnlyHeap
{
public:// 默认构造OnlyHeap(){}static OnlyHeap* CreateObject(){return new OnlyHeap;}private:// 拷贝构造OnlyHeap(const OnlyHeap& oh){}~OnlyHeap(){}
};int main()
{// OnlyHeap oh1;OnlyHeap* oh2 = OnlyHeap::CreateObject();OnlyHeap oh3(*oh2);return 0;
}

在这里插入图片描述

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

class OnlyStack
{
public:static OnlyStack CreateStack(){OnlyStack os;return os;}// 这里不能将拷贝构造删除// 因为CreateStack函数是传值返回,需要拷贝构造// OnlyStack(const OnlyStack& os) = delete;
private:OnlyStack(){}// new分为构造和operator new 两个部分// 我们已经对构造函数动手了,拷贝构造又不能动// 那么接下来只有对operator new动手了// 实现专属的operator new// 那么new这个对象的时候就不会调用全局的,而是调用这里的/*void* operator new(size_t size){cout << "void* operator new(size_t)" << endl;return malloc(size);}*/// 我们将operator new删除了,那么就new不了对象了void* operator new(size_t) = delete;
};int main()
{OnlyStack os1 = OnlyStack::CreateStack();// OnlyStack* os2 = new OnlyStack;OnlyStack* os3;os3 = new OnlyStack(os1);return 0;
}

在这里插入图片描述

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

  1. C++98
    由于派生类的构造需要调用基类的构造函数,而这里将构造函数私有后,派生类则不能调用基类的构造函数,那么该类不能被继承。
class CannotInherit
{
public:static CannotInherit CreateObject(){return CannotInherit();}private:CannotInherit(){}
};class DerivedClass : public CannotInherit
{DerivedClass(){}
};

在这里插入图片描述

  1. C++11
    final修饰类,表示该类不能被继承。

在这里插入图片描述

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

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

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

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

单例模式有两种实现模式:

5.1 饿汉模式

就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象

饿汉模式的优缺点

  • 优点:简单
  • 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
// 饿汉模式程序启动时就创建一个唯一的实例对象
// 那么什么变量能够在进入main函数之前就定义呢
// 那么就是全局变量了
// 但是下面单例模式中的构造函数私有化了
// 导致外面的无法构造对象了
// 全局变量不行,那么就可以使用静态变量了
// 在单例模式中添加一个该类的静态类对象
// 在类中声明,在类外定义class SingLeton
{static SingLeton* GetInstance(){return &_sl;}private:// 私有化构造函数SingLeton(){}// 防拷贝// 删除拷贝构造SingLeton(const SingLeton& sl) = delete;// 删除拷贝赋值SingLeton& operator=(const SingLeton& sl) = delete;private:static SingLeton _sl;
};SingLeton SingLeton::_sl;

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

那么这里单例模式中的饿汉模式就完成了,需要某个资源只创建一个对象,那么就在单例模式中添加这个资源的成员变量即可。


5.2 懒汉模式

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

懒汉模式的优缺点

  • 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
  • 缺点:复杂

假设下面懒汉模式下存储的对象是map

// 懒汉模式第一次使用实例对象时,创建对象
// 所以我们在类里面定义一个该类的静态指针类型
// 再在类外面对该指针初始化为nullptr
// 当第一个使用一个类时,再创建对象
class SingLeton
{
public:static SingLeton* GetInstance(){if (_psl == nullptr){_psl = new SingLeton;}return _psl;}void Insert(const string& key, const string& value){_dict[key] = value;}void Print(){for (auto dict : _dict){cout << dict.first << ':' << dict.second << endl;}cout << endl;}private:// 私有化构造函数SingLeton(){}private:static SingLeton* _psl;map<string, string> _dict;};SingLeton* SingLeton::_psl = nullptr;

那么为什么需要删除拷贝构造和拷贝赋值呢?

int main()
{SingLeton::GetInstance()->Insert("want","想");SingLeton::GetInstance()->Insert("miss", "错过");SingLeton::GetInstance()->Insert("left", "左边");SingLeton p(*(SingLeton::GetInstance()));p.Insert("miss", "想念");SingLeton::GetInstance()->Print();p.Print();return 0;
}

在这里插入图片描述
因为不删除拷贝构造和拷贝赋值会导致单例不具有唯一性。

删除拷贝构造和拷贝赋值后的懒汉模式。

// 懒汉模式第一次使用实例对象时,创建对象
class SingLeton
{
public:static SingLeton* GetInstance(){if (_psl == nullptr){_psl = new SingLeton;}return _psl;}void Insert(const string& key, const string& value){_dict.insert(make_pair(key, value));}void Print(){for (auto dict : _dict){cout << dict.first << ':' << dict.second << endl;}}private:// 私有化构造函数SingLeton(){}// 防拷贝// 删除拷贝构造SingLeton(const SingLeton& sl) = delete;// 删除拷贝赋值SingLeton& operator=(const SingLeton& sl) = delete;private:static SingLeton* _psl;map<string, string> _dict;
};

依照上面的代码还可以看出一个问题,那就是_psl是new出来的,需要delete吗?如何delete呢?

一般来说单例模式是伴随着一整个程序的,程序结束后会自动释放,不排除单例模式用到一半后不需要它了的情况,也有可能是程序结束后需要对数据进行持久化,所以可能需要delete,那么如何delete呢?

首先可以想到这个delete SingLeton::GetInstance(),但是这个写法太挫了,别人可能看漏或是不理解,在后面继续使用但是模式,那么下面这段代码教大家如何delete这段数据。

首先在SingLeton中定义一个函数static void DelInstance()用来释放空间,再定义一个内部类InstanceCleaner,而这个类是空类,在SingLeton中声明一个静态的InstanceCleaner类对象,在类外面定义,由于是空类,并不用担心会影响程序启动的速度,在InstanceCleaner的析构函数中调用DelInstance,那么在程序结束后会释放InstanceCleaner对象,调用析构函数,再调用DelInstance释放数据。值得注意的是SingLeton对象中的数据可以在程序结束后,依靠InstanceCleaner来释放数据,也可以自己手动调用DelInstance提前释放数据。

class SingLeton
{
public:static SingLeton* GetInstance(){if (_psl == nullptr){_psl = new SingLeton;}return _psl;}static void DelInstance(){if (_psl){delete _psl;_psl = nullptr;cout << "static void DelInstance()" << endl;}}void Insert(const string& key, const string& value){_dict.insert(make_pair(key, value));}void Print(){for (auto dict : _dict){cout << dict.first << ':' << dict.second << endl;}}class InstanceCleaner{public:InstanceCleaner(){}~InstanceCleaner(){DelInstance();}};private:// 私有化构造函数SingLeton(){}~SingLeton(){}// 防拷贝// 删除拷贝构造SingLeton(const SingLeton& sl) = delete;// 删除拷贝赋值SingLeton& operator=(const SingLeton& sl) = delete;
private:static SingLeton* _psl;map<string, string> _dict;static InstanceCleaner _InstanceCleaner;
};SingLeton* SingLeton::_psl = nullptr;
SingLeton::InstanceCleaner _InstanceCleaner;
int main()
{SingLeton::GetInstance()->Insert("want","想");SingLeton::GetInstance()->Insert("miss", "错过");SingLeton::GetInstance()->Insert("left", "左边");cout << "Hello C++" << endl;SingLeton::GetInstance()->DelInstance();return 0;
}

在这里插入图片描述


实际上上面的懒汉模式还存在线程安全问题,在创建和删除单例的时候,多个线程可能会同时进入,这里我们保证只有第一个调用的线程才可以创建或是删除的单例。

// 懒汉模式第一次使用实例对象时,创建对象
#include <mutex>
class SingLeton
{
public:static SingLeton* GetInstance(){// 防止浪费锁资源if (_psl == nullptr){// 防止多个线程同时进入unique_lock<mutex> lock(mtx);if (_psl == nullptr){_psl = new SingLeton;}}return _psl;}static void DelInstance(){if (_psl){unique_lock<mutex> lock(mtx);if (_psl){delete _psl;_psl = nullptr;cout << "static void DelInstance()" << endl;}}}void Insert(const string& key, const string& value){_dict.insert(make_pair(key, value));}void Print(){for (auto dict : _dict){cout << dict.first << ':' << dict.second << endl;}}class InstanceCleaner{public:InstanceCleaner(){}~InstanceCleaner(){DelInstance();}};private:// 私有化构造函数SingLeton(){}~SingLeton(){}// 防拷贝// 删除拷贝构造SingLeton(const SingLeton& sl) = delete;// 删除拷贝赋值SingLeton& operator=(const SingLeton& sl) = delete;
private:static SingLeton* _psl;static mutex mtx;map<string, string> _dict;static InstanceCleaner _InstanceCleaner;
};SingLeton* SingLeton::_psl = nullptr;
SingLeton::InstanceCleaner _InstanceCleaner;

这里我们设计一个最简单的单例模式,下面的并且下面的代码是懒汉模式,只有第一次调用函数时,才会创建单例。sl是一个静态对象,只有在第一次调用的时候才会初始化,下面的代码在C++11之前是存在线程安全问题的,这也是C++11之前的缺陷,而C++11之后就不存在线程安全问题了,保证了sl只初始化一次。

class SingLeton
{
public:static SingLeton& GetInstance(){static SingLeton sl;return sl;}private:// 私有化构造函数SingLeton(){}~SingLeton(){}// 防拷贝// 删除拷贝构造SingLeton(const SingLeton& sl) = delete;// 删除拷贝赋值SingLeton& operator=(const SingLeton& sl) = delete;
};

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

在这里插入图片描述


http://www.ppmy.cn/embedded/159051.html

相关文章

四.3 Redis 五大数据类型/结构的详细说明/详细使用( hash 哈希表数据类型详解和使用)

四.3 Redis 五大数据类型/结构的详细说明/详细使用&#xff08; hash 哈希表数据类型详解和使用&#xff09; 文章目录 四.3 Redis 五大数据类型/结构的详细说明/详细使用&#xff08; hash 哈希表数据类型详解和使用&#xff09;2.hash 哈希表常用指令(详细讲解说明)2.1 hset …

javascript-es6 (一)

作用域&#xff08;scope&#xff09; 规定了变量能够被访问的“范围”&#xff0c;离开了这个“范围”变量便不能被访问 局部作用域 函数作用域&#xff1a; 在函数内部声明的变量只能在函数内部被访问&#xff0c;外部无法直接访问 function getSum(){ //函数内部是函数作用…

OpenCV边沿检测(Python版)

边缘检测是图像处理中的一项重要任务&#xff0c;用于找到图像中的边界或边缘。它在计算机视觉、图像处理和模式识别等领域中具有广泛的应用。 边缘可以被定义为图像亮度、颜色或纹理的突变区域。边缘检测算法旨在识别这些变化并将其标记为边缘。边缘检测可以用于分割图像、检测…

【4Day创客实践入门教程】Day2 探秘微控制器——单片机与MicroPython初步

Day2 探秘微控制器——单片机与MicroPython初步 目录 Day2 探秘微控制器——单片机与MicroPython初步MicroPython语言基础开始基础语法注释与输出变量模块与函数 单片机基础后记 Day0 创想启程——课程与项目预览Day1 工具箱构建——开发环境的构建Day2 探秘微控制器——单片机…

嵌入式硬件篇---CPUGPUTPU

文章目录 第一部分&#xff1a;处理器CPU&#xff08;中央处理器&#xff09;1.通用性2.核心数3.缓存4.指令集5.功耗和发热 GPU&#xff08;图形处理器&#xff09;1.并行处理2.核心数量3.内存带宽4.专门的应用 TPU&#xff08;张量处理单元&#xff09;1.为深度学习定制2.低精…

SpringBoot 中的测试jar包knife4j(实现效果非常简单)

1、效果图 非常快的可以看见你实现的接口 路径http://localhost:8080/doc.html#/home 端口必须是自己的 2、实现效果 2.1、导入jar包 <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-openapi3-jakarta-spring-boot-star…

【leetcode】T541 (两点反思)

解题反思 闷着头往&#xff0c;往往会写成一团浆糊&#xff0c;还推倒重来&#xff0c;谋划好全局思路再开始很重要。 熟悉C的工具库很重要&#xff0c;一开始看到反转就还想着用stack来着&#xff0c;后面突然想起来用reverse函数刚好可以用哇&#xff0c;这题也就迎刃而解了…

python小知识-typing注解你的程序

python小知识-typing注解你的程序 1. Typing的简介 typing 是 Python 的一个标准库&#xff0c;它提供了类型注解的支持&#xff0c;但并不会强制类型检查。类型注解在 Python 3.5 中引入&#xff0c;并在后续版本中得到了增强和扩展。typing 库允许开发者为变量、函数参数和…