这个设计模式细节挺多,每个模式感觉好像能大致回想起来,但是总有遗漏,所以打算写一篇文章来复习一遍。
设计一个防拷贝的类
- class CopyBan 不能被拷贝
- {
- public:
- CopyBan()可以构造
- {
- cout<<"CopyBan()"<
- }
- ~CopyBan() 可以析构
- {
- cout << "~CopyBan()" << endl;
- }
-
- CopyBan(const CopyBan& fc) = delete;
- CopyBan& operator=(const CopyBan& fc) = delete;
-
- 赋值也可以封,不能被拷贝可能不想自己的数据复制给其它对象。
- };
还有种防拷贝的方式是如下的,
- class CopyBan
- {
-
- private:
- CopyBan(const CopyBan& fc);//防拷贝2,只声明,不实现
-
- };
-
- 虽然外部调用拷贝构造时会出错,但要编译才报错,如果直接用delete就可以在写代码阶段就报红了
- 设为私有是为了防止外部调用,但防不住别人去实现,实现也没用
- CopyBan::CopyBan(const CopyBan& fc)
- {
- cout<<"CopyBan::CopyBan(const CopyBan& fc)";
- }
二 StackOnly
设计一个只能在栈上创建的类。
- class StackOnly
- {
- public:
- 析构不能封
- ~StackOnly()
- {
- cout << "~StackOnly()" << endl;
- }
- 析构函数一封,在栈上都无法创建对象,会直接报红
- static StackOnly()
- {
- StackOnly s1;
-
- return s1;
- }
- 目前场景中感觉封不封构造都可以,因为静态对象封不住,除非把拷贝也封了
- 这样静态对象就不能调用构造和拷贝构造来创建对象了。
-
- StackOnly(const StackOnly& s)拷贝构造可用于栈上创建对象
- {
- cout << "StackOnly(const StackOnly& s)" << endl;
- }
- private:
- StackOnly()
- {
- cout << "StackOnly()" << endl;
- }
-
-
- void* operator new(size_t size) = delete;
-
- new = operator new + 构造, new可以调用拷贝和普通构造, 所以只能封operator new
-
-
- };
所以说new在最上层,其实是个关键字,可以申请空间加调用构造函数初始化,而内部封装了operator new这个函数,用于申请空间,调用构造函数有其它函数完成,此时我们在类内重载一个operator new,调用时先用类内的,而类内的已被删除,编译器选择直接报错,而不是去类外找全局的operator new,显示调用全局的用::operator new。如下使用new可能会去调用全局的operator new,所以感觉这个类啥也防不住。
- void test2()
- {
- StackOnly* s1 = ::new StackOnly;
- delete s1;
- }
三 HeapOnly
只能在堆上创建
- class HeapOnly
- {
- public:
- void Delete()
- {
- delete this;
- cout << "Delete";
- }
- HeapOnly()
- {
- cout << "HeapOnly()" << endl;
- }
- HeapOnly(const HeapOnly& s)
- {
- cout << "HeapOnly(const HeapOnly& s)" << endl;
- }
- //封析构
- private:
- ~HeapOnly()
- {
- cout<<"~HeapOnly()"<
- }
- };
方法2,在栈上创建对象无非就是用构造和拷贝构造函数,如果我们把这两个封了,就不能在栈上创建对象了。封了构造函数,那new也就没办法调用构造函数了,所以必须提供一个公有函数,在该函数内用new, 这样new可以调用构造函数,我们也可以调用这个公有函数来创建对象。好,问题来了,如何调用这个公有函数,用对象调,对象哪里来,调用Creat这个公有函数创建对象呗,这不就逻辑死循环了吗?所以这个公有函数就必须是静态的。析构就可以不用private修饰了。
- class HeapOnly2
- {
- public:
- static HeapOnly2* Creat()
- {
- return new HeapOnly2;
- }
- ~HeapOnly2()
- {
- cout << "~HeapOnly2()" << endl;
- }
- private:
- HeapOnly2()
- {
- cout << "HeapOnly2()" << endl;
- }
- HeapOnly2(const HeapOnly2& s)
- {
- cout << "HeapOnly2(const HeapOnly2& s)" << endl;
- }
- };
四 单例模式之饿汉模式
单例模式是指该类只实例化一个对象,那构造得设为private,免得让外部创建对象,那我们如何创建那个唯一的对象呢,如果创建一个全局对象,然后写一个静态函数Creat函数,返回这个对象的指针或者引用就好了,可是构造函数私有了,外部无法调用。所以还得在类内创建对象,大佬是将其设为静态成员变量。这样编译前sgl成员就会被创建。至于Creat函数是返回指针还是引用都可以。
- class SingleHungry
- {
- public:
- static SingleHungry* Creat()
- {
- return &sgl;
- }
-
- private:
-
- 析构可以设为私有,~SingleHungry()会由于静态成员变量是类内的,可以调用private修饰的
- ~SingleHungry()
- {
- cout<<"~SingleHungry()"<
- }
-
- SingleHungry()
- {
- cout << "SingleHungry()"<
- }
-
- 之所以要把拷贝封了,就是防止Creat函数返回对象后被拿去拷贝对象,至于赋值应该可封可不封。
-
- SingleHungry(const SingleHungry& sh) = delete;
-
- SingleHungry operator=(const SingleHungry& sh) = delete;
-
- static SingleHungry sgl;
- };
- SingleHungry SingleHungry::sgl;
-
sg1属于类内的成员, 所以定义时可以调用private修饰的构造函数,例如当一个静态成员函数的定义和声明分离,静态成员函数的定义内是可以调用private修饰的成员函数的,所以我认为sg1可以调用构造函数的原因和这个是类似的,祖师爷也没办法了,只能做这种特殊处理了。
五 单例模式之懒汉模式
饿汉模式是定义一个静态变量,之后要使用这个类,就先调用一个Creat返回这个静态对象的指针或者引用,但是如果程序存在大量的静态变量,那在启动的时候就需要做非常多的初始化工作,一个程序启动可能要半个小时,而且最关键的是无法决定静态变量的初始化顺序,有时候B类依赖A类初始化,但是有可能B类静态对象初始化时,A类还未创建,这就出问题。
所以我们一开始就先不创建对象,而是定义一个静态对象的指针,而且把指针初始化为空,这个时候初始化指针还不简单吗。那什么时候创建对象呢?调用Creat函数的时候就创建对象给sgl指针。
这样也就可以确定静态对象的创建顺序了,可是还有个问题就是sgl析构的问题。这个指针指向的资源如何释放,有人说,没事调用析构函数,大坑 ! sgl是new返回的,要用delete销毁,因为delete有两个作用,一个是释放sgl指向的空间,还有就是调用析构函数释放SingleLazy的对象内部的资源,举个例子,例如 string* p new string ; delete不仅要释放p指向的这个string对象,还要调用析构函数释放内部指针指向的资源的,所以不能sgl不能直接调用析构函数。delete可以,但是有时候例如我们除了释放内存的资源外,可能还想将保存到磁盘上,那就不能单单写个delete,所以我们再写一个函数Destroy()去封装delete,此时把析构封了,免得保存操作没做就释放了,并且在delete前把资源保存到磁盘。
- class SingleLazy
- {
- public:
- static SingleLazy* Creat()
- {
- if(sgl == nullptr)
- sgl = new SingleLazy();
- return sgl;
- }
- static void Destroy()
- {
- string s("he");
- FILE* f = fopen("test.txt","w");
- fwrite(s.c_str(),sizeof(string),1,f);
- delete sgl;
- }
- private:
- SingleLazy()
- {
- cout << "SingleLazy()" << endl;
- }
- ~SingleLazy()
- {
- cout << "~SingleLazy" << endl;
- }
-
- SingleLazy(const SingleLazy& sh) = delete;
- SingleLazy operator=(const SingleLazy& sh) = delete;
- static SingleLazy* sgl;
- };
- SingleLazy* SingleLazy::sgl = nullptr;
-
- void test9()
- {
-
- SingleLazy*p=SingleLazy::Creat();
-
- SingleLazy::Destroy(); 但是还有个缺点,就是释放还是要手动释放,
- 特别是当有十几二十个单例对象的时候,一个个Destroy会麻烦
- 能不能智能一点呢
-
- }
噢,智能指针,用智能指针,但是智能指针一般是程序结束后自动调用析构的,我们显示调用也不是不可以,但是智能指针的析构函数就得把指针置空,不然程序结束还会再调用,也就是说程序结束都会调用类对象的析构函数,很容易二次析构,我感觉用智能指针应该是可以的。还有第二种办法就是再写一个静态对象(不能是普通,不然会逻辑死循环),当程序结束,这个静态成员也就要调用析构函数释放了,我们在函数内把单例对象的释放方法放入,也就实现了智能化的释放资源,而且还可以选择自主释放。
- class Garbage Garbage是SingleLazy的内部类,方便访问sgl变量
- {
- public:
- ~Garbage()
- {
- if(SingleLazy::sgl)
- delete SingleLazy::sgl;
- }
- };
- private:
-
- static Garbage gb; gb是SingleLazy成员变量,也可以写到全局,这个时候是不是静态就无所谓了
说完懒汉模式后,我再提一种自己的,根据饿汉模式优化的。而且还不用操心释放的问题。(后来看了effective c++发现早有前辈也提出这种优雅的模式)
- class SingleHungry
- {
- public:
- static SingleHungry& Creat()
- {
- static SingleHungry sgl;
- return sgl;
- }
-
- private:
- ~SingleHungry()
- {
- cout << "~SingleHungry()" << endl;
- }
- SingleHungry()
- {
- cout << "SingleHungry()" << endl;
- }
- SingleHungry(const SingleHungry& sh) = delete;
- SingleHungry operator=(const SingleHungry& sh) = delete;
- };
六 NonInherit
设计一个类不能被继承,最后的方法就是在该类名后加个final,这是c++11支持的。
而c++98则是将该类构造函数设为私有,这样派生类就调不到基类的构造函数,而且你会发现这个时候派生类的默认构造函数被delete了,感觉可能是编译器发现自己的默认构造初始化不了自己的成员,就气急败坏地删了,还有更多情况会被delete,有兴趣可了解了解。
-
相关阅读:
VUE全家桶 (Vue-cli、Vue-route、Vuex)学习笔记
练习题60:接口练习2:真的鸭子会游泳 木头鸭子不会游泳 橡皮鸭子会游泳
不知道什么的复习题
语法练习:not_string
MongoDB快速上手
http 知识整理
JUC并发编程系列详解篇六(死锁的基本概念)
数据结构 | 算法的时间复杂度与空间复杂度【通俗易懂】
MIT6.5830 Lab1-Go tutorial实验记录(一
自动化立体仓库系统实训
-
原文地址:https://blog.csdn.net/m0_62792369/article/details/133947581