柚子快報(bào)邀請(qǐng)碼778899分享:C++設(shè)計(jì)模式 —— 單例模式
柚子快報(bào)邀請(qǐng)碼778899分享:C++設(shè)計(jì)模式 —— 單例模式
設(shè)計(jì)模式 —— 單例模式
一個(gè)問題C++11后代寫法單例模式的兩種模式餓漢模式懶漢模式
在了解C++面向?qū)ο蟮娜筇匦裕悍庋b,繼承,多態(tài)之后。我們創(chuàng)建類的時(shí)候就有了比較大的空間。但是,我們平時(shí)在創(chuàng)建類的時(shí)候,不是簡(jiǎn)單寫個(gè)class和繼承關(guān)系就完事了的。我們寫的類要在一些場(chǎng)景下滿足一些特殊的要求。
一個(gè)問題
現(xiàn)在有一個(gè)具體的要求,創(chuàng)建一個(gè)類,保證處處只有一個(gè)實(shí)體,這個(gè)要求在平常的工作中是很常見的, 配置文件管理,日志系統(tǒng),數(shù)據(jù)庫連接池, 線程池等。
如何實(shí)現(xiàn)呢?保證只有一個(gè)實(shí)體,可以考慮靜態(tài)成員變量,我們之前在C++繼承中說過,無論繼承關(guān)系有多少層,只要為靜態(tài)成員,全局就只有一份。所以我們可以先從這個(gè)方向入手。
//單例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
}
return instance;
}
static SingleClass* instance;
};
//靜態(tài)成員在外部初始化
SingleClass* SingleClass::instance = nullptr;
int main()
{
SingleClass* s1 = SingleClass::GetInstance();
SingleClass* s2 = SingleClass::GetInstance();
if (s1 == s2)
{
std::cout << "s1和s2為同一實(shí)體" << std::endl;
}
else
{
std::cout << "s1和s2不為同一實(shí)體" << std::endl;
}
}
這樣看上去問題解決了,但是: 我們可以在類外創(chuàng)建對(duì)象,這不符合我們的要求,究其原因,我們把構(gòu)造函數(shù)設(shè)為了公有解決這個(gè)問題將它聲明為私有就可以了。
//單例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
}
return instance;
}
private:
SingleClass(){} //構(gòu)造函數(shù)為私有
static SingleClass* instance;
};
這樣就可以了,但是別忘了我們還有拷貝構(gòu)造和賦值拷貝,這兩個(gè)也可以構(gòu)造出新對(duì)象,所以為了保險(xiǎn)可以直接把他倆禁了:
//單例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
}
return instance;
}
private:
SingleClass(){} //構(gòu)造函數(shù)為私有
SingleClass(const SingleClass&) = delete; // 禁止拷貝構(gòu)造
SingleClass& operator=(const SingleClass&) = delete; // 禁止賦值操作
static SingleClass* instance;
};
//靜態(tài)成員在外部初始化
SingleClass* SingleClass::instance = nullptr;
這就是單例模式的雛形了。
單例模式(Singleton Pattern) 實(shí)現(xiàn)一個(gè)類保證處處只有一個(gè)實(shí)例。單例模式的核心思想是:
私有化構(gòu)造函數(shù):禁止外部直接創(chuàng)建對(duì)象。
靜態(tài)方法獲取實(shí)例:通過靜態(tài)成員函數(shù)控制唯一實(shí)例的創(chuàng)建和訪問。
禁止拷貝和賦值:防止通過拷貝構(gòu)造函數(shù)或賦值操作生成新實(shí)例。
上面的代碼還不能保證在多線程條件下是安全的的:
//單例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
}
return instance;
}
void PrintAddress()
{
std::cout << "地址為:" << this << std::endl;
}
private:
SingleClass(){} //構(gòu)造函數(shù)為私有
SingleClass(const SingleClass&) = delete; // 禁止拷貝構(gòu)造
SingleClass& operator=(const SingleClass&) = delete; // 禁止賦值操作
static SingleClass* instance;
//static std::mutex mtx;
};
//靜態(tài)成員在外部初始化
SingleClass* SingleClass::instance = nullptr;
//std::mutex SingleClass::mtx;
// 全局互斥鎖,用于保護(hù)輸出
std::mutex coutMtx;
// 線程函數(shù)
void threadFunc()
{
SingleClass* instance = SingleClass::GetInstance();
std::lock_guard
instance->PrintAddress();
}
int main()
{
const int threadNumber = 100;
std::vector
//創(chuàng)建多個(gè)線程
for (int i = 0; i < threadNumber; i++)
{
threadVT.emplace_back(threadFunc);
}
for (auto& t : threadVT)
{
t.join();
}
}
大家可以試一下,可能會(huì)有不同的地址。為了保證線程安全,我們還得加鎖:
//單例模式
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
std::lock_guard
instance = new SingleClass();
}
return instance;
}
private:
SingleClass(){} //構(gòu)造函數(shù)為私有
SingleClass(const SingleClass&) = delete; // 禁止拷貝構(gòu)造
SingleClass& operator=(const SingleClass&) = delete; // 禁止賦值操作
static SingleClass* instance;
static std::mutex mtx;
};
//靜態(tài)成員在外部初始化
SingleClass* SingleClass::instance = nullptr;
std::mutex SingleClass::mtx;
C++11后代寫法
在 C++11 中,局部靜態(tài)變量的初始化是線程安全的,因此可以簡(jiǎn)化代碼
class SingleClass
{
public:
static SingleClass& GetInstance()
{
static SingleClass instance;
return instance;
}
private:
SingleClass() {};
SingleClass(const SingleClass&) = delete; // 禁止拷貝構(gòu)造
SingleClass& operator=(const SingleClass&) = delete; // 禁止賦值操作
};
int main()
{
SingleClass& s1 = SingleClass::GetInstance();
SingleClass& s2 = SingleClass::GetInstance();
if (&s1 == &s2)
{
std::cout << "s1和s2同一對(duì)象" << std::endl;
}
else
{
std::cout << "s1和s2不為同一對(duì)象" << std::endl;
}
}
單例模式的兩種模式
餓漢模式
餓漢模式講究的是對(duì)象已經(jīng)創(chuàng)建好,要用的時(shí)候直接拿就行
class SingleClass
{
public:
static SingleClass* GetInstance()
{
return instance;
}
private:
SingleClass() {}
SingleClass(const SingleClass&) = delete; // 禁止拷貝構(gòu)造
SingleClass& operator=(const SingleClass&) = delete; // 禁止賦值操作
static SingleClass* instance;
};
SingleClass* SingleClass::instance = new SingleClass();
懶漢模式
懶漢模式講究的是對(duì)象要用時(shí)才創(chuàng)建:
class SingleClass
{
public:
static SingleClass* GetInstance()
{
if (instance == nullptr)
{
instance = new SingleClass();
return instance;
}
}
private:
SingleClass() {}
SingleClass(const SingleClass&) = delete; // 禁止拷貝構(gòu)造
SingleClass& operator=(const SingleClass&) = delete; // 禁止賦值操作
static SingleClass* instance;
};
SingleClass* SingleClass::instance = nullptr;
這個(gè)和我們最開始寫的代碼差不多,要改進(jìn)的話還要保證線程安全。
我們可以將這種思想放到我們實(shí)際的代碼中,比如我們?nèi)罩鞠到y(tǒng)的開發(fā):
class LoggerManager
{
public:
static LoggerManager &getInstance()
{
// c++11之后,針對(duì)靜態(tài)局部變量,編譯器在編譯的層面實(shí)現(xiàn)了線程安全
// 當(dāng)靜態(tài)局部變量在沒有構(gòu)造完成之前,其他的線程進(jìn)入就會(huì)阻塞
static LoggerManager eton;
return eton;
}
void addLogger(logs::logger::ptr &logger)
{
if (hasLogger(logger->name()))
return;
std::unique_lock
_loggers.insert(std::make_pair(logger->name(), logger));
}
bool hasLogger(const std::string &name)
{
std::unique_lock
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return false;
}
return true;
}
logs::logger::ptr getLogger(const std::string &name)
{
std::unique_lock
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return logger::ptr();
}
return it->second;
}
logs::logger::ptr rootLogger()
{
return _root_logger;
}
private:
LoggerManager()
{
std::unique_ptr
builder->buildLoggerName("root");
_root_logger = builder->build();
_loggers.insert(std::make_pair("root", _root_logger));
}
private:
std::mutex _mutex;
logs::logger::ptr _root_logger; // 默認(rèn)日志器
std::unordered_map
};
柚子快報(bào)邀請(qǐng)碼778899分享:C++設(shè)計(jì)模式 —— 單例模式
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。