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

1.单例模式说明

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

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

2.使用单例模式的优点

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

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

3.单例模式实现分析

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

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

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

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

把构造函数写到private:下面

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

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

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

4.实际的应用场景

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

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

5.最基础的懒汉模式

#include 


using namespace std;

class Singleton {
private:
    static Singleton* m_pInstance;

private:
    Singleton() {
        cout << "constructor called!" << endl;
    }

    Singleton(Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    ~Singleton() {
        cout << "destructor called!" << endl;
    }

    static Singleton* getInstance() {
        if (m_pInstance == nullptr) {
            m_pInstance = new Singleton;
        }

        return m_pInstance;
    }
};

Singleton* Singleton::m_pInstance = nullptr;


int main()
{
    Singleton* instance1 = Singleton::getInstance();
    Singleton* instance2 = Singleton::getInstance();
    std::cout << "Hello World!\n";
}

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

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

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

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

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

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

#include 
#include 

class Singleton {
public:
    using Ptr = std::shared_ptr;

    ~Singleton() {
        std::cout << "destructor called!" << std::endl;
    }

    Singleton(Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Ptr getInstance() {
        if (m_pInstance == nullptr) {
            std::lock_guard lk(m_mutex);

            if (m_pInstance == nullptr) {
                m_pInstance = std::shared_ptr(new Singleton);
            }
        }

        return m_pInstance;
    }

private:
    Singleton() {
        std::cout << "constructor called!" << std::endl;
    }

private:
    static Ptr m_pInstance;
    static std::mutex m_mutex;
};


Singleton::Ptr Singleton::m_pInstance = nullptr;
std::mutex Singleton::m_mutex;

int main() {
    Singleton::Ptr instance1 = Singleton::getInstance();
    Singleton::Ptr instance2 = Singleton::getInstance();

    return 0;
}

输出结果为

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

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

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

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

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

#include 
#include 

class Singleton
{
public:
    ~Singleton() {
        std::cout << "destructor called!" << std::endl;
    }

    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }

private:
    Singleton() {
        std::cout << "constructor called!" << std::endl;
    }
};

int main(int argc, char* argv[])
{
    Singleton& instance_1 = Singleton::getInstance();
    Singleton& instance_2 = Singleton::getInstance();

    return 0;
}

这种方法又叫做 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.单例模式之饿汉式

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

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

#include 
#include 


class Singleton {
public:
    using Ptr = std::shared_ptr;
    static Ptr getInstance() {
        return m_pInstance;
    }

    ~Singleton() {
        std::cout << "destructor called!" << std::endl;
    }
private:
    static Ptr m_pInstance;
private:
    Singleton() {
        std::cout << "constructor called!" << std::endl;
    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;
};
Singleton::Ptr Singleton::m_pInstance = std::shared_ptr(new Singleton);


int main(int argc, char* argv[])
{
    Singleton::Ptr instance1 = Singleton::getInstance();
    Singleton::Ptr instance2 = Singleton::getInstance();

    return 0;
}

9.单例模式之单例模板

#include 
#include 


//单例模板
template 
class Singleton {
private:
    Singleton() = default;
    ~Singleton() = default;
public:
    Singleton(const Singleton&) = delete;
    void operator = (const Singleton&) = delete;
   
    static T* instance()
    {
        static T m_instance;
        return &m_instance;
    }
};


class Student {
    public:
    Student() {
        std::cout << "constructor called!" << std::endl;
    }

    ~Student() {
        std::cout << "destructor called!" << std::endl;
    }
private:
    int num;
public:
    void setnum(int value) {
        num = value;
    }

    void getnum()
    {
        std::cout << num << std::endl;
    }
};


int main(int argc, char* argv[])
{
    Singleton::instance()->setnum(1);
    Singleton::instance()->getnum();
    Singleton::instance()->setnum(2);
    Singleton::instance()->getnum();
}

输出结果

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

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

9.改善后的单例模板类

#include 
#include 


//单例模板
template 
class Singleton {
public:
    Singleton() = default;
    virtual ~Singleton() = default;
    Singleton(const Singleton&) = delete;
    void operator = (const Singleton&) = delete;

    static T* instance()
    {
        static T m_instance;
        return &m_instance;
    }
};



class Student:public Singleton {
    private:
    Student() {
        std::cout << "constructor called!" << std::endl;
    }

    ~Student() {
        std::cout << "destructor called!" << std::endl;
    }

    friend class Singleton;
private:
    int num;
public:
    void setnum(int value) {
        num = value;
    }

    void getnum()
    {
        std::cout << num << std::endl;
    }
};


int main(int argc, char* argv[])
{
    //Student stu;  报错 不可访问构造函数
    Student::instance()->setnum(1);
    Student::instance()->getnum();
    Student::instance()->setnum(2);
    Student::instance()->getnum();
}

输出结果

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

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

你可能感兴趣的:(设计模式,C++,c++,单例模式,开发语言)