溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++如何實現(xiàn)單例模式

發(fā)布時間:2021-09-29 13:33:03 來源:億速云 閱讀:124 作者:小新 欄目:開發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)C++如何實現(xiàn)單例模式,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

    單例模式:就是只有一個實例。

    singleton pattern單例模式:確保某一個類在程序運行中只能生成一個實例,并提供一個訪問它的全局訪問點。這個類稱為單例類。如一個工程中,數(shù)據(jù)庫訪問對象只有一個,電腦的鼠標(biāo)只能連接一個,操作系統(tǒng)只能有一個窗口管理器等,這時可以考慮使用單例模式。

    眾所周知,c++中,類對象被創(chuàng)建時,編譯系統(tǒng)為對象分配內(nèi)存空間,并自動調(diào)用構(gòu)造函數(shù),由構(gòu)造函數(shù)完成成員的初始化工作,也就是說使用構(gòu)造函數(shù)來初始化對象。

    1、那么我們需要把構(gòu)造函數(shù)設(shè)置為私有的 private,這樣可以禁止別人使用構(gòu)造函數(shù)創(chuàng)建其他的實例。

    2、又單例類要一直向系統(tǒng)提供這個實例,那么,需要聲明它為靜態(tài)的實例成員,在需要的時候,才創(chuàng)建該實例。

    3、且應(yīng)該把這個靜態(tài)成員設(shè)置為 null,在一個public 的方法里去判斷,只有在靜態(tài)實例成員為 null,也就是沒有被初始化的時候,才去初始化它,且只被初始化一次。

    通常我們可以讓一個全局變量使得一個對象被訪問,但它不能阻止你實例化多個對象。如果采用全局或者靜態(tài)變量的方式,會影響封裝性,難以保證別的代碼不會對全局變量造成影響。

    一個最好的辦法是,讓類自身負(fù)責(zé)保存它的唯一實例。這個類可以保證沒有其他實例可以被創(chuàng)建,并且它可以提供一個訪問該實例的方法,單例模式比全局對象好還包括,單例類可以繼承。

    單例模式又分為兩種基本的情形:餓漢式和懶漢式

    直接在靜態(tài)區(qū)初始化 instance,然后通過 get 方法返回,這樣這個類每次直接先生成一個對象,好像好久沒吃飯的餓漢子,急著吃飯一樣,急切的 new 對象,這叫做餓漢式單例類?;蛘呤窃?get 方法中才 new instance,然后返回這個對象,和懶漢字一樣,不主動做事,需要調(diào)用 get 方法的時候,才 new 對象,這就叫做懶漢式單例類。

    如下是懶漢式單例類

    //單例模式示例
    class Singleton
    {
    public:
        static Singleton * getInstance()
        {
            if (instance == NULL) {
                instance = new Singleton();
            }
    
            return instance;
        }
    
    private:
        //私有的構(gòu)造函數(shù),防止外人私自調(diào)用
        Singleton()
        {
            cout << "實例化了" << count << "個對象!" << endl;
            count++;
        }
        //聲明一個靜態(tài)實例,靜態(tài)函數(shù)只能使用靜態(tài)的數(shù)據(jù)成員。整個類中靜態(tài)成員只有一個實例,通常在實現(xiàn)源文件中被初始化。
        static Singleton *instance;
        //記錄實例化的對象
        int count = 1;
    };
    
    Singleton * Singleton::instance = NULL;
    
    int main(void)
    {
        Singleton::getInstance();
        Singleton::getInstance();
        Singleton::getInstance();
        Singleton::getInstance();
    
        return 0;
    }

    實例化了1個對象!

    Program ended with exit code: 0

    小結(jié):

    懶漢式單例模式是用時間換取控件,餓漢式單例模式,是用空間換取時間。

    繼續(xù)分析,考慮多線程下的懶漢式單例模式

    上述代碼在單線程的情況下,運行正常,但是遇到了多線程就出問題,假設(shè)有兩個線程同時運行了這個單例類,同時運行到了判斷 if 語句,并且當(dāng)時,instance 實例確實沒有被初始化呢,那么兩個線程都會去運行并創(chuàng)建實例,此時就不滿足單例類的要求了。那么我們需要寫上線程同步的功能。

    //考慮到多線程情形下的單例模式
    class Singleton
    {
    public:
        //get 方法
        static Singleton * getInstance(){
            //聯(lián)系互斥信號量機(jī)制,給代碼加鎖
            lock();
            //判斷 null
            if (NULL == instance) {
                //判斷類沒有生成對象,才實例化對象,否則不再實例化
                instance = new Singleton();
            }
            //使用完畢,解鎖
            unlock();
            //返回一個實例化的對象
            return instance;
        }
    private:
        //聲明對象計數(shù)器
        int count = 0;
        //聲明一個靜態(tài)的實例
        static Singleton *instance;
        //私有構(gòu)造函數(shù)
        Singleton(){
            count++;
            cout << "實例化了" << count << "個對象!" << endl;
        }
    };
    //初始化 instance
    Singleton * Singleton::instance = NULL;

    此時,還是有 ab 兩個線程來運行這個單例類,由于在同一時刻,只有一個線程能拿到同步鎖(互斥信號量機(jī)制),a 拿到了同步鎖,b 只能等待,如果 a發(fā)現(xiàn)實例還沒創(chuàng)建,a 就會創(chuàng)建一個實例,創(chuàng)建完畢,a 釋放同步鎖,然后 b 才能拿到同步鎖,繼續(xù)運行接下來的代碼,b 發(fā)現(xiàn) a 線程運行的時候,已經(jīng)生成了一個實例,b 線程就不會重復(fù)創(chuàng)建實例了,這樣就保證了我們在多線程環(huán)境中只能得到一個實例。

    繼續(xù)分析多線程下的懶漢式單例模式

    代碼中,每次 get 方法中,得到 instance,都要判斷是否為空,且判斷是否為空之前,都要先加同步鎖,如果線程很多的時候,就要先等待加了同步鎖的線程運行完畢,才能繼續(xù)判斷余下的線程,這樣就會造成大量線程的阻塞,且加鎖是個非常消耗時間的過程,應(yīng)該盡量避免(除非很有必要的時候)??尚械霓k法是,雙重判斷方法。

    因為,只是在實例還沒有創(chuàng)建的時候,需要加鎖判斷,保證每次只有一個線程創(chuàng)建實例,而當(dāng)實例已經(jīng)創(chuàng)建之后,其實就不需要加鎖操作了。

    雙重判斷的線程安全的懶漢式單例模式

    class Singleton
    {
    public:
        //get 方法
        static Singleton * getInstance(){
            //先判斷一次 null,只有 null 的時候需要加鎖,其他的時候,其實不需要加鎖
            if (NULL == instance) {
                //聯(lián)系互斥信號量機(jī)制,給代碼加鎖
                lock();
                //然后再次判斷 null
                if (NULL == instance) {
                    //判斷類沒有生成對象,才實例化對象,否則不再實例化
                    instance = new Singleton();
                }
                //使用完畢,解鎖
                unlock();
            }
                    //返回一個實例化的對象
            return instance;
        }
    private:
        //聲明對象計數(shù)器
        int count = 0;
        //聲明一個靜態(tài)的實例
        static Singleton *instance;
        //私有構(gòu)造函數(shù)
        Singleton(){
            count++;
            cout << "實例化了" << count << "個對象!" << endl;
        }
    };
    //初始化 instance
    Singleton * Singleton::instance = NULL;

    這樣的雙重檢測機(jī)制,提高了單例模式在多線程下的效率,因為這樣的代碼,只需要在第一次創(chuàng)建實例的時候,需要加鎖,其他的時候,線程無需排隊等待加鎖之后,再去判斷了,比較高效。

    再看餓漢式的單例模式,之前看了懶漢式的單例類,是線程不安全的,通過加鎖(雙重鎖),實現(xiàn)線程安全

    回憶餓漢式單例類:直接在靜態(tài)區(qū)初始化 instance,然后通過 get 方法返回,這樣這個類每次直接先生成一個對象,好像好久沒吃飯的餓漢子,急著吃飯一樣,急切的 new 對象,這叫做餓漢式單例類。

    class Singleton
    {
    public:
        //get 方法
        static Singleton * getInstance(){
            //返回一個實例化的對象
            return instance;
        }
    private:
        //聲明一個靜態(tài)的實例
        static Singleton *instance;
        //私有構(gòu)造函數(shù)
        Singleton(){
    
        }
    };
    //每次先直接實例化instance,get 方法直接返回這個實例
    Singleton * Singleton::instance = new Singleton();

    注意:靜態(tài)初始化實例可以保證線程安全,因為靜態(tài)實例初始化在程序開始時進(jìn)入主函數(shù)之前,就由主線程以單線程方式完成了初始化!餓漢式的單例類,也就是靜態(tài)初始化實例保證其線程安全性,故在性能需求較高時,應(yīng)使用這種模式,避免頻繁的鎖爭奪。

    繼續(xù)看單例模式

    上面的單例模式?jīng)]有 destory() 方法,也就是說,貌似上面的單例類沒有主動析構(gòu)這個唯一實例!然而這就導(dǎo)致了一個問題,在程序結(jié)束之后,該單例對象沒有delete,導(dǎo)致內(nèi)存泄露!下面是一些大神的方法:一個妥善的方法是讓這個類自己知道在合適的時候把自己刪除,或者說把刪除自己的操作掛在操作系統(tǒng)中的某個合適的點上,使其在恰當(dāng)?shù)臅r候被自動執(zhí)行。

    我們知道,程序在結(jié)束的時候,系統(tǒng)會自動析構(gòu)所有的全局變量。事實上,系統(tǒng)也會析構(gòu)所有的類的靜態(tài)成員變量,就像這些靜態(tài)成員也是全局變量一樣。如果在類的析構(gòu)行為中有必須的操作,比如關(guān)閉文件,釋放外部資源,那么上面的代碼無法實現(xiàn)這個要求。我們需要一種方法,正常的刪除該實例。利用這些特征,我們可以在單例類中定義一個這樣的靜態(tài)成員變量,而它的唯一工作就是在析構(gòu)函數(shù)中刪除單例類的實例。如下面的代碼中的Garbage類:

    class Singleton
    {
    public:
        //get 方法
        static Singleton * getInstance(){
            //判斷單例否
            if (NULL == instance) {
                instance = new Singleton();
            }
            //返回一個實例化的對象
            return instance;
        }
        //c++ 嵌套的內(nèi)部類,作用是刪除單例類對象,Garbage被定義為Singleton的內(nèi)嵌類,以防該類被在其他地方濫用。
        class Garbage
        {
        public:
            ~Garbage(){
                if (Singleton::instance != NULL) {
                    cout << "單例類的唯一實例被析構(gòu)了" << endl;
                    delete Singleton::instance;
                }
            }
        };
    
    private:
        //單例類中聲明一個觸發(fā)垃圾回收類的靜態(tài)成員變量,它的唯一工作就是在析構(gòu)函數(shù)中刪除單例類的實例,利用程序在結(jié)束時析構(gòu)全局變量的特性,選擇最終的釋放時機(jī);
        static Garbage garbage;
        //聲明一個靜態(tài)的實例
        static Singleton *instance;
        //單例類的私有構(gòu)造函數(shù)
        Singleton(){
            cout << "調(diào)用了單例類的構(gòu)造函數(shù)" << endl;
        }
        //單例類的私有析構(gòu)函數(shù)
        ~Singleton(){
            cout << "調(diào)用了單例類的析構(gòu)函數(shù)" << endl;
        }
    };
    //初始化內(nèi)部的靜態(tài)變量,目睹是啟動刪除的析構(gòu)函數(shù),如果不初始化,就不會被析構(gòu)
    //內(nèi)部類可以訪問外部類的私有成員,外部類不能訪問內(nèi)部類的私有成員!
    Singleton::Garbage Singleton::garbage;
    //初始化instance為 null
    Singleton * Singleton::instance = NULL;
    
    int main(void)
    {
        Singleton *a = Singleton::getInstance();
        Singleton *b = Singleton::getInstance();
        Singleton *c = Singleton::getInstance();
    
        if (a == b) {
            cout << "a = b" << endl;
        }
    
        return 0;
    }

    調(diào)用了單例類的構(gòu)造函數(shù)

    a = b

    單例類的唯一實例被析構(gòu)了

    調(diào)用了單例類的析構(gòu)函數(shù)

    Program ended with exit code: 0

    類Garbage被定義為Singleton的內(nèi)嵌類,以防該類在其他地方濫用,程序運行結(jié)束時,系統(tǒng)會調(diào)用Singleton的靜態(tài)成員garbage的析構(gòu)函數(shù),該析構(gòu)函數(shù)會刪除單例的唯一實例,使用這種方法釋放單例對象有以下特征:

    1、在單例類內(nèi)部定義專有的嵌套類;

    2、在單例類內(nèi)定義私有的專門用于釋放的靜態(tài)成員;

    3、利用程序在結(jié)束時析構(gòu)全局變量的特性,選擇最終的釋放時機(jī);

    4、使用單例的代碼不需要任何操作,不必關(guān)心對象的釋放。

    其實,繼續(xù)想單例類的實現(xiàn),有的人會這樣做:

    在程序結(jié)束時調(diào)一個專門的方法,這個方法里判斷實例對象是否為 null,如果不為 null,就對返回的指針掉用delete操作。這樣做可以實現(xiàn)刪除單例的功能,但不僅很丑陋,而且容易出錯。因為這樣的附加代碼很容易被忘記,而且也很難保證在delete之后,沒有代碼再調(diào)用GetInstance函數(shù)。不推薦直接的刪除方法。

    繼續(xù)查看單例模式:單例模式在實際開發(fā)過程中是很有用的

    單例模式的特征總結(jié):

    1、一個類只有一個實例

    2、提供一個全局訪問點

    3、禁止拷貝

    逐個分析:

    1、實現(xiàn)只有一個實例,需要做的事情:將構(gòu)造函數(shù)聲明為私有

    2、提供一個全局訪問點,需要做的事情:類中創(chuàng)建靜態(tài)成員和靜態(tài)成員方法

    3、禁止拷貝:把拷貝構(gòu)造函數(shù)聲明為私有,并且不提供實現(xiàn),將賦值運算符聲明為私有,防止對象的賦值

    完整的單例類實現(xiàn)代碼如下:

    class Singleton
    {
    public:
        //get 方法
        static Singleton * getInstance(){
            if (NULL == instance) {
                lock();
                //判斷單例否
                if (NULL == instance) {
                    instance = new Singleton();
                }
                unlock();
            }
            //返回一個實例化的對象
            return instance;
        }
        //c++ 嵌套的內(nèi)部類,作用是刪除單例類對象,Garbage被定義為Singleton的私有內(nèi)嵌類,以防該類被在其他地方濫用。
        class Garbage
        {
        public:
            ~Garbage(){
                if (Singleton::instance != NULL) {
                    cout << "單例類的唯一實例被析構(gòu)了" << endl;
                    delete Singleton::instance;
                }
            }
        };
        
    private:
        //單例類中定義一個這樣的靜態(tài)成員變量,而它的唯一工作就是在析構(gòu)函數(shù)中刪除單例類的實例,利用程序在結(jié)束時析構(gòu)全局變量的特性,選擇最終的釋放時機(jī);
        static Garbage garbage;
        //聲明一個靜態(tài)的實例
        static Singleton *instance;
        //單例類的私有構(gòu)造函數(shù)
        Singleton(){
            cout << "調(diào)用了單例類的構(gòu)造函數(shù)" << endl;
        }
        //單例類的私有析構(gòu)函數(shù)
        ~Singleton(){
            cout << "調(diào)用了單例類的析構(gòu)函數(shù)" << endl;
        }
        //把拷貝構(gòu)造函數(shù)聲明為私有,就可以禁止外人拷貝對象,也不用實現(xiàn)它,聲明私有即可
        Singleton(const Singleton &copy);
        //把賦值運算符重載為私有的,防止對象之間的賦值操作
        Singleton & operator=(const Singleton &other);
    };
    //初始化內(nèi)部似有淚的靜態(tài)變量,目睹是啟動刪除的析構(gòu)函數(shù),如果不初始化,就不會被析構(gòu)
    //內(nèi)部類可以訪問外部類的私有成員,外部類不能訪問內(nèi)部類的私有成員!
    Singleton::Garbage Singleton::garbage;
    //初始化instance為 null
    Singleton * Singleton::instance = NULL;
    
    int main(void)
    {
        Singleton *a = Singleton::getInstance();
        Singleton *b = Singleton::getInstance();
        Singleton *c = Singleton::getInstance();
        
        if (a == b) {
            cout << "a = b" << endl;
        }
        
        return 0;
    }

    單例類de測試,兩個方法:

    1、實例化多個對象,看調(diào)用了幾次構(gòu)造函數(shù),如果只調(diào)用一次,說明只創(chuàng)建一個實例

    2、單步跟蹤,查看對象的地址,是否一樣,一樣則為一個對象

    關(guān)于“C++如何實現(xiàn)單例模式”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,使各位可以學(xué)到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

    c++
    AI