• 特殊类的设计


    目录

    只能再对上创建的类

    方法 1

    方法 2

    只能在栈上创建的类

    方法 1

    方法 2

    单例模式

    饿汉模式

    懒汉模式

    不能被继承的类

    方法 1

    方法 2



    只能再对上创建的类

    • 如果一个类只能再堆上创建,那么应该怎么创建?

    其实有几种方法:

    方法 1

    1. 可以将该类的构造函数私有,然后提供一个函数用来获取该类对象的函数。

    2. 然后我们可以再该函数中 new 一个该类的对象,然后返回给类的指针

    1. // 设计一个只能再堆上创建的类
    2. class HeapOnly
    3. {
    4. public:
    5. static HeapOnly* getObject()
    6. {
    7. return new HeapOnly;
    8. }
    9. private:
    10. HeapOnly()
    11. {
    12. //...
    13. }
    14. };

    上面为什么要把 getObjext 设计为 static 呢?

    因为成员函数的话,调用需要用到 this 指针,但是 this 指针指在对象里面才可以,但是我们现在没有对象,所以我们只能通过 static成员函数来获取对象。

    但是上面这样就结束了吗?没有!

    如果我们调用拷贝构造呢?

    1. void test1()
    2. {
    3. HeapOnly* hp = HeapOnly::getObject();
    4. HeapOnly hp2(*hp);
    5. }

    这样的话,hp2 又创建到栈上了,所以我们还需要将构造函数也私有了,或者删除了。

    1. class HeapOnly
    2. {
    3. public:
    4. static HeapOnly* getObject()
    5. {
    6. return new HeapOnly;
    7. }
    8. private:
    9. HeapOnly()
    10. {
    11. //...
    12. }
    13. HeapOnly(const HeapOnly& hp) = delete;
    14. };

    方法 2

    1. 我们第一种是堆构造函数下手,实际上我们还是可以析构函数下手的。

    2. 我们将析构函数私有后,然后栈上开辟的对象不能调用析构函数,所以无法定义。

    3. 但是我们可以创建在堆上,这样就可以创建成功。

    4. 但是我们需要提供一个 destory 函数,我们需要主动的调用这个函数。

    1. class HeapOnly
    2. {
    3. public:
    4. HeapOnly()
    5. {
    6. //...
    7. }
    8. void Destory()
    9. {
    10. this->~HeapOnly();
    11. }
    12. private:
    13. ~HeapOnly()
    14. {
    15. //...
    16. }
    17. HeapOnly(const HeapOnly& hp) = delete;
    18. };

    测试代码:

    1. void test2()
    2. {
    3. HeapOnly hp;
    4. }
    • 如果是这样的话,那么是不可以的,因为析构函数以及是私有了,所以 hp 无法访问私有的成员函数,所以不可以创建。

    • 但是我们可以创建在对上,那么我们就可以创建成功,当需要析构的时候,我们可以调用 Destroy 函数。

    1. void test2()
    2. {
    3. HeapOnly* hp(new HeapOnly);
    4. hp->Destory();
    5. }

    只能在栈上创建的类

    • 如果想要创建一个只能在栈上创建的类,那么我们应该怎么办呢?

    • 首先我们想一下对析构函数肯定是没办法下手了,那么就只能对构造函数下手。

    方法 1

    1. 所以下面,我们还是像上面一样,将构造函数私有。

    2. 然后提供一个函数获取该类的对象。

    1. class StackOnly
    2. {
    3. public:
    4. static StackOnly getObject()
    5. {
    6. return StackOnly();
    7. }
    8. private:
    9. StackOnly()
    10. {
    11. //...
    12. }
    13. };

    方法 2

    • 上面这样就可以了,但是实际上,我们还是可以继续简单一点。

    • 我们是否还记得操作符重载,而 new 调用了 operator new ,那么我们就可以将 operator new 重载,山吃掉,这样就调用不了 new 了。

    • 这里再多说一句,如果使用 malloc 开辟的空间它并不能算是对象,所以用 malloc 开辟的空间不作数。

    1. class StackOnly
    2. {
    3. public:
    4. private:
    5. void* operator new(size_t size) = delete;
    6. };
    • 上面也是一种方法,且比较简单。

    单例模式

    单例模式就是我们想让该类里面的成员变量只有一份,说简单一点也就是只想要该类只能创建一个对象。

    就比如我们的成员变量是一个线程池,或者是内存池等,所以我们并不希望我们又多份这样会极大的占据系统资源。

    而单例模式的设计也是分为两种:

    饿汉模式

    • 饿汉模式就是再该进程启动之前就将所有的资源都准备好了。

    所以我们看一下恶汉模式如何设计:

    1. 我们可以将该类的构造函数私有,然后提供一个 get 函数,获取该类的对象。

    2. 既然我们只想要有一个该类的对象,那么我们可以提前创建一个该类的对象,也就是可以将该类的对象提前创建为一个 static 的一个对象,然后每一次调用就返回该 static 对象的引用。

    3. 为了不能拷贝何赋值,我们屏删除掉拷贝构造和赋值重载。

    下面我们就看一下设计的具体代码何细节:

    1. class hungry
    2. {
    3. public:
    4. static hungry& getObject()
    5. {
    6. return _single;
    7. }
    8. private:
    9. unordered_map<int, int> hash;
    10. static hungry _single;
    11. };
    • 我们就可以这样设计,但是 static 的对象需要再类外面初始化:

    1. class hungry
    2. {
    3. public:
    4. static hungry& getObject()
    5. {
    6. return _single;
    7. }
    8. private:
    9. unordered_map<int, int> hash;
    10. static hungry _single;
    11. };
    12. hungry hungry::_single;
    • 这样可以初始化吗?

    • hungry 类不是把构造函数私有了吗?

    • 那么这里调用构造会不会失败呢?

    • 其实这里调用构造函数是不会失败的,因为 _single 是类内部的,类内部的成员是可以访问类的私有的没所以这里调用没有任何问题。

    • 为了解释上面的类内的是可以访问类内的私有的,假如我们在类内部声明了一个函数,然后再类外面定义,那么类外面定义的函数就是可以访问到类内部的私有的:

      就像下面这个 fun 函数一样。

    1. class hungry
    2. {
    3. public:
    4. static hungry& getObject()
    5. {
    6. return _single;
    7. }
    8. void fun();
    9. private:
    10. unordered_map<int, int> hash;
    11. static hungry _single;
    12. };
    13. hungry hungry::_single;
    14. void hungry::fun()
    15. {
    16. hash[10] = 20;
    17. }
    • 所以虽然构造函数说私有的,但是 _single 是类内部的,所以可以访问到构造函数。

    • 而这样还不够,我们还需要将构造函数私有,还有我们需要防止拷贝。

    1. class hungry
    2. {
    3. public:
    4. static hungry& getObject()
    5. {
    6. return _single;
    7. }
    8. void fun();
    9. void Add(const pair<int, int>& kv)
    10. {
    11. hash.insert(kv);
    12. }
    13. void Print()
    14. {
    15. for (auto& kv : hash)
    16. {
    17. cout << kv.first << " " << kv.second << endl;
    18. }
    19. }
    20. private:
    21. hungry()
    22. {
    23. //...
    24. }
    25. //防拷贝
    26. hungry(const hungry& hu) = delete;
    27. hungry& operator=(hungry hu) = delete;
    28. unordered_map<int, int> hash;
    29. static hungry _single;
    30. };
    31. hungry hungry::_single;

    懒汉模式

    • 懒汉模式就是当我们需要的时候再创建,而不是再进程启动之前就创建。

    • 这样呢,他就可以让进程启动的快一点。

    那么怎么才可以做到呢?

    下面看一下懒汉模式的创建:

    1. 首先既然是单例模式,那么我们当然不能让该类随便创建,所以我们还是需要将构造函数私有。

    2. 然后我们还是可以模仿上面的使用 static 的一个 get 来获取该类的对象。

    3. 我们可以将该类的一个成员变量设计为该类的一个指针,如果我们是第一次调用 get 的话,那么我们就需要new 一个该对象,然后赋值给该指针,然后改回该类对象的引用。

    4. 如果不是第一次调用get,那么就直接返回该类的对象的引用即可。

    下面看一下实现的代码和细节:

    1. //懒汉模式
    2. class lazy
    3. {
    4. public:
    5. static lazy& getObject()
    6. {
    7. if (_single == nullptr)
    8. {
    9. _single = new lazy;
    10. }
    11. return *_single;
    12. }
    13. void fun();
    14. void Add(const pair<int, int>& kv)
    15. {
    16. hash[kv.first] = kv.second;
    17. }
    18. void Print()
    19. {
    20. for (auto& kv : hash)
    21. {
    22. cout << kv.first << " " << kv.second << endl;
    23. }
    24. }
    25. private:
    26. lazy()
    27. {
    28. //...
    29. }
    30. //防拷贝
    31. lazy(const lazy& hu) = delete;
    32. lazy& operator=(lazy hu) = delete;
    33. unordered_map<int, int> hash;
    34. static lazy* _single;
    35. };
    36. lazy* lazy::_single = nullptr;
    • 其实这里实现起来和上面基本相同,只不过就是再 get 的时候需要判断一下 _single 是否为空。

    上面看起来是结束了,但是还没有,如果这样的话,那么该对象怎析构呢?

    或者说是我们想要显示的析构呢?

    假设我们想要该类析构的时候帮我们把数据写到文件种:

    所以其实我们还是需要提供一个可以析构的函数:

    1. void Destroy()
    2. {
    3. this->~lazy();
    4. }
    5. ~lazy()
    6. {
    7. // 析构
    8. FILE* fd = fopen("test.txt", "w");
    9. for (auto& kv : hash)
    10. {
    11. fprintf(fd, "%d", kv.first);
    12. fprintf(fd, "%s", " ");
    13. fprintf(fd, "%d", kv.second);
    14. }
    15. fclose(fd);
    16. }

    我们就可以提高这两个函数,然后当我们想要析构的时候,我们就调用 destroy 函数就可以了。

    不能被继承的类

    方法 1

    • 实际上这个是比较简单的。

    • 我们再之前说,如果一个类被继承,那么派生类需要如何初始化基类呢?

    • 组要调用基类的构造函数来初始化,所以根据这个原因,我们可以将基类的构造函数私有。

    • 就像上面的将构造函数私有,然后提高一个获取该类对象的一个函数。

    方法 2

    • 第二种就更简单了。

    • C++11中有一个关键字 final

    • 被 final 修饰的类就无法被继承。

  • 相关阅读:
    2024前端面试准备之HTML篇
    【C语言刷题】Leetcode203——移除链表元素
    Linux下自动备份MySQL数据库并上传到远程FTP服务器
    latex 模板使用技巧——参考文献篇
    【论文阅读】Pay Attention to MLPs
    git clone http/https 报错 10054/443 问题
    微服务的优缺点分别是什么?如何用好微服务架构?
    自己实现str
    基于 Transformation-Equivariant 的自动驾驶 3D 目标检测
    一款基于SpringBoot+SpringSecurity的后台管理系统,强烈推荐
  • 原文地址:https://blog.csdn.net/m0_73455775/article/details/133844719