單例模式 單例模式一般用於全局只需要一個唯一的實例的情況。 例如說,日誌讀寫的功能,一般來說全局只需一個日誌讀寫實例,然後其他的類實例去獲取這個實例進行日誌讀寫。 又例如說,有一個協作的功能,需要各個模塊發送給主控制器,主控制器需要做成單例,這樣子模塊之間操作控制器就是操作實際主控制器的內容。 怎麼 ...
單例模式
單例模式一般用於全局只需要一個唯一的實例的情況。
例如說,日誌讀寫的功能,一般來說全局只需一個日誌讀寫實例,然後其他的類實例去獲取這個實例進行日誌讀寫。
又例如說,有一個協作的功能,需要各個模塊發送給主控制器,主控制器需要做成單例,這樣子模塊之間操作控制器就是操作實際主控制器的內容。
怎麼寫一個單例模式
C語言編寫一個單例模式
先從最簡單的C語言開始,一般我們說到單例模式是指面向對象的單例模式,因為一個類生成一個實例對象就是單例模式。
那麼落到C語言,就是結構體了,我們用結構體創建單個實例。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
typedef struct _Data {
void* pData;
int value;
} Data;
void* GetData() {
static Data* pData = NULL;
if(NULL != pData) {
return pData;
}
pData = (Data*)malloc(sizeof(Data));
assert(NULL != pData);
pData->value = 0; /* init */
return (void*)pData;
}
void FreeData() {
Data* pd = GetData();
if(NULL != pd) {
free(pd);
pd = NULL;
}
}
int main() {
Data* pdata0 = GetData();
pdata0->value = 0;
Data* pdata1 = GetData();
pdata1->value = 100;
printf("value: %d\n", pdata0->value);
FreeData();
}
那麼我們看以上代碼,有兩個函數GetData和FreeData,看GetData,這裡面就是我們常見的懶漢模式,只有到需要用的時候才去創建實例。
在上面GetData中,如果是第一次進入到函數時pData是NULL,此時會申請一片記憶體空間,並且如果value需要初始化,也可以在這裡進行。到了第二次調用這個函數之後,就直接返回原來的變數了,因為變數保存在靜態區,所以每次返回的都是同一實例。這樣就實現了懶漢式的單例模式。
用C++寫一個單例模式
最經典的單例模式是SingletonMeyer's,這種單例模式由Scott Meyers 在 《Effective C++》中提出。也就是我們常見的C++的懶漢式單例模式。代碼如下:
#include <iostream>
//using namespace std;
using std::cout;
using std::endl;
class Singleton {
public:
static Singleton& getInstance() {
static Singleton s_singleton; /* 永遠只能獲取這個變數,因為傳遞出去的是引用 */
return s_singleton;
}
void SetValue(int value) {
this->m_value_ = value;
}
int GetValue() {
return this->m_value_;
}
private:
int m_value_;
Singleton() {
SetValue(0);
};
~Singleton() {};
Singleton(const Singleton&) = delete; /* 禁止複製構造 */
Singleton& operator=(const Singleton&)= delete; /* 禁止複製構造 */
};
int main() {
Singleton& a = Singleton::getInstance();
cout<<"a value is "<<a.GetValue()<<endl;
Singleton& b = Singleton::getInstance();
cout<<"b value is "<<b.GetValue()<<endl;
cout<<"change a value to 50"<<endl;
a.SetValue(50);
cout<<"b value is "<<b.GetValue()<<endl;
return 0;
}
如上,我們將構造函數放到私有,這樣就只能通過getInstance來構造
然後我們,在初次調用getInstance的時候創建實例,並返回引用。這樣就實現了一種懶漢式的單例模式。
然後後續的getInstance獲取到的都會是第一次申請的Singleton對象
在main函數中,我們先列印兩個引用的value,兩個值都是0,然後我們改變其中一個引用的value,這個時候另外一個引用的value也發生了改變,因為這兩個引用指向的是同一個實例。
a value is 0
b value is 0
change a value to 50
b value is 50
用C++寫另一種單例模式
另外一種單例模式,就是餓漢式。
#include <iostream>
using std::cout;
using std::endl;
class Singleton {
public:
static Singleton& getInstance() {
return m_singleton_;
}
void SetValue(int value) {
this->m_value_ = value;
}
int GetValue() {
return this->m_value_;
}
private:
static Singleton m_singleton_;
int m_value_;
Singleton() {
SetValue(0);
}
~Singleton() {};
Singleton(const Singleton&) = delete; /* 禁止複製構造 */
Singleton& operator=(const Singleton&)= delete; /* 禁止複製構造 */
};
Singleton Singleton::m_singleton_; /* 靜態成員類外初始化 */
int main() {
Singleton& a = Singleton::getInstance();
cout<<"a value is "<<a.GetValue()<<endl;
Singleton& b = Singleton::getInstance();
cout<<"b value is "<<b.GetValue()<<endl;
cout<<"change a value to 50"<<endl;
a.SetValue(50);
cout<<"b value is "<<b.GetValue()<<endl;
return 0;
}
餓漢式和懶漢式唯一不同的是,本來在函數的靜態成員變成了類成員變數。
這樣就導致這個成員在類創建的時候就已經生成了實例。也就是所謂的餓漢式,先把東西都準備好,然後需要的時候直接返回就好
上面代碼的的main函數沒有任何改動,就是演示兩個引用指向的是同一實例。
線程安全?
看了懶漢式和餓漢式的,我們大概可以知道,餓漢式的是線程安全的,因為它在類載入時已經完成對實例的初始化了
而懶漢式只在第一次訪問的時候才進行初始化,這樣會導致多條線程同時是第一次訪問的時候,實例創建了多次。
那麼怎麼保證懶漢式線程安全呢,加鎖就可以了。
在第一次訪問時判斷是否有加鎖,這裡具體代碼就不演示了。