• c++单例模式包括懒汉模式和饿汉模式(优劣势分析和改进方法)


    1.单例模式说明

    在整个软件的运行过程中,让整个类有且只有一个实例化对象存在于整个进程中。

    是最简单的一个设计模式,然后再项目开发中也是使用最广的。

    2.使用单例模式的优点

    1.节省资源:再整个软件的运行过程中,只有一个实例化对象,不用重新分配新的堆空间。

    2.数据的传递:由于单例只会创建一个实例化对象,比如有一个在停车场对你的车辆进行计费的程序。但是计费需要多个步骤,这样每个步骤调用的都是同一个单例,就能够记录每个步骤计算后的结果,知道算出正确结果为止。

    3.单例模式实现分析

    1.构造函数只能调用一次

    如果已经有了实例化对象,就直接返回生成的实例化对象

    如果没有就调用一次构造函数

    2.具体实现,让构造函数只能被调用一次

    把构造函数写到private:下面

    删除拷贝构造函数和赋值符号构造函数

    3.对象的生成不能依赖对象,所以要设置成静态的。

    4.返回的创建的对象不能是类类型,因为这样会生成零时变量,因此返回类型要用指针或者引用

    4.实际的应用场景

    1.项目中的日志模块,一个项目只有一个日志的实例化对象

    2.项目中的进程监控模块。

    5.最基础的懒汉模式

    1. #include
    2. using namespace std;
    3. class Singleton {
    4. private:
    5. static Singleton* m_pInstance;
    6. private:
    7. Singleton() {
    8. cout << "constructor called!" << endl;
    9. }
    10. Singleton(Singleton&) = delete;
    11. Singleton& operator=(const Singleton&) = delete;
    12. public:
    13. ~Singleton() {
    14. cout << "destructor called!" << endl;
    15. }
    16. static Singleton* getInstance() {
    17. if (m_pInstance == nullptr) {
    18. m_pInstance = new Singleton;
    19. }
    20. return m_pInstance;
    21. }
    22. };
    23. Singleton* Singleton::m_pInstance = nullptr;
    24. int main()
    25. {
    26. Singleton* instance1 = Singleton::getInstance();
    27. Singleton* instance2 = Singleton::getInstance();
    28. std::cout << "Hello World!\n";
    29. }

    可以看到在main函数里面调用了两次getinstance,但是调用了一次构造函,说明达到了我们想要的效果

    这个版本的懒汉模式还存在以下的缺陷

    1.线程安全的问题:当多个线程同时获取单例的时候可能引发竞争的问题;当第一个线程

    进入到 if (m_pInstance == nullptr),这个判断条件的时候,m_pInstance符合条件nullptr就会创建对应的实例化对象,与此同时,第二个线程也会进入到if (m_pInstance == nullptr)的判断。此时的m_pinstance也符合nullptr的条件,也会创建一个单例的实例化对象,此时就会有两个实例化对象,解决方法加锁

    内存泄漏的问题:getInstance函数里面 ,new 了一个 类对象,但是没有进行释放,解决方法使用智能指针

    6.线程安全,内存安全的懒汉模式(使用智能指针,加锁)

    1. #include
    2. #include
    3. class Singleton {
    4. public:
    5. using Ptr = std::shared_ptr;
    6. ~Singleton() {
    7. std::cout << "destructor called!" << std::endl;
    8. }
    9. Singleton(Singleton&) = delete;
    10. Singleton& operator=(const Singleton&) = delete;
    11. static Ptr getInstance() {
    12. if (m_pInstance == nullptr) {
    13. std::lock_guard lk(m_mutex);
    14. if (m_pInstance == nullptr) {
    15. m_pInstance = std::shared_ptr(new Singleton);
    16. }
    17. }
    18. return m_pInstance;
    19. }
    20. private:
    21. Singleton() {
    22. std::cout << "constructor called!" << std::endl;
    23. }
    24. private:
    25. static Ptr m_pInstance;
    26. static std::mutex m_mutex;
    27. };
    28. Singleton::Ptr Singleton::m_pInstance = nullptr;
    29. std::mutex Singleton::m_mutex;
    30. int main() {
    31. Singleton::Ptr instance1 = Singleton::getInstance();
    32. Singleton::Ptr instance2 = Singleton::getInstance();
    33. return 0;
    34. }

    输出结果为

    程序对堆空间进行了释放,解决了内存安全的问题,

    又使用了锁,解决了线程安全的问题。

    缺陷:单例使用了智能指针,要求调用的用户也需要使用智能指针,

    使用了锁也会增加相应的开销。理论上肯定是希望我们设计的程序越简单越好

    7.最推荐的懒汉式单例(magic static )——局部静态变量

    1. #include
    2. #include
    3. class Singleton
    4. {
    5. public:
    6. ~Singleton() {
    7. std::cout << "destructor called!" << std::endl;
    8. }
    9. Singleton(const Singleton&) = delete;
    10. Singleton& operator=(const Singleton&) = delete;
    11. static Singleton& getInstance() {
    12. static Singleton instance;
    13. return instance;
    14. }
    15. private:
    16. Singleton() {
    17. std::cout << "constructor called!" << std::endl;
    18. }
    19. };
    20. int main(int argc, char* argv[])
    21. {
    22. Singleton& instance_1 = Singleton::getInstance();
    23. Singleton& instance_2 = Singleton::getInstance();
    24. return 0;
    25. }

    这种方法又叫做 Meyers' SingletonMeyer's的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:

    If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

    如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。

    这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。

    C++静态变量的生存期 是从声明到程序结束,这也是一种懒汉式。

    8.单例模式之饿汉式

    饿汉模式 的区别就是,在程序调用单例之前提前生成实例化对象。

    这样就不存在多线程竞争的问题。

    1. #include
    2. #include
    3. class Singleton {
    4. public:
    5. using Ptr = std::shared_ptr;
    6. static Ptr getInstance() {
    7. return m_pInstance;
    8. }
    9. ~Singleton() {
    10. std::cout << "destructor called!" << std::endl;
    11. }
    12. private:
    13. static Ptr m_pInstance;
    14. private:
    15. Singleton() {
    16. std::cout << "constructor called!" << std::endl;
    17. }
    18. Singleton(const Singleton&) = delete;
    19. Singleton& operator=(const Singleton&) = delete;
    20. };
    21. Singleton::Ptr Singleton::m_pInstance = std::shared_ptr(new Singleton);
    22. int main(int argc, char* argv[])
    23. {
    24. Singleton::Ptr instance1 = Singleton::getInstance();
    25. Singleton::Ptr instance2 = Singleton::getInstance();
    26. return 0;
    27. }

    9.单例模式之单例模板

    1. #include
    2. #include
    3. //单例模板
    4. template <typename T>
    5. class Singleton {
    6. private:
    7. Singleton() = default;
    8. ~Singleton() = default;
    9. public:
    10. Singleton(const Singleton&) = delete;
    11. void operator = (const Singleton&) = delete;
    12. static T* instance()
    13. {
    14. static T m_instance;
    15. return &m_instance;
    16. }
    17. };
    18. class Student {
    19. public:
    20. Student() {
    21. std::cout << "constructor called!" << std::endl;
    22. }
    23. ~Student() {
    24. std::cout << "destructor called!" << std::endl;
    25. }
    26. private:
    27. int num;
    28. public:
    29. void setnum(int value) {
    30. num = value;
    31. }
    32. void getnum()
    33. {
    34. std::cout << num << std::endl;
    35. }
    36. };
    37. int main(int argc, char* argv[])
    38. {
    39. Singleton::instance()->setnum(1);
    40. Singleton::instance()->getnum();
    41. Singleton::instance()->setnum(2);
    42. Singleton::instance()->getnum();
    43. }

    输出结果

    可以看到Student类调用了两次,但是只调用了一次构造函数,实现了我们想要的效果,其他的类也可以通过调用单列模板,达到这样类似的效果。

    缺陷,不能阻止这样的声明出现 Student s; 在项目的其他地方声明这个类,也没问题。我们并没有禁止Student 类创建自己的对象

    9.改善后的单例模板类

    1. #include
    2. #include
    3. //单例模板
    4. template <typename T>
    5. class Singleton {
    6. public:
    7. Singleton() = default;
    8. virtual ~Singleton() = default;
    9. Singleton(const Singleton&) = delete;
    10. void operator = (const Singleton&) = delete;
    11. static T* instance()
    12. {
    13. static T m_instance;
    14. return &m_instance;
    15. }
    16. };
    17. class Student:public Singleton {
    18. private:
    19. Student() {
    20. std::cout << "constructor called!" << std::endl;
    21. }
    22. ~Student() {
    23. std::cout << "destructor called!" << std::endl;
    24. }
    25. friend class Singleton;
    26. private:
    27. int num;
    28. public:
    29. void setnum(int value) {
    30. num = value;
    31. }
    32. void getnum()
    33. {
    34. std::cout << num << std::endl;
    35. }
    36. };
    37. int main(int argc, char* argv[])
    38. {
    39. //Student stu; 报错 不可访问构造函数
    40. Student::instance()->setnum(1);
    41. Student::instance()->getnum();
    42. Student::instance()->setnum(2);
    43. Student::instance()->getnum();
    44. }

    输出结果

    本文只是做为一个总结,记录,实践作为自己学习用。参考了网上的很多其他文章。

    主要参考了这篇C++ 单例模式-CSDN博客

  • 相关阅读:
    实例方法(instance method)、类方法、构造方法(三)
    鄂州市高新技术企业申报奖励有哪些?除了资金还有别的好处吗?2022年申报材料以及申报流程有变化吗啊?
    界面组件DevExpress ASP.NET Core v22.1 - 增强数据导出功能
    速盾:cdn 缓存图片
    EdrawMax思维导图,EdrawMax组织结构图
    Java 类加载的过程
    大一大二一心学算法的利弊痴迷于算法时间不足怎么办?
    如何学习VBA_3.2.18:DIR函数的补充说明
    加固平板电脑在无人机的应用|亿道三防onerugged
    5G与UWB定位技术融合的四种方式
  • 原文地址:https://blog.csdn.net/dz131lsq/article/details/136175527