c++設計模式——單例模式


單例模式

  1. 一個類只允許創建唯一的對象
  2. 禁止在類的外部創建對象:私有化構造函數:private或protected
  3. 類的內部維護唯一對象:靜態成員變量
  4. 提供訪問單例對象的方法:靜態成員函數,返回在類內部唯一構造的實例

創建方式

  1. 餓漢式:單例對象無論用或不用,程序啟動即創建。
  2. 懶漢式:單例對象在用的時候再創建,不用即銷毀。

一:

#include <iostream>
using namespace std;
 
class Singleton
{
    public:
        static Singleton *GetInstance()
        {
            if (m_Instance == NULL )
            {
                m_Instance = new Singleton ();
            }
            return m_Instance;
        }
     
        static void DestoryInstance()
        {
            if (m_Instance != NULL )
            {
                delete m_Instance;
                m_Instance = NULL ;
            }
        }
        // This is just a operation example
        int GetTest()
        {
            return m_Test;
        }
     
    private:
        Singleton()
        {
            m_Test = 10;
        }
        static Singleton *m_Instance;
        int m_Test;
};
Singleton *Singleton::m_Instance = NULL;
 
int main()
{
    Singleton *singletonObj=Singleton::GetInstance();
    
    cout<<singletonObj->GetTest()<<endl;
    Singleton::DestoryInstance();
    return 0;
}

二:多線程下單例模式實現

#include <iostream>
using namespace std;
 
class Singleton
{
    public:
        static Singleton *GetInstance()
        {
            if (m_Instance == NULL )
            {
                Lock(); // C++沒有直接的Lock操作,請使用其它庫的Lock,比如Boost,此處僅為了說明
                if (m_Instance == NULL )
                {
                    m_Instance = new Singleton ();
                }
                UnLock(); // C++沒有直接的Lock操作,請使用其它庫的Lock,比如Boost,此處僅為了說明
            }
            return m_Instance;
        }
     
        static void DestoryInstance()
        {
            if (m_Instance != NULL )
            {
                delete m_Instance;
                m_Instance = NULL ;
            }
        }
     
        int GetTest()
        {
            return m_Test;
        }
     
    private:
        Singleton()
        {
            m_Test = 0;
        }
        static Singleton *m_Instance;
        int m_Test;
};
 
Singleton *Singleton ::m_Instance = NULL;
 
int main()
{
    Singleton *singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<endl;
    Singleton ::DestoryInstance();
 
    return 0;
}

  1.為何要使用雙重檢查鎖定(兩次檢查null)呢?

  有兩個線程同時到達,即同時調用 GetInstance() ,此時由於 m_Instance == null ,所以很明顯,兩個線程都可以通過第一重的 m_Instance == null ,進入第一重 if 語句后,由於存在鎖機制,所以會有一個線程進入 lock 語句並進入第二重 m_Instance == null ,而另外的一個線程則會在 lock 語句的外面等待。而當第一個線程執行完 new  Singleton ()語句后,便會退出鎖定區域,此時,第二個線程便可以進入 lock 語句塊,此時,如果沒有第二重 m_Instance == null 的話,那么第二個線程還是可以調用 new  Singleton ()語句,這樣第二個線程也會創建一個 SingleTon實例,這樣也還是違背了單例模式的初衷的,所以這里必須要使用雙重檢查鎖定

  2.如果我去掉第一重 m_Instance == null ,程序還是可以在多線程下完好的運行的

  考慮在沒有第一重 m_Instance == null 的情況下,當有兩個線程同時到達,此時,由於 lock 機制的存在,第一個線程會進入 lock 語句塊,並且可以順利執行 new SingleTon(),當第一個線程退出 lock 語句塊時, singleTon 這個靜態變量已不為 null 了,所以當第二個線程進入 lock 時,還是會被第二重 m_Instance == null 擋在外面,而無法執行 new Singleton(),所以在沒有第一重 m_Instance == null 的情況下,也是可以實現單例模式的?那么為什么需要第一重 singleton == null 呢?這里就涉及一個性能問題了,因為對於單例模式的話,new SingleTon()只需要執行一次就 OK 了,而如果沒有第一重 m_Instance == null 的話,每一次有線程進入GetInstance()時,均會執行鎖定操作來實現線程同步,這是非常耗費性能的,而如果我加上第一重 m_Instance == null 的話,那么就只有在第一次,也就是 m_Instance ==null 成立時的情況下執行一次鎖定以實現線程同步,而以后的話,便只要直接返回 Singleton 實例就 OK 了而根本無需再進入 lock 語句塊了,這樣就可以解決由線程同步帶來的性能問題了。但是,如果進行大數據的操作,加鎖操作將成為一個性能的瓶頸;為此,一種新的單例模式的實現也就出現了。

