• 特殊类的设计


    这个设计模式细节挺多,每个模式感觉好像能大致回想起来,但是总有遗漏,所以打算写一篇文章来复习一遍。

    一 CopyBan

    设计一个防拷贝的类

    1. class CopyBan 不能被拷贝
    2. {
    3. public:
    4. CopyBan()可以构造
    5. {
    6. cout<<"CopyBan()"<
    7. }
    8. ~CopyBan() 可以析构
    9. {
    10. cout << "~CopyBan()" << endl;
    11. }
    12. CopyBan(const CopyBan& fc) = delete;
    13. CopyBan& operator=(const CopyBan& fc) = delete;
    14. 赋值也可以封,不能被拷贝可能不想自己的数据复制给其它对象。
    15. };

    还有种防拷贝的方式是如下的,

    1. class CopyBan
    2. {
    3. private:
    4. CopyBan(const CopyBan& fc);//防拷贝2,只声明,不实现
    5. };
    6. 虽然外部调用拷贝构造时会出错,但要编译才报错,如果直接用delete就可以在写代码阶段就报红了
    7. 设为私有是为了防止外部调用,但防不住别人去实现,实现也没用
    8. CopyBan::CopyBan(const CopyBan& fc)
    9. {
    10. cout<<"CopyBan::CopyBan(const CopyBan& fc)";
    11. }

    二 StackOnly

    设计一个只能在栈上创建的类。

    1. class StackOnly
    2. {
    3. public:
    4. 析构不能封
    5. ~StackOnly()
    6. {
    7. cout << "~StackOnly()" << endl;
    8. }
    9. 析构函数一封,在栈上都无法创建对象,会直接报红
    10. static StackOnly()
    11. {
    12. StackOnly s1;
    13. return s1;
    14. }
    15. 目前场景中感觉封不封构造都可以,因为静态对象封不住,除非把拷贝也封了
    16. 这样静态对象就不能调用构造和拷贝构造来创建对象了。
    17. StackOnly(const StackOnly& s)拷贝构造可用于栈上创建对象
    18. {
    19. cout << "StackOnly(const StackOnly& s)" << endl;
    20. }
    21. private:
    22. StackOnly()
    23. {
    24. cout << "StackOnly()" << endl;
    25. }
    26. void* operator new(size_t size) = delete;
    27. new = operator new + 构造, new可以调用拷贝和普通构造, 所以只能封operator new
    28. };

       所以说new在最上层,其实是个关键字,可以申请空间加调用构造函数初始化,而内部封装了operator new这个函数,用于申请空间,调用构造函数有其它函数完成,此时我们在类内重载一个operator new,调用时先用类内的,而类内的已被删除,编译器选择直接报错,而不是去类外找全局的operator new,显示调用全局的用::operator new。如下使用new可能会去调用全局的operator new,所以感觉这个类啥也防不住。

    1. void test2()
    2. {
    3. StackOnly* s1 = ::new StackOnly;
    4. delete s1;
    5. }

    三 HeapOnly

    只能在堆上创建 

    1. class HeapOnly
    2. {
    3. public:
    4. void Delete()
    5. {
    6. delete this;
    7. cout << "Delete";
    8. }
    9. HeapOnly()
    10. {
    11. cout << "HeapOnly()" << endl;
    12. }
    13. HeapOnly(const HeapOnly& s)
    14. {
    15. cout << "HeapOnly(const HeapOnly& s)" << endl;
    16. }
    17. //封析构
    18. private:
    19. ~HeapOnly()
    20. {
    21. cout<<"~HeapOnly()"<
    22. }
    23. };

      方法2,在栈上创建对象无非就是用构造和拷贝构造函数,如果我们把这两个封了,就不能在栈上创建对象了。封了构造函数,那new也就没办法调用构造函数了,所以必须提供一个公有函数,在该函数内用new, 这样new可以调用构造函数,我们也可以调用这个公有函数来创建对象。好,问题来了,如何调用这个公有函数,用对象调,对象哪里来,调用Creat这个公有函数创建对象呗,这不就逻辑死循环了吗?所以这个公有函数就必须是静态的。析构就可以不用private修饰了。

    1. class HeapOnly2
    2. {
    3. public:
    4. static HeapOnly2* Creat()
    5. {
    6. return new HeapOnly2;
    7. }
    8. ~HeapOnly2()
    9. {
    10. cout << "~HeapOnly2()" << endl;
    11. }
    12. private:
    13. HeapOnly2()
    14. {
    15. cout << "HeapOnly2()" << endl;
    16. }
    17. HeapOnly2(const HeapOnly2& s)
    18. {
    19. cout << "HeapOnly2(const HeapOnly2& s)" << endl;
    20. }
    21. };

    四 单例模式之饿汉模式

       单例模式是指该类只实例化一个对象,那构造得设为private,免得让外部创建对象,那我们如何创建那个唯一的对象呢,如果创建一个全局对象,然后写一个静态函数Creat函数,返回这个对象的指针或者引用就好了,可是构造函数私有了,外部无法调用。所以还得在类内创建对象,大佬是将其设为静态成员变量。这样编译前sgl成员就会被创建。至于Creat函数是返回指针还是引用都可以。

    1. class SingleHungry
    2. {
    3. public:
    4. static SingleHungry* Creat()
    5. {
    6. return &sgl;
    7. }
    8. private:
    9. 析构可以设为私有,~SingleHungry()会由于静态成员变量是类内的,可以调用private修饰的
    10. ~SingleHungry()
    11. {
    12. cout<<"~SingleHungry()"<
    13. }
    14. SingleHungry()
    15. {
    16. cout << "SingleHungry()"<
    17. }
    18. 之所以要把拷贝封了,就是防止Creat函数返回对象后被拿去拷贝对象,至于赋值应该可封可不封。
    19. SingleHungry(const SingleHungry& sh) = delete;
    20. SingleHungry operator=(const SingleHungry& sh) = delete;
    21. static SingleHungry sgl;
    22. };
    23. 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前把资源保存到磁盘。

    1. class SingleLazy
    2. {
    3. public:
    4. static SingleLazy* Creat()
    5. {
    6. if(sgl == nullptr)
    7. sgl = new SingleLazy();
    8. return sgl;
    9. }
    10. static void Destroy()
    11. {
    12. string s("he");
    13. FILE* f = fopen("test.txt","w");
    14. fwrite(s.c_str(),sizeof(string),1,f);
    15. delete sgl;
    16. }
    17. private:
    18. SingleLazy()
    19. {
    20. cout << "SingleLazy()" << endl;
    21. }
    22. ~SingleLazy()
    23. {
    24. cout << "~SingleLazy" << endl;
    25. }
    26. SingleLazy(const SingleLazy& sh) = delete;
    27. SingleLazy operator=(const SingleLazy& sh) = delete;
    28. static SingleLazy* sgl;
    29. };
    30. SingleLazy* SingleLazy::sgl = nullptr;
    31. void test9()
    32. {
    33. SingleLazy*p=SingleLazy::Creat();
    34. SingleLazy::Destroy(); 但是还有个缺点,就是释放还是要手动释放,
    35. 特别是当有十几二十个单例对象的时候,一个个Destroy会麻烦
    36. 能不能智能一点呢
    37. }

        噢,智能指针,用智能指针,但是智能指针一般是程序结束后自动调用析构的,我们显示调用也不是不可以,但是智能指针的析构函数就得把指针置空,不然程序结束还会再调用,也就是说程序结束都会调用类对象的析构函数,很容易二次析构,我感觉用智能指针应该是可以的。还有第二种办法就是再写一个静态对象(不能是普通,不然会逻辑死循环),当程序结束,这个静态成员也就要调用析构函数释放了,我们在函数内把单例对象的释放方法放入,也就实现了智能化的释放资源,而且还可以选择自主释放。

    1. class Garbage Garbage是SingleLazy的内部类,方便访问sgl变量
    2. {
    3. public:
    4. ~Garbage()
    5. {
    6. if(SingleLazy::sgl)
    7. delete SingleLazy::sgl;
    8. }
    9. };
    10. private:
    11. static Garbage gb; gb是SingleLazy成员变量,也可以写到全局,这个时候是不是静态就无所谓了

    说完懒汉模式后,我再提一种自己的,根据饿汉模式优化的。而且还不用操心释放的问题。(后来看了effective c++发现早有前辈也提出这种优雅的模式)

    1. class SingleHungry
    2. {
    3. public:
    4. static SingleHungry& Creat()
    5. {
    6. static SingleHungry sgl;
    7. return sgl;
    8. }
    9. private:
    10. ~SingleHungry()
    11. {
    12. cout << "~SingleHungry()" << endl;
    13. }
    14. SingleHungry()
    15. {
    16. cout << "SingleHungry()" << endl;
    17. }
    18. SingleHungry(const SingleHungry& sh) = delete;
    19. SingleHungry operator=(const SingleHungry& sh) = delete;
    20. };

    六 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