提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
目录
文章目录
前言
一、请设计一个类,不能被拷贝
C++98
C++11
二、请设计一个类,只能在堆上创建对象
方法一:
方法二:
三、请设计一个类,只能在栈上创建对象
第一种方式:
第二种方式:
四、请设计一个类,不能被继承
C++98方式
C++11方法
五、请设计一个类,只能创建一个对象(单例模式)
饿汉模式
懒汉模式
懒汉方式一:使用指针
懒汉方式二:局部的静态对象
总结
前言
世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!
提示:以下是本篇文章正文内容,下面案例可供参考
一、请设计一个类,不能被拷贝
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
-
C++98
将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。
class CopyBan
{// ...private:CopyBan(const CopyBan&);CopyBan& operator=(const CopyBan&);//...
};
原因:
- 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
- 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
-
C++11
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 =delete,表示让编译器删除掉该默认成员函数。
class CopyBan
{// ...CopyBan(const CopyBan&) = delete;CopyBan& operator=(const CopyBan&) = delete;//...
};
二、请设计一个类,只能在堆上创建对象
实现方式:
- 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
- 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
-
方法一:
// 对象只能在堆上
class HeapOnly
{
public:template<class... Args>static HeapOnly* CreateObj(Args&&... args){// 访问限定符只限定类外面的,而不限定类里面的,可以在类里面创建一个该类型的对象return new HeapOnly(args...);}HeapOnly(const HeapOnly&) = delete;HeapOnly& operator=(const HeapOnly&) = delete;
private:// 构造私有化HeapOnly(){}// 带参的构造函数HeapOnly(int x, int y):_x(x),_y(y){}int _x;int _y;vector<int> _a;
};int main()
{//HeapOnly ho1;//HeapOnly* ho2 = new HeapOnly;// 我们想要调用类成员函数,我们就得有类的对象,但是类的对象又是由该函数生成的,这就是先有鸡还是先有蛋的问题;// 所以我们可以将生成类的对象的函数定义为静态的HeapOnly* ho3 = HeapOnly::CreateObj();HeapOnly* ho4 = HeapOnly::CreateObj(1,1);//HeapOnly copy(*ho3);return 0;
}
-
方法二:
// 只能在堆上(方法二)
class HeapOnly
{
public:// 构造私有化HeapOnly(){}HeapOnly(int x, int y):_x(x),_y(y){}void Destroy(){delete this;// 调用析构函数// delete分为两步:先调用析构,再free指向的空间}private:// 封掉析构函数,但是还要用析构,所以不能彻底封死,放到私有部分~HeapOnly(){cout << "~HeapOnly()" << endl;}int _x;int _y;vector<int> _a;
};int main()
{ //HeapOnly ho1;// 栈区对象出了作用域,会自动调用析构函数// 方法一:手动释放空间HeapOnly* ptr = new HeapOnly;// 不会自动调用析构,因为是堆区上面的//delete ptr;ptr->Destroy();// 方法二:lambda表达式// 释放的删除器是delete,但是底层的delete调不动,会报错,所以只能默认的写一个删除器,调用Destroy()函数shared_ptr<HeapOnly> ptr(new HeapOnly, [](HeapOnly* ptr) {ptr->Destroy(); });return 0;
}
三、请设计一个类,只能在栈上创建对象
第一种方式:
将构造函数私有化,阻止了在堆上创建对象,然后设计静态方法创建对象返回即可。
第二种方式:
new是由operator new和构造函数(拷贝构造也是构造)两部分组成,封一下operator new,operator new是默认调用的是全局的operator new,我们可以重载一个专属的operator new
// 对象只能在Stack
class StackOnly
{
public:template<class... Args>static StackOnly CreateObj(Args&&... args){return StackOnly(args...);// 返回一个栈上的匿名对象}//StackOnly(const StackOnly&) = delete;StackOnly& operator=(const StackOnly&) = delete;// 重载一个类专属的operator new// 意思是new想在堆区上开辟一个StackOnly类型的对象,就不能调用全局的operator new,只能调用该类的operator new// 但是该类的operator new()封住了,所以new也就不能在堆区上开辟空间了void* operator new(size_t n) = delete;void operator delete(void* p) = delete; // 禁止调用deletprivate:// 构造私有化,阻止了在堆上创建对象StackOnly(){}StackOnly(int x, int y):_x(x),_y(y){}int _x;int _y;vector<int> _a;
};int main()
{// 优化了之后,可能是直接构造,但是优化是在语法逻辑之后,得先拷贝,然后再优化StackOnly so1 = StackOnly::CreateObj();StackOnly so2 = StackOnly::CreateObj(1,1);// 因为上面两行,拷贝构造函数是不能封死的// new的时候,调不了构造函数,但可以调用拷贝构造函数(没封)// new是在堆区上开辟一个对象空间,我们只想在栈区上开辟空间,但是拷贝构造又不能封死,所以要让new不能使用//StackOnly* so3 = new StackOnly(so1);// new是由operator new和构造函数(拷贝构造也是构造)两部分组成// 封一下operator new,operator new是默认调用的是全局的operator new,我们可以重载一个专属的operator newreturn 0;
}
四、请设计一个类,不能被继承
-
C++98方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};
-
C++11方法
final关键字,final修饰类,表示该类不能被继承。
class A final
{// ....
};
五、请设计一个类,只能创建一个对象(单例模式)
设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模 式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置 信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再 通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:
-
饿汉模式
就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。
namespace hunger
{// 饿汉:一开始(main之前)就创建出全局的对象// 问题:// 1、如果单例对象数据较多,构造初始化成本较高,那么会影响程序启动的速度。迟迟进不了main函数// 2、多个单例类有初始化启动依赖关系,饿汉无法控制。// 假设:A和B两个单例,假设要求A先初始化,B再初始化,饿汉无法保证。// 都是全局的,谁先谁后不清楚,而且还定义在不同的文件class Singleton{public:static Singleton* GetInstance(){return &_sint;// 返回该类型的对象}void Print(){cout << _x << endl;cout << _y << endl;for (auto& e : _vstr){cout << e << " ";}cout << endl;}// 添加数据void AddStr(const string& s){_vstr.push_back(s);}Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;private:// 构造函数私有化Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" }):_x(x), _y(y), _vstr(vstr){}// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面// 再把这个类设计成单例,这个数据就只有一份了int _x;int _y;vector<string> _vstr;// 静态成员对象,不存在类的其中一个对象中,存在静态区,相当于全局的,是属于这个类中的所有对象的,// 定义在类中,受类域限制,但是能访问私有的构造函数static Singleton _sint;};// 静态成员变量在类里面声明,在类外面定义;也可以调用私有的构造函数// 第一个Singleton是类型;第二个Singleton是类域Singleton Singleton::_sint(1, 1, { "陕西","四川" });
}// Singleton _sint;
// 在类外面不能创建该类的对象,因为该类的构造函数私有话了,所以只能把它放到类里面,写成静态的
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好。
-
懒汉模式
如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
懒汉方式一:使用指针
// 完美的解决了饿汉的问题(懒汉模式)
namespace lazy
{// 懒汉方式一:两个问题:new出来的对象要考虑释放问题、线程安全问题class Singleton{public:static Singleton* GetInstance(){// 第一次调用时,创建单例对象// 线程安全问题,需要加锁if (_psint == nullptr){_psint = new Singleton;}return _psint;}static void DelInstance(){if (_psint){delete _psint;_psint = nullptr;}}void Print(){cout << _x << endl;cout << _y << endl;for (auto& e : _vstr){cout << e << " ";}cout << endl;}void AddStr(const string& s){_vstr.push_back(s);}Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;private:Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" }):_x(x), _y(y), _vstr(vstr){}// 什么情况下需要显示的释放?~Singleton(){// 把数据写到文件cout << "~Singleton()" << endl;}// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面// 再把这个类设计成单例,这个数据就只有一份了int _x;int _y;vector<string> _vstr;// 静态成员对象,不存在对象中,存在静态区,相当于全局的,定义在类中,受类域限制// 懒汉模式 ---> 第一种方式:使用指针static Singleton* _psint;// 万一外面忘记调用DelInstance()函数来显示释放单例对象,怎么办?// 内部类class GC{public:~GC(){Singleton::DelInstance();}};static GC gc;// 定义一个全局的GC类的对象,出了作用域会自动调用析构函数};Singleton* Singleton::_psint = nullptr;Singleton::GC Singleton::gc;
懒汉方式二:局部的静态对象
- 饿汉定义的是全局的静态变量,将全局的静态变量放入类域中;全局的静态变量在main()函数之前就要创建;
- 懒汉定义的是局部的静态变量,第一次调用函数时构造初始化;第二次调用函数时,不会重复构造初始化;
- 局部的静态变量的声明周期也是全局的
// 懒汉方式二:class Singleton{public:static Singleton* GetInstance(){// 局部的静态对象,第一次调用函数时构造初始化;第二次调用函数时,不会重复构造初始化// C++11及之后这样写才可以// C++11之前无法保证这里的构造初始化是线程安全static Singleton _sinst;return &_sinst;}void Print(){cout << _x << endl;cout << _y << endl;for (auto& e : _vstr){cout << e << " ";}cout << endl;}void AddStr(const string& s){_vstr.push_back(s);}Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;private:Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" }):_x(x), _y(y), _vstr(vstr){cout << "Singleton()" << endl;}~Singleton(){// 把数据写到文件cout << "~Singleton()" << endl;}// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面// 再把这个类设计成单例,这个数据就只有一份了int _x;int _y;vector<string> _vstr;};}
int main()
{cout << "xxxxxxxxx" << endl;cout << hunger::Singleton::GetInstance() << endl;cout << hunger::Singleton::GetInstance() << endl;cout << hunger::Singleton::GetInstance() << endl;lazy::Singleton::GetInstance()->Print();lazy::Singleton::GetInstance()->Print();lazy::Singleton::GetInstance()->Print();lazy::Singleton::GetInstance()->AddStr("甘肃");lazy::Singleton::GetInstance()->Print();//lazy::Singleton::DelInstance();cout << "xxxxxxxxx" << endl;return 0;
}
总结
好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。