• 【C++】特殊类设计


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

    目录

    文章目录

    前言

    一、请设计一个类,不能被拷贝

    C++98

    C++11

    二、请设计一个类,只能在堆上创建对象

    方法一:

    方法二:

    三、请设计一个类,只能在栈上创建对象

    第一种方式:

    第二种方式:

    四、请设计一个类,不能被继承

    C++98方式

    C++11方法

    五、请设计一个类,只能创建一个对象(单例模式)

    饿汉模式

    懒汉模式

    懒汉方式一:使用指针

    懒汉方式二:局部的静态对象

    总结



    前言

    世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、请设计一个类,不能被拷贝

    拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

    • C++98

    将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

    1. class CopyBan
    2. {
    3. // ...
    4. private:
    5. CopyBan(const CopyBan&);
    6. CopyBan& operator=(const CopyBan&);
    7. //...
    8. };

    原因:

    1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
    2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
    • C++11

    C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 =delete,表示让编译器删除掉该默认成员函数。

    1. class CopyBan
    2. {
    3. // ...
    4. CopyBan(const CopyBan&) = delete;
    5. CopyBan& operator=(const CopyBan&) = delete;
    6. //...
    7. };

    二、请设计一个类,只能在堆上创建对象

    实现方式:

    1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
    2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
    • 方法一:

    1. // 对象只能在堆上
    2. class HeapOnly
    3. {
    4. public:
    5. template<class... Args>
    6. static HeapOnly* CreateObj(Args&&... args)
    7. {
    8. // 访问限定符只限定类外面的,而不限定类里面的,可以在类里面创建一个该类型的对象
    9. return new HeapOnly(args...);
    10. }
    11. HeapOnly(const HeapOnly&) = delete;
    12. HeapOnly& operator=(const HeapOnly&) = delete;
    13. private:
    14. // 构造私有化
    15. HeapOnly()
    16. {}
    17. // 带参的构造函数
    18. HeapOnly(int x, int y)
    19. :_x(x)
    20. ,_y(y)
    21. {}
    22. int _x;
    23. int _y;
    24. vector<int> _a;
    25. };
    26. int main()
    27. {
    28. //HeapOnly ho1;
    29. //HeapOnly* ho2 = new HeapOnly;
    30. // 我们想要调用类成员函数,我们就得有类的对象,但是类的对象又是由该函数生成的,这就是先有鸡还是先有蛋的问题;
    31. // 所以我们可以将生成类的对象的函数定义为静态的
    32. HeapOnly* ho3 = HeapOnly::CreateObj();
    33. HeapOnly* ho4 = HeapOnly::CreateObj(1,1);
    34. //HeapOnly copy(*ho3);
    35. return 0;
    36. }
    • 方法二:

    1. // 只能在堆上(方法二)
    2. class HeapOnly
    3. {
    4. public:
    5. // 构造私有化
    6. HeapOnly()
    7. {}
    8. HeapOnly(int x, int y)
    9. :_x(x)
    10. ,_y(y)
    11. {}
    12. void Destroy()
    13. {
    14. delete this;// 调用析构函数
    15. // delete分为两步:先调用析构,再free指向的空间
    16. }
    17. private:
    18. // 封掉析构函数,但是还要用析构,所以不能彻底封死,放到私有部分
    19. ~HeapOnly()
    20. {
    21. cout << "~HeapOnly()" << endl;
    22. }
    23. int _x;
    24. int _y;
    25. vector<int> _a;
    26. };
    27. int main()
    28. {
    29. //HeapOnly ho1;// 栈区对象出了作用域,会自动调用析构函数
    30. // 方法一:手动释放空间
    31. HeapOnly* ptr = new HeapOnly;// 不会自动调用析构,因为是堆区上面的
    32. //delete ptr;
    33. ptr->Destroy();
    34. // 方法二:lambda表达式
    35. // 释放的删除器是delete,但是底层的delete调不动,会报错,所以只能默认的写一个删除器,调用Destroy()函数
    36. shared_ptr ptr(new HeapOnly, [](HeapOnly* ptr) {ptr->Destroy(); });
    37. return 0;
    38. }

    三、请设计一个类,只能在栈上创建对象

    第一种方式:

    将构造函数私有化,阻止了在堆上创建对象,然后设计静态方法创建对象返回即可。

    第二种方式:

    new是由operator new和构造函数(拷贝构造也是构造)两部分组成,封一下operator new,operator new是默认调用的是全局的operator new,我们可以重载一个专属的operator new

    1. // 对象只能在Stack
    2. class StackOnly
    3. {
    4. public:
    5. template<class... Args>
    6. static StackOnly CreateObj(Args&&... args)
    7. {
    8. return StackOnly(args...);// 返回一个栈上的匿名对象
    9. }
    10. //StackOnly(const StackOnly&) = delete;
    11. StackOnly& operator=(const StackOnly&) = delete;
    12. // 重载一个类专属的operator new
    13. // 意思是new想在堆区上开辟一个StackOnly类型的对象,就不能调用全局的operator new,只能调用该类的operator new
    14. // 但是该类的operator new()封住了,所以new也就不能在堆区上开辟空间了
    15. void* operator new(size_t n) = delete;
    16. void operator delete(void* p) = delete; // 禁止调用delet
    17. private:
    18. // 构造私有化,阻止了在堆上创建对象
    19. StackOnly()
    20. {}
    21. StackOnly(int x, int y)
    22. :_x(x)
    23. ,_y(y)
    24. {}
    25. int _x;
    26. int _y;
    27. vector<int> _a;
    28. };
    29. int main()
    30. {
    31. // 优化了之后,可能是直接构造,但是优化是在语法逻辑之后,得先拷贝,然后再优化
    32. StackOnly so1 = StackOnly::CreateObj();
    33. StackOnly so2 = StackOnly::CreateObj(1,1);
    34. // 因为上面两行,拷贝构造函数是不能封死的
    35. // new的时候,调不了构造函数,但可以调用拷贝构造函数(没封)
    36. // new是在堆区上开辟一个对象空间,我们只想在栈区上开辟空间,但是拷贝构造又不能封死,所以要让new不能使用
    37. //StackOnly* so3 = new StackOnly(so1);
    38. // new是由operator new和构造函数(拷贝构造也是构造)两部分组成
    39. // 封一下operator new,operator new是默认调用的是全局的operator new,我们可以重载一个专属的operator new
    40. return 0;
    41. }

    四、请设计一个类,不能被继承

    • C++98方式

    1. // C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
    2. class NonInherit
    3. {
    4. public:
    5. static NonInherit GetInstance()
    6. {
    7. return NonInherit();
    8. }
    9. private:
    10. NonInherit()
    11. {}
    12. };
    • C++11方法

    final关键字,final修饰类,表示该类不能被继承。

    1. class A final
    2. {
    3. // ....
    4. };

    五、请设计一个类,只能创建一个对象(单例模式)

    设计模式:

    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

    使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模 式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

    单例模式:

    一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置 信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再 通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

    单例模式有两种实现模式:

    • 饿汉模式

    就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。

    1. namespace hunger
    2. {
    3. // 饿汉:一开始(main之前)就创建出全局的对象
    4. // 问题:
    5. // 1、如果单例对象数据较多,构造初始化成本较高,那么会影响程序启动的速度。迟迟进不了main函数
    6. // 2、多个单例类有初始化启动依赖关系,饿汉无法控制。
    7. // 假设:A和B两个单例,假设要求A先初始化,B再初始化,饿汉无法保证。
    8. // 都是全局的,谁先谁后不清楚,而且还定义在不同的文件
    9. class Singleton
    10. {
    11. public:
    12. static Singleton* GetInstance()
    13. {
    14. return &_sint;// 返回该类型的对象
    15. }
    16. void Print()
    17. {
    18. cout << _x << endl;
    19. cout << _y << endl;
    20. for (auto& e : _vstr)
    21. {
    22. cout << e << " ";
    23. }
    24. cout << endl;
    25. }
    26. // 添加数据
    27. void AddStr(const string& s)
    28. {
    29. _vstr.push_back(s);
    30. }
    31. Singleton(Singleton const&) = delete;
    32. Singleton& operator=(Singleton const&) = delete;
    33. private:
    34. // 构造函数私有化
    35. Singleton(int x = 0, int y = 0, const vector& vstr = { "yyyyy","xxxx" })
    36. :_x(x)
    37. , _y(y)
    38. , _vstr(vstr)
    39. {}
    40. // 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
    41. // 再把这个类设计成单例,这个数据就只有一份了
    42. int _x;
    43. int _y;
    44. vector _vstr;
    45. // 静态成员对象,不存在类的其中一个对象中,存在静态区,相当于全局的,是属于这个类中的所有对象的,
    46. // 定义在类中,受类域限制,但是能访问私有的构造函数
    47. static Singleton _sint;
    48. };
    49. // 静态成员变量在类里面声明,在类外面定义;也可以调用私有的构造函数
    50. // 第一个Singleton是类型;第二个Singleton是类域
    51. Singleton Singleton::_sint(1, 1, { "陕西","四川" });
    52. }
    53. // Singleton _sint;
    54. // 在类外面不能创建该类的对象,因为该类的构造函数私有话了,所以只能把它放到类里面,写成静态的

    如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避 免资源竞争,提高响应速度更好。

    • 懒汉模式

    如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化, 就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

    懒汉方式一:使用指针

    1. // 完美的解决了饿汉的问题(懒汉模式)
    2. namespace lazy
    3. {
    4. // 懒汉方式一:两个问题:new出来的对象要考虑释放问题、线程安全问题
    5. class Singleton
    6. {
    7. public:
    8. static Singleton* GetInstance()
    9. {
    10. // 第一次调用时,创建单例对象
    11. // 线程安全问题,需要加锁
    12. if (_psint == nullptr)
    13. {
    14. _psint = new Singleton;
    15. }
    16. return _psint;
    17. }
    18. static void DelInstance()
    19. {
    20. if (_psint)
    21. {
    22. delete _psint;
    23. _psint = nullptr;
    24. }
    25. }
    26. void Print()
    27. {
    28. cout << _x << endl;
    29. cout << _y << endl;
    30. for (auto& e : _vstr)
    31. {
    32. cout << e << " ";
    33. }
    34. cout << endl;
    35. }
    36. void AddStr(const string& s)
    37. {
    38. _vstr.push_back(s);
    39. }
    40. Singleton(Singleton const&) = delete;
    41. Singleton& operator=(Singleton const&) = delete;
    42. private:
    43. Singleton(int x = 0, int y = 0, const vector& vstr = { "yyyyy","xxxx" })
    44. :_x(x)
    45. , _y(y)
    46. , _vstr(vstr)
    47. {}
    48. // 什么情况下需要显示的释放?
    49. ~Singleton()
    50. {
    51. // 把数据写到文件
    52. cout << "~Singleton()" << endl;
    53. }
    54. // 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
    55. // 再把这个类设计成单例,这个数据就只有一份了
    56. int _x;
    57. int _y;
    58. vector _vstr;
    59. // 静态成员对象,不存在对象中,存在静态区,相当于全局的,定义在类中,受类域限制
    60. // 懒汉模式 ---> 第一种方式:使用指针
    61. static Singleton* _psint;
    62. // 万一外面忘记调用DelInstance()函数来显示释放单例对象,怎么办?
    63. // 内部类
    64. class GC
    65. {
    66. public:
    67. ~GC()
    68. {
    69. Singleton::DelInstance();
    70. }
    71. };
    72. static GC gc;// 定义一个全局的GC类的对象,出了作用域会自动调用析构函数
    73. };
    74. Singleton* Singleton::_psint = nullptr;
    75. Singleton::GC Singleton::gc;

    懒汉方式二:局部的静态对象

    • 饿汉定义的是全局的静态变量,将全局的静态变量放入类域中;全局的静态变量在main()函数之前就要创建;
    • 懒汉定义的是局部的静态变量,第一次调用函数时构造初始化;第二次调用函数时,不会重复构造初始化;
    • 局部的静态变量的声明周期也是全局的
    1. // 懒汉方式二:
    2. class Singleton
    3. {
    4. public:
    5. static Singleton* GetInstance()
    6. {
    7. // 局部的静态对象,第一次调用函数时构造初始化;第二次调用函数时,不会重复构造初始化
    8. // C++11及之后这样写才可以
    9. // C++11之前无法保证这里的构造初始化是线程安全
    10. static Singleton _sinst;
    11. return &_sinst;
    12. }
    13. void Print()
    14. {
    15. cout << _x << endl;
    16. cout << _y << endl;
    17. for (auto& e : _vstr)
    18. {
    19. cout << e << " ";
    20. }
    21. cout << endl;
    22. }
    23. void AddStr(const string& s)
    24. {
    25. _vstr.push_back(s);
    26. }
    27. Singleton(Singleton const&) = delete;
    28. Singleton& operator=(Singleton const&) = delete;
    29. private:
    30. Singleton(int x = 0, int y = 0, const vector& vstr = { "yyyyy","xxxx" })
    31. :_x(x)
    32. , _y(y)
    33. , _vstr(vstr)
    34. {
    35. cout << "Singleton()" << endl;
    36. }
    37. ~Singleton()
    38. {
    39. // 把数据写到文件
    40. cout << "~Singleton()" << endl;
    41. }
    42. // 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
    43. // 再把这个类设计成单例,这个数据就只有一份了
    44. int _x;
    45. int _y;
    46. vector _vstr;
    47. };
    48. }
    1. int main()
    2. {
    3. cout << "xxxxxxxxx" << endl;
    4. cout << hunger::Singleton::GetInstance() << endl;
    5. cout << hunger::Singleton::GetInstance() << endl;
    6. cout << hunger::Singleton::GetInstance() << endl;
    7. lazy::Singleton::GetInstance()->Print();
    8. lazy::Singleton::GetInstance()->Print();
    9. lazy::Singleton::GetInstance()->Print();
    10. lazy::Singleton::GetInstance()->AddStr("甘肃");
    11. lazy::Singleton::GetInstance()->Print();
    12. //lazy::Singleton::DelInstance();
    13. cout << "xxxxxxxxx" << endl;
    14. return 0;
    15. }

    总结

    好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
    不积硅步,无以至千里;不积小流,无以成江海。

  • 相关阅读:
    Windows下WSL将ubuntu挪位置-系统盘清理
    shiro认证
    第十九周进度(了解物联网基础知识)
    快消品行业SaaS多租户解决方案,构建全产业链数字化营销竞争力
    数据结构与算法之美笔记05(链表)
    【算法集训 | 暑期刷题营】终章
    js的es5
    Android无障碍自动化结合opencv实现支付宝能量自动收集
    C语言描述数据结构 —— 常见排序(2)冒泡排序、快速排序
    docker学习(一)
  • 原文地址:https://blog.csdn.net/2301_79585944/article/details/139687761