C++單例模式


單例模式是任何面向對象語言繞不過的,單例模式是很有必要的,接下來我用最朴素的語言來解釋和記錄單例模式的學習。

  • 什么是單例模式?

單例模式就是一個類只能被實例化一次 ,更准確的說是只能有一個實例化的對象的類。

  • 創建一個單例模式的類(初想)

一個類只能有一個實例化的對象,那么這個類就要禁止別人new出來,或者通過直接定義一個對象出來

class CAR
{
public:
    CAR(){}
    ~CAR(){}
};
CAR a;
CAR *b = new CAR;

很明顯這樣的類可以被程序員用上面這兩種方式實例化。那么考慮,如何禁止用上面的這兩種方式實例化一個類呢?

如果把構造函數私有化,很明顯上面這兩種方法都會默認的去調用構造函數,當構造函數是private或者protected時,構造函數將無法從外部調用。

class CSingleton
{
private:
    CSingleton()
    {
    }
};
int main()
{
    CSingleton t;
    CSingleton *tt = new CSingleton;
}

上面的代碼選擇了這樣實例化類,很明顯編譯器會報錯,因為私有化的構造函數無法被外部調用

error: ‘CSingleton::CSingleton()’ is private

既然構造函數是私有了,那么他就只能被類內部的成員函數調用,所以我們可以搞一個共有函數去供外部調用,然后這個函數返回一個對象,為了保證多次調用這個函數返回的是一個對象,我們可以把類內部要返回的對象設置為靜態的,就有了下面的代碼:

class CSingleton
{
private:
    CSingleton()
    {
    }
    static CSingleton *p;
public:
    static CSingleton* getInstance()
    {
        if(p == NULL)
            p = new CSingleton();
        return p;
    }
};
CSingleton* CSingleton::p = NULL;

我們在主函數調用來測試一下

int main()
{
    CSingleton *t = CSingleton::getInstance();
    CSingleton *tt = CSingleton::getInstance();
    cout << t << endl << tt << endl;
}

結果是

0x1c59c0
0x1c59c0

兩個地址一樣,證明我們的單例類的正確的,原理其實很簡單,第一次調用獲取實例的函數時,靜態類的變量指針空,所以會創建一個對象出來,第二次調用就不是空了,直接返回第一次的對象指針(地址)。

同時思考另一個問題,如果兩個線程同時獲取實例化對象呢?顯然是不行的,會出現兩個線程同時要對象的時候指針還都是空的情況就完了,想到這種情況你肯定會毫不猶豫的去加個鎖。(進一步思考)

class CSingleton
{
private:
    CSingleton()
    {
        pthread_mutex_init(&mtx,0);
    }
    static CSingleton *p;
public:
    static pthread_mutex_t mtx;
    static CSingleton* getInstance()
    {
        if(p == NULL)
        {
            pthread_mutex_lock(&mtx);
            p = new CSingleton();
            pthread_mutex_unlock(&mtx);
        }
        return p;
    }
};
pthread_mutex_t CSingleton::mtx;
CSingleton* CSingleton::p = NULL;

上面的代碼就是加鎖之后的了,你可以用下面的方法調用

void* fun1(void *)
{
    while(1)
    {
        CSingleton *pt = CSingleton::getInstance();
        cout << "fun1: pt_addr = " << pt << endl;
        Sleep(1000);
    }
}
void* fun2(void *)
{
    while(1)
    {
        CSingleton *pt = CSingleton::getInstance();
        cout << "fun2: pt_addr = " << pt << endl;
        Sleep(1000);
    }
}
void callSingleton()
{
    pthread_mutex_init(&CSingleton::mtx,0);
    pthread_t pt_1 = 0;
    pthread_t pt_2 = 0;
    int ret = pthread_create(&pt_1,0,&fun1,0);
    if(ret != 0)
    {
        printf("error\n");
    }
    ret = pthread_create(&pt_2,0,&fun2,0);
    if(ret != 0)
    {
        printf("error\n");
    }
    pthread_join(pt_1,0);
    pthread_join(pt_2,0);
}

你可以這樣在fun1,fun2中隨意的去實例化這個類了,運行結果如下

fun1: pt_addr = 0xb85a38
fun2: pt_addr = 0xb85a38
fun1: pt_addr = 0xb85a38
fun2: pt_addr = 0xb85a38
fun1: pt_addr = 0xb85a38
fun2: pt_addr = 0xb85a38

總結一下我們的這種實現單例模式的方式:類中聲明一個靜態的本類指針,再寫一個public的函數來讓這個指針指向我們新創建的實例,返回這個指針(這個實例的地址),並進行加鎖,這個對象就永遠只有一份,然后單例模式就實現了。

class CSingleton
{
private:
    CSingleton()
    {
        pthread_mutex_init(&mtx,0);
    }
public:
    static pthread_mutex_t mtx;
    static CSingleton* getInstance()
    {
        pthread_mutex_lock(&mtx);
        static CSingleton obj;
        pthread_mutex_unlock(&mtx);
        return &obj;
    }
};
pthread_mutex_t CSingleton::mtx;

也可以像上面的代碼一樣把靜態對象的放到函數里面,這樣就省的在去外部聲明一下了,只要返回一個靜態類的地址,就算這個函數執行完也不會被銷毀,它被保存在靜態區和全局變量差不多。

再次總結:只要返回一個本類對象的地址就好了,這個地址要是靜態的。別忘記加鎖。

而且上面這種方式也被人們成為懶漢模式,為什么叫懶漢?因為這樣的方式只有在我調用 CSingleton::getInstance(); 的時候才會返回一個實例化的對象,懶死了,我不要你你就不給我,是不是?

 

 

下面這種方式就和上面的不同,人家還沒要,我就忍不住先給人家准備好了,如飢似渴,所以也叫餓漢模式。

我們注意到上面第一種方式,類中的靜態變量要先被外部聲明,否則編譯器不會為它分配空間,像這樣 CSingleton* CSingleton::p = NULL; 其實我們可以在這一步就new一個對象出來,因為p是CSingleton的成員,它是可以調用構造函數的哦,於是我們改成這樣就是餓漢模式了

class CSingleton
{
private:
    CSingleton()
    {
    }
    static CSingleton *p;
public:
    static CSingleton* getInstance()
    {
        return p;
    }
};
CSingleton* CSingleton::p = new CSingleton();

我們這樣鎖也不用加了,因為我們調用 CSingleton::getInstance() 之前這個類就已經被實例化了,我們調用這個函數的目地只是為了得到這個對象的地址。餓漢模式就實現了

總結:利用靜態變量和私有化構造函數的特性來實現單例模式。搞一個靜態的自身類指針,然后把構造函數私有化,這樣new的時候就只能讓本類中的成員調用,然后不擇手段在類內部new出這個對象,並提供一種方法供外部得到這個對象的地址。


免責聲明!

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



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