特殊类设计
1.请设计一个类,不能被拷贝
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98的方式(只声明)
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
原因:
1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不 能禁止拷贝了
2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写 反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
C++11的方式(=delete)
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上
=delete,表示让编译器删除掉该默认成员函数。
2. 请设计一个类,只能在堆上创建对象
实现方式:
1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
new的需要手动delete,其他两个会自动释放
解决办法1
直接把析构私有,让hp1和hp3无法自动析构
不过会引发hp2无法手动释放
解决办法
解决办法2
这样就会有可以拷贝hp2的情况(拷贝后可能在栈)
解决办法
把拷贝构造和赋值封死
3. 请设计一个类,只能在栈上创建对象
方法:
同上将构造函数私有化,然后设计静态方法创建对象返回即可。
因为这里的拷贝也符合了,所以就会出现以下问题(就可以在堆上开空间了)
重要知识点
解决办法:要先了解new的原理,这里new的时候不可以调用构造,但是还可以调用拷贝构造,但这里不能直接封了拷贝构造,因为StackOnly st用的是拷贝构造,new:会调用两个部分,一部分是构造一部分是operator new,之所以要调用operator new,而不直接调用malloc,是因为operator new会抛异常
4. 请设计一个类,不能被继承
C++98方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit{public:static NonInherit GetInstance(){return NonInherit();}private:NonInherit(){}
};
C++11方法(final)
final关键字,final修饰类,表示该类不能被继承。
class A final{// ....};
5. 请设计一个类,只能创建一个对象(单例模式)
设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的 总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打 仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后 来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模 式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置 信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再 通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。 单例模式有两种实现模式:
饿汉模式
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
// 饿汉模式
// 优点:简单
// 缺点:可能会导致进程启动慢(都不知道是在初始化还是系统挂了),且如果有多个单例类对象实例启动顺序不确定。
第一步:构造函数私有(不能随意创建)
把map<string,string>设为私有
创建static变量
类外声明
2、提供获取单例对象的接口函数
创建对象
问题(需要防止拷贝)
然后就会出现一个问题,没有绝对防死可以拷贝构造对象
Singleton copy(Singleton::GetInstance());
第三步防拷贝
添加数据并打印
饿汉模式:一开始(main函数之前)就创建单例对象
静态的在main之前就创建了1、如果单例对象初始化内容很多,影响启动速度2、如果两个单例类,互相有依赖关系。 假设有A B两个单例类,要求A先创建,B再创建,B的初始化创建依赖A
class Singleton
{
public:// 2、提供获取单例对象的接口函数static Singleton& GetInstance()//instance是实例的意思{return _sinst;}//覆盖型的Addvoid Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}private:// 1、构造函数私有Singleton(){// ...}// 3、防拷贝Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;//使map全局只有唯一实例map<string, string> _dict;// ...//可以创建自己类型的对象,也不会套娃,因为静态的在静态区//静态的在main之前就创建了static Singleton _sinst;
};Singleton Singleton::_sinst;int main()
{//保证创建的都是一个对象//三个地址都一样,说明三个创建的是同一个cout << &Singleton::GetInstance() << endl;cout << &Singleton::GetInstance() << endl;cout << &Singleton::GetInstance() << endl;Singleton copy(Singleton::GetInstance());//添加数据Singleton::GetInstance().Add({ "1111","2222" });Singleton::GetInstance().Print();return 0;
}
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好。
大问题用懒汉解决
1、如果单例对象初始化内容很多,影响启动速度
2、如果两个单例类,互相有依赖关系。
假设有A B两个单例类,要求A先创建,B再创建,B的初始化创建依赖A,就没法用饿汉了,然后就有了下面的懒汉
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取 文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
// 懒汉
// 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控 制。
// 缺点:复杂
//懒汉用的static指针,main之前创建一个指针不耽误时间
//顺序可以随便控制
//单例一般不用智能指针,所以需要显示释放
// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)
改进
//懒汉用的static指针,main之前创建一个指针不耽误时间
//顺序可以随便控制
//单例一般不用智能指针,所以需要显示释放
// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)namespace lazy
{class Singleton{public://改进::2// 2、提供获取单例对象的接口函数static Singleton& GetInstance(){if (_psinst == nullptr){// 第一次调用GetInstance的时候创建单例对象_psinst = new Singleton;}return *_psinst;}//改进::3// 一般单例不用释放。// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)static void DelInstance(){if (_psinst){delete _psinst;_psinst = nullptr;}}void Add(const pair<string, string>& kv){_dict[kv.first] = kv.second;}void Print(){for (auto& e : _dict){cout << e.first << ":" << e.second << endl;}cout << endl;}//改进::5,因为一个单例显示释放一次多个就要多次delete太麻烦//所以有了GC//用智能指针又没办法显示释放class GC{public:~GC(){lazy::Singleton::DelInstance();}};private:// 1、构造函数私有Singleton(){// ...}//程序结束后,把东西写入文件中(可持久化)~Singleton(){cout << "~Singleton()" << endl;//改进::4// map数据写到文件中,可持久化FILE* fin = fopen("map.txt", "w");for (auto& e : _dict){fputs(e.first.c_str(), fin);fputs(":", fin);fputs(e.second.c_str(), fin);fputs("\n", fin);}}// 3、防拷贝Singleton(const Singleton& s) = delete;Singleton& operator=(const Singleton& s) = delete;map<string, string> _dict;// ...//改进::这里弄一个指针static Singleton* _psinst;//改进::5static GC _gc;};//类外定义Singleton* Singleton::_psinst = nullptr;//改进::5Singleton::GC Singleton::_gc;
}
int main()
{//Singleton s1;//Singleton s2;cout << &lazy::Singleton::GetInstance() << endl;cout << &lazy::Singleton::GetInstance() << endl;cout << &lazy::Singleton::GetInstance() << endl;//Singleton copy(Singleton::GetInstance());lazy::Singleton::GetInstance().Add({ "xxx", "111" });lazy::Singleton::GetInstance().Add({ "yyy", "222" });lazy::Singleton::GetInstance().Add({ "zzz", "333" });lazy::Singleton::GetInstance().Add({ "abc", "333" });lazy::Singleton::GetInstance().Print();//lazy::Singleton::DelInstance();lazy::Singleton::GetInstance().Add({ "abc", "444" });lazy::Singleton::GetInstance().Print();//lazy::Singleton::DelInstance();return 0;
}