C++设计模式(一):单例模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hf19931101/article/details/79561207

单例模式

单例模式: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决: 一个全局使用的类频繁地创建与销毁。

何时使用: 想控制实例数目,节省系统资源的时候。

如何解决: 判断系统是否已存在单例,如果有则返回,没有则创建。

关键代码: 构造函数是私有的。

单例大约有两种实现方法:懒汉与饿汉。

懒汉: 故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,所以上边的经典方法被归为懒汉实现;

饿汉: 饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。

特点与选择:
由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。在访问量较小时,采用懒汉实现。这是以时间换空间。

1.1 懒汉模式(Lazy Singleton)

非线程安全的实现:

class Singleton
{
private:
    static Singleton* m_pIns;

    Singleton() {};
    ~Singleton() {};
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton& that) = delete;

public:
    static Singleton* GetInstance();
    static void Release();
};

Singleton* Singleton::GetInstance()
{
    if (!m_pIns)
    {
        m_pIns = new Singleton();
    }
    return m_pIns;
}

void Singleton::Release()
{
    if (m_pIns)
    {
        delete m_pIns;
    }
}

Singleton* Singleton::m_pIns = nullptr;
int main()
{

    Singleton* pIns= Singleton::GetInstance();
    pIns->Release();

    return 0;
}

考虑两个线程同时首次调用instance方法且同时检测到p是NULL值,则两个线程会同时构造一个实例给p,这是严重的错误。可以使用的所谓的“双检锁模式”(Double-Checked Locking Pattern)。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。可以使用C++11中的来实现Lock()和Unlock():

Singleton* Singleton::GetInstance()
{
    if (!m_pIns)
    {
        Lock();
        if (!m_pIns)
        {
            m_pIns = new Singleton();
        }
        Unlock();
    }
    return m_pIns;
}

1.2 饿汉模式(Eager Singleton)


class Singleton
{
private:
    static Singleton* m_pIns;

    Singleton() {};
    ~Singleton() {};
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton& that) = delete;

public:
    static Singleton* GetInstance();
    static void Release();
};

Singleton* Singleton::GetInstance()
{
    return m_pIns;
}

void Singleton::Release()
{
    if (m_pIns)
    {
        delete m_pIns;
    }
}

Singleton* Singleton::m_pIns = new Singleton();

int main()
{

    Singleton* pIns= Singleton::GetInstance();
    pIns->Release();

    return 0;
}

这种实现在编译器初始化的时候就完成了实例的创建,由于实例化是在初始化阶段执行的,所以没有线程安全的问题,但是潜在问题在于no-localstatic对象(函数外的static对象)在不同编译单元(可理解为cpp文件和其包含的头文件)中的初始化顺序是未定义的。如果在初始化完成之前调用Instance()方法会返回一个未定义的实例。例如有两个单例 SingletonA 和 SingletonB ,都采用了 Eager Initialization ,那么如果 SingletonA 的初始化需要 SingletonB,而这两个单例又在不同的编译单元,初始化顺序是不定的,如果 SingletonA 在 SingletonB 之前初始化,就会出错。

为了解决上面的问题,Scott Meyers在《Effective C++》(Item 04)中的提出另一种更优雅的单例模式实现,使用local static对象(函数内的static对象)。当第一次访问 Instance() 方法时才创建实例。

class Singleton
{
private:
    static Singleton* m_pIns;

    Singleton() {};
    ~Singleton() {};
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton& that) = delete;

public:
    static Singleton* GetInstance();
    static void Release();
};

Singleton* Singleton::GetInstance()
{
    m_pIns = new Singleton();
    return m_pIns;
}

void Singleton::Release()
{
    if (m_pIns)
    {
        delete m_pIns;
    }
}

int main()
{

    Singleton* pIns= Singleton::GetInstance();
    pIns->Release();

    return 0;
}

C++0x之后该实现是线程安全的。

参考博客:
https://www.cnblogs.com/chengjundu/p/8473564.html
http://blog.csdn.net/woxiaohahaa/article/details/51344409

猜你喜欢

转载自blog.csdn.net/hf19931101/article/details/79561207