• C++特殊类与单例模式


    一、特殊类

    类的特殊设计方式

    ①不能被拷贝的类

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

    在C++98中,需要将拷贝构造设置成私有,并且只声明不定义,是因为该构造函数根本不会调用,定义了也并无意义

    1. //不能被拷贝的类
    2. //C++98
    3. class CopyBan
    4. {
    5. private:
    6. CopyBan(const CopyBan&);
    7. CopyBan& operator=(const CopyBan&);
    8. };
    9. //C++11
    10. class CopyBan
    11. {
    12. CopyBan(const CopyBan&) = delete;
    13. CopyBan& operator=(const CopyBan&) = delete;
    14. };

    ②只能在堆上创建对象的类

    实现方法:

    1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象

    2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

    1. //只能在堆上创建对象的类
    2. class HeapOnly
    3. {
    4. public:
    5. static HeapOnly* CreateObject()
    6. {
    7. return new HeapOnly;
    8. }
    9. private:
    10. HeapOnly() {}
    11. HeapOnly(const HeapOnly&);//C++98
    12. HeapOnly(const HeapOnly&) = delete;//C++11
    13. };

    ③只能在栈上创建对象的类

    将构造函数私有化,然后设计静态方法创建对象返回

    禁用operator new可以把下面用new 调用拷贝构造申请对象给禁止

    1. //只能在栈上创建对象的类
    2. class StackOnly
    3. {
    4. public:
    5. static StackOnly CreateObject(int x=0)
    6. {
    7. return StackOnly(x);
    8. }
    9. void* operator new(size_t size) = delete;
    10. void operator delete(void* p) = delete;
    11. StackOnly(StackOnly&& st)
    12. //拷贝构造被禁止之后,会导致创建StackOnly st1=StackOnly::CreateObj(1)失败,因此需要开放移动构造
    13. //但这样又会导致static StackOnly st2=move(st1)构造成功,因此并不能完全禁止
    14. :_x(st._x)
    15. {}
    16. private:
    17. StackOnly(int x=0)
    18. :_x(x)
    19. {}
    20. StackOnly(const StackOnly& st) = delete;//禁止静态对象拷贝
    21. private:
    22. int _x;
    23. };

    ④不能被继承的类

    1. //不能被继承的类
    2. //C++98
    3. class NonInherit
    4. {
    5. public:
    6. static NonInherit GetInstance()
    7. {
    8. return NonInherit();
    9. }
    10. private:
    11. NonInherit() {}
    12. };
    13. //C++11
    14. class A final
    15. {
    16. };

    二、单例模式

    使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保障代码的可靠性

    单例模式:一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享

    单例模式有两种实现模式:饿汉模式和懒汉模式

    饿汉模式:

    饿汉模式是指:不论未来是否使用,在main函数执行之前,就已经生成一个实例

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

    饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using namespace std;
    8. //单例对象:只能创建一个对象的类
    9. //饿汉模式
    10. //在main函数程序执行之前,就已经有了一个实例
    11. class Singleton
    12. {
    13. public:
    14. static Singleton* GetInstance()
    15. {
    16. return _ins;
    17. }
    18. void Add(const string& str)
    19. {
    20. _mtx.lock();
    21. _v.push_back(str);
    22. _mtx.unlock();
    23. }
    24. void Print()
    25. {
    26. _mtx.lock();
    27. for (auto& e : _v)
    28. {
    29. cout << e << endl;
    30. }
    31. cout << endl;
    32. _mtx.unlock();
    33. }
    34. private:
    35. Singleton() {};//限制类外随意创建对象
    36. //C++98防止拷贝
    37. //Singleton(Singleton const&);
    38. //Singleton& operator=(Singleton const&);
    39. //C++11防拷贝
    40. Singleton(Singleton const&) = delete;
    41. Singleton& operator=(Singleton const&) = delete;
    42. private:
    43. static Singleton* _ins;
    44. mutex _mtx;
    45. vector _v;
    46. };
    47. Singleton* Singleton::_ins=new Singleton;//在程序入口之前,就完成对单例对象的初始化
    48. int main()
    49. {
    50. srand(time(0));
    51. int n = 30;
    52. thread t1([n]() {
    53. for (size_t i = 0; i < n; ++i)
    54. {
    55. Singleton::GetInstance()->Add("t1线程:" + to_string(rand()));
    56. }
    57. });
    58. thread t2([n]() {
    59. for (size_t i = 0; i < n; ++i)
    60. {
    61. Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));
    62. }
    63. });
    64. t1.join();
    65. t2.join();
    66. Singleton::GetInstance()->Print();
    67. return 0;
    68. }

    懒汉模式:

    懒汉模式顾名思义:只有在第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制

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

    懒汉模式的实现相比于饿汉模式更加复杂。既然是手动启用实例,就需要考虑手动释放实例问题,手动释放实例之后不能影响自动释放实例,并且也不能重复释放

    自动释放问题:定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象

    懒汉模式中需要考虑线程安全问题:因此设计双检查加锁,第一次检查用于提高效率,避免每次访问单例都加减锁;第二次检查用于保证线程安全并确保单例只new一次

    私有类内有两个锁,一个全局锁一个局部锁,全局锁存在的目的是使得单例获取与释放时都全局同一,保障全局唯一单例

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using namespace std;
    8. //懒汉模式
    9. //第一次调用时才会创建单例
    10. class Singleton
    11. {
    12. public:
    13. ~Singleton()
    14. {
    15. //程序可能有持久化需求,要求在程序结束时,将数据写到文件中
    16. }
    17. static Singleton* GetInstance()
    18. {
    19. //双检查加锁
    20. if (_ins == nullptr)//用于提高效率 避免每次访问单例都加减锁
    21. {
    22. _imtx.lock();
    23. if (_ins == nullptr)//保证线程安全和只new一次
    24. {
    25. _ins = new Singleton;
    26. }
    27. _imtx.unlock();
    28. }
    29. return _ins;
    30. }
    31. void Add(const string& str)
    32. {
    33. _vmtx.lock();
    34. _v.push_back(str);
    35. _vmtx.unlock();
    36. }
    37. void Print()
    38. {
    39. _vmtx.lock();
    40. for (auto& e : _v)
    41. {
    42. cout << e << endl;
    43. }
    44. cout << endl;
    45. _vmtx.unlock();
    46. }
    47. //显示手动释放单例
    48. static void DelInstance()//一般全局都要使用单例对象,所以一般情况下不需要显示释放
    49. {
    50. _imtx.lock();
    51. if (_ins)
    52. {
    53. delete _ins;
    54. _ins = nullptr;//显示释放后置空,这样自动释放重复也不影响
    55. }
    56. _imtx.unlock();
    57. }
    58. //保证单例对象的回收(自动回收)
    59. class GC
    60. {
    61. public:
    62. ~GC()
    63. {
    64. DelInstance();
    65. }
    66. };
    67. static GC _gc;
    68. private:
    69. Singleton() {}//限制类外随意创建对象
    70. //没有处理拷贝构造,是由于锁的存在,默认生成的拷贝构造
    71. //实际上仍然需要防拷贝
    72. Singleton(const Singleton& s) = delete;
    73. Singleton& operator=(const Singleton& s) = delete;
    74. private:
    75. mutex _vmtx;
    76. vector _v;
    77. static Singleton* _ins;//静态成员类外实现
    78. static mutex _imtx;
    79. };
    80. Singleton* Singleton::_ins = nullptr;
    81. mutex Singleton::_imtx;
    82. Singleton::GC Singleton::_gc;
    83. int main()
    84. {
    85. srand(time(0));
    86. int n = 30;
    87. thread t1([n]() {
    88. for (size_t i = 0; i < n; ++i)
    89. {
    90. Singleton::GetInstance()->Add("t1线程:" + to_string(rand()));
    91. }
    92. });
    93. thread t2([n]() {
    94. for (size_t i = 0; i < n; ++i)
    95. {
    96. Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));
    97. }
    98. });
    99. t1.join();
    100. t2.join();
    101. Singleton::GetInstance()->Print();
    102. return 0;
    103. }

  • 相关阅读:
    如图是怎样实现点动和连续的
    论文复现|Panoptic Deeplab(全景分割PyTorch)
    2023/9/8 -- C++/QT
    使用 js 动态修改在线 svg 背景色
    vin图像识别易语言代码
    MySQL实用安装教程
    centos7.9 扩容swap分区
    C# 图片的绘制
    给灭霸点颜色看看
    深度解析一道单词变换算法题
  • 原文地址:https://blog.csdn.net/RXY24601/article/details/134231590