欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > 【C++】特殊类设计

【C++】特殊类设计

2025/1/31 22:02:59 来源:https://blog.csdn.net/qq_55401402/article/details/139021158  浏览:    关键词:【C++】特殊类设计

在这里插入图片描述

目录

  • 一、请设计一个类,不能被拷贝
  • 二、请设计一个类,只能在堆上创建对象
  • 三、请设计一个类,只能在栈上创建对象
  • 四、请设计一个类,不能被继承
  • 五、请设计一个类,只能创建一个对象(单例模式)
    • 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;
};

结尾

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

在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com