三:

#include <iostream>
using namespace std;
 
class Singleton
{
public:
    static Singleton *GetInstance()
    {
        return const_cast<Singleton *>(m_Instance);//const_cast參考鏈接:http://www.cnblogs.com/tianzeng/p/9062074.html
    }
 
    static void DestoryInstance()
    {
        if (m_Instance != NULL )
        {
            delete m_Instance;
            m_Instance = NULL ;
        }
    }
 
    int GetTest()
    {
        return m_Test;
    }
 
private:
    Singleton()
    {
        m_Test = 10;
    }
    static const Singleton *m_Instance;//靜態變量,在程序初始化之前已經完成 
    int m_Test;
};

const Singleton *Singleton ::m_Instance = new Singleton();
 
int main()
{
    Singleton *singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<endl;
    Singleton ::DestoryInstance();
    return 0;
}

  因為靜態初始化在程序開始時,也就是進入主函數之前,由主線程以單線程方式完成了初始化,所以靜態初始化實例保證了線程安全性。在性能要求比較高時,就可以使用這種方式,從而避免頻繁的加鎖和解鎖造成的資源浪費。由於上述三種實現,都要考慮到實例的銷毀,關於實例的銷毀,待會在分析。由此,就出現了第四種實現方式:

四:

#include <iostream>
using namespace std;
 
class Singleton
{
    public:
        static Singleton *GetInstance()
        {
            static Singleton m_Instance;
            return &m_Instance;
        }
     
        int GetTest()
        {
            return m_Test++;
        }
     
    private:
        Singleton()
        {
            m_Test = 10;
        };
        int m_Test;
};
 
int main()
{
    Singleton *singletonObj=Singleton::GetInstance();
    cout<<singletonObj->GetTest()<<"----->"<<Singleton::GetInstance()<<endl;;
    
    singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<"----->"<<Singleton::GetInstance()<<endl;
    return 0; 
}

實例銷毀

  在上述的四種方法中,除了第四種沒有使用new操作符實例化對象以外,其余三種都使用了;我們一般的編程觀念是,new操作是需要和delete操作進行匹配的;是的,這種觀念是正確的。在上述的實現中,是添加了一個DestoryInstance的static函數,這也是最簡單,最普通的處理方法了;但是,很多時候,我們是很容易忘記調用DestoryInstance函數,就像你忘記了調用delete操作一樣。由於怕忘記delete操作,所以就有了智能指針;那么,在單例模型中,沒有“智能單例”

  在實際項目中,特別是客戶端開發,其實是不在乎這個實例的銷毀的。因為,全局就這么一個變量,全局都要用,它的生命周期伴隨着軟件的生命周期,軟件結束了,它也就自然而然的結束了,因為一個程序關閉之后,它會釋放它占用的內存資源的,所以,也就沒有所謂的內存泄漏了。但是,有以下情況,是必須需要進行實例銷毀的:

  在類中,有一些文件鎖了,文件句柄,數據庫連接等等,這些隨着程序的關閉而不會立即關閉的資源,必須要在程序關閉前,進行手動釋放;

五:

#include <iostream>
using namespace std;
 
class Singleton
{
    public:
        static Singleton *GetInstance()
        {
            return m_Instance;
        }
     
        int GetTest()
        {
            return m_Test;
        }
     
    private:
        Singleton()
        {
            m_Test = 10;
        }
        static Singleton *m_Instance;
        int m_Test;
        
        // This is important
        class GC
        {
            public :
                ~GC()
                {
                    // We can destory all the resouce here, eg:db connector, file handle and so on
                    if(m_Instance != NULL )
                    {
                        cout<< "Here is the test" <<endl;
                        delete m_Instance;
                        m_Instance = nullptr ;
                    }
                }
        };
        static GC gc;
};
 
Singleton *Singleton::m_Instance = new Singleton();
Singleton ::GC Singleton::gc;
 
int main()
{
    Singleton *singletonObj = Singleton ::GetInstance();
    cout<<singletonObj->GetTest()<<endl;
 
    return 0;
}

模式擴展

  單例模式和工廠模式在實際項目中經常見到,兩種模式的組合。

  一種產品,在一個工廠中進行生產,這是一個工廠模式的描述;而只需要一個工廠,就可以生產一種產品,這是一個單例模式的描述。所以,在實際中,一種產品,我們只需要一個工廠,此時,就需要工廠模式和單例模式的結合設計。由於單例模式提供對外一個全局的訪問點,所以,我們就需要使用簡單工廠模式中那樣的方法,定義一個標識,用來標識要創建的是哪一個單件。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM