單例模式
參考博客
【C++】單例模式(餓漢模式、懶漢模式)
C++單例模式總結(jié)與剖析
餓漢單例模式 C++實現(xiàn)
C++單例模式(餓漢式)
設(shè)計模式(Design Pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類的、代碼設(shè)計經(jīng)驗的總結(jié)
,一共有23種經(jīng)典設(shè)計模式
使用設(shè)計模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性
設(shè)計模式使代碼編寫真正工程化,設(shè)計模式是軟件工程的基石脈絡(luò),如同大廈的結(jié)構(gòu)一樣
單例模式是設(shè)計模式中最常用的一種模式,一個類只能創(chuàng)建一個對象,即單例模式,該模式可以保證系統(tǒng)中該類只有一個實例,并提供一個訪問它的全局訪問點,該實例被所有程序模塊共享
基礎(chǔ)要點:
- 全局只有一個實例:static 特性,同時禁止用戶自己聲明并定義實例(把構(gòu)造函數(shù)設(shè)為 private)
- 線程安全
- 禁止賦值和拷貝
- 用戶通過接口獲取實例:使用 static 類成員函數(shù)
單例的實現(xiàn)主要有餓漢式和懶漢式兩種,分別進行介紹
餓漢式
不管你將來用不用,程序啟動時就創(chuàng)建一個唯一的實例對象
優(yōu)點:簡單
缺點:可能會導(dǎo)致進程啟動慢,且如果有多個單例類對象實例啟動順序不確定
示例代碼:
// Hunger_Singleton_pattern
// Created by lei on 2022/05/13
#include <iostream>
#include <memory>
using namespace std;
class Example
{
public:
typedef shared_ptr<Example> Ptr;
static Ptr GetSingleton()
{
cout << "Get Singleton" << endl;
return single;
}
void test()
{
cout << "Instance location:" << this << endl;
}
~Example() { cout << "Deconstructor called" << endl; };
private:
static Ptr single;
Example() { cout << "Constructor called" << endl; };
Example &operator=(const Example &examp) = delete;
Example(const Example &examp) = delete;
};
Example::Ptr Example::single = shared_ptr<Example>(new Example);
int main()
{
Example::Ptr a = Example::GetSingleton();
Example::Ptr b = Example::GetSingleton();
a->test();
b->test();
cout << "main end" << endl;
return 0;
}
打印輸出:
Constructor called
Get Singleton
Get Singleton
Instance location:0x55d43c9dce70
Instance location:0x55d43c9dce70
main end
Deconstructor called
可以看到拷貝構(gòu)造函數(shù)只調(diào)用了一次,并且兩個對象內(nèi)存地址相同,說明該類只能實例化一個對象
餓漢單例模式的靜態(tài)變量的初始化由C++完成,規(guī)避了線程安全問題,所以餓漢單例模式是線程安全的
在大多數(shù)情況下使用餓漢單例模式是沒有問題的
有缺陷的懶漢模式
懶漢式(Lazy-Initialization)的方法是直到使用時才實例化對象,也就說直到調(diào)用get_instance() 方法的時候才 new 一個單例的對象, 如果不被調(diào)用就不會占用內(nèi)存
// Defect_Lazy_Singleton_pattern
// Created by lei on 2022/05/13
#include <iostream>
#include <thread>
using namespace std;
class Singleton
{
private:
Singleton()
{
cout << "constructor called!" << endl;
}
Singleton(const Singleton &) = delete;
Singleton& operator=(const Singleton &) = delete;
static Singleton *m_instance_ptr;
public:
~Singleton()
{
cout << "destructor called!" << endl;
}
static Singleton *get_instance()
{
if (m_instance_ptr == nullptr)
{
m_instance_ptr = new Singleton;
}
return m_instance_ptr;
}
void use() const { cout << "in use" << endl; }
};
Singleton *Singleton::m_instance_ptr = nullptr; //靜態(tài)成員變量類內(nèi)聲明類外初始化
int main()
{
Singleton *instance = Singleton::get_instance();
Singleton *instance_2 = Singleton::get_instance();
// thread t1(Singleton::get_instance);
// thread t2(Singleton::get_instance);
// thread t3(Singleton::get_instance);
// thread t4(Singleton::get_instance);
// t1.join();
// t2.join();
// t3.join();
// t4.join();
return 0;
}
打印輸出:
constructor called!
取了兩次類的實例,卻只有一次類的構(gòu)造函數(shù)被調(diào)用,表明只生成了唯一實例,這是個最基礎(chǔ)版本的單例實現(xiàn),存在以下問題
1、當(dāng)多線程獲取單例時有可能引發(fā)競態(tài)條件:第一個線程在if中判斷?m_instance_ptr
是空的,于是開始實例化單例;同時第2個線程也嘗試獲取單例,這個時候判斷m_instance_ptr
還是空的,于是也開始實例化單例;這樣就會實例化出兩個對象
2、類中只負責(zé)new出對象,卻沒有負責(zé)delete對象,因此只有構(gòu)造函數(shù)被調(diào)用,析構(gòu)函數(shù)卻沒有被調(diào)用,因此會導(dǎo)致內(nèi)存泄漏
改進的懶漢模式
對應(yīng)上面兩個問題,有以下解決方法:
1、用mutex加鎖
2、使用智能指針
// Improve_Lazy_Singleton_pattern
// Created by lei on 2022/05/13
#include <iostream>
#include <memory> // shared_ptr
#include <mutex> // mutex
#include <thread>
using namespace std;
class Singleton
{
public:
typedef shared_ptr<Singleton> Ptr;
~Singleton()
{
cout << "destructor called!" << endl;
}
Singleton(const Singleton &) = delete;
Singleton& operator=(const Singleton &) = delete;
static Ptr get_instance()
{
// "double checked lock"
if (m_instance_ptr == nullptr)
{
lock_guard<mutex> lk(m_mutex);
if (m_instance_ptr == nullptr)
{
m_instance_ptr = shared_ptr<Singleton>(new Singleton);
}
}
return m_instance_ptr;
}
private:
Singleton()
{
cout << "constructor called!" << endl;
}
static Ptr m_instance_ptr;
static mutex m_mutex;
};
// initialization static variables out of class
Singleton::Ptr Singleton::m_instance_ptr = nullptr;
mutex Singleton::m_mutex;
int main()
{
Singleton::Ptr instance = Singleton::get_instance();
Singleton::Ptr instance2 = Singleton::get_instance();
// thread t1(Singleton::get_instance);
// thread t2(Singleton::get_instance);
// thread t3(Singleton::get_instance);
// thread t4(Singleton::get_instance);
// t1.join();
// t2.join();
// t3.join();
// t4.join();
return 0;
}
打印輸出:
constructor called!
destructor called!
只構(gòu)造了一次實例,并且發(fā)生了析構(gòu)
缺陷是雙檢鎖依然會失效,具體原因可以看下面的文章
https://www.drdobbs.com/cpp/c-and-the-perils-of-double-checked-locki/184405726
推薦的懶漢模式
// Recommand_Lazy_Singleton_pattern
// Created by lei on 2022/05/13
#include <iostream>
#include <thread>
using namespace std;
class Singleton
{
public:
~Singleton()
{
cout << "destructor called!" << endl;
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;
static Singleton &get_instance()
{
static Singleton instance;
return instance;
}
private:
Singleton()
{
cout << "constructor called!" << endl;
}
};
int main()
{
Singleton &instance_1 = Singleton::get_instance();
Singleton &instance_2 = Singleton::get_instance();
// thread t1(Singleton::get_instance);
// thread t2(Singleton::get_instance);
// thread t3(Singleton::get_instance);
// thread t4(Singleton::get_instance);
// t1.join();
// t2.join();
// t3.join();
// t4.join();
return 0;
}
打印輸出:
constructor called!
destructor called!
這種方法又叫做 Meyers’ Singleton Meyer’s的單例, 是著名的寫出《Effective C++》系列書籍的作者 Meyers 提出的。所用到的特性是在C++11標(biāo)準(zhǔn)中的Magic Static特性:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization
如果當(dāng)變量在初始化的時候,并發(fā)同時進入聲明語句,并發(fā)線程將會阻塞等待初始化結(jié)束
這是最推薦的一種單例實現(xiàn)方式:
- 通過局部靜態(tài)變量的特性保證了線程安全
- 不需要使用共享指針,代碼簡潔
- 注意在使用的時候需要聲明單例的引用?
Single&
?才能獲取對象
once_flag與call_once
參考博客
C++11于once flag,call_once:分析的實現(xiàn)
C++11實現(xiàn)線程安全的單例模式(使用std::call_once)
在多線程編程中,有一個常見的情景是某個任務(wù)僅僅須要運行一次
在C++11中提供了非常方便的輔助類once_flag與call_once
once_flag和call_once的聲明:
struct once_flag
{
constexpr once_flag() noexcept;
once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;
};
template<class Callable, class ...Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args);
} // std
簡單示例:
// once_flag and call_once simple example
// Created by lei on 2022/05/13
#include <iostream>
using namespace std;
once_flag flag;
void do_once()
{
call_once(flag, [&]()
{ cout << "Called once" << endl; });
}
int main()
{
std::thread t1(do_once);
std::thread t2(do_once);
std::thread t3(do_once);
std::thread t4(do_once);
t1.join();
t2.join();
t3.join();
t4.join();
}
打印輸出:
Called once
可以看到4個線程只執(zhí)行了一次do_once( )函數(shù)
call_once實現(xiàn)單例模式
// Call_once_Singleton_pattern
// Created by lei on 2022/05/13
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
once_flag cons_flag;
class A
{
public:
typedef shared_ptr<A> Ptr;
void m_print() { cout << "m_a++ = " << ++m_a << endl; }
static Ptr getInstance(int a)
{
cout << "Get instance" << endl;
if (m_instance_ptr == nullptr)
{
lock_guard<mutex> m_lock(m_mutex);
if (m_instance_ptr == nullptr)
{
call_once(cons_flag, [&]()
{ m_instance_ptr.reset(new A(a)); });
}
}
return m_instance_ptr;
}
~A()
{
cout << "Deconstructor called" << endl;
}
private:
static mutex m_mutex;
int m_a;
static Ptr m_instance_ptr;
A(int a_) : m_a(a_)
{
cout << "Constructor called" << endl
<< "m_a = " << m_a << endl;
}
A &operator=(const A &A_) = delete;
A(const A &A_) = delete;
};
A::Ptr A::m_instance_ptr = nullptr;
mutex A::m_mutex;
void test(int aa)
{
cout << "Go in test..." << endl;
A::Ptr tp = A::getInstance(aa);
cout << "tp location:" << tp << endl;
tp->m_print();
cout << endl;
}
int main()
{
thread t1(test, 1);
thread t2(test, 2);
thread t3(test, 3);
thread t4(test, 4);
t1.join();
t2.join();
t3.join();
t4.join();
cout << "main end..." << endl;
return 0;
}
打印輸出:
Go in test...
Get instance
Constructor called
m_a = 4
tp location:0x7fd964000f30
m_a++ = 5
Go in test...
Get instance
tp location:0x7fd964000f30
m_a++ = 6
Go in test...
Get instance
tp location:0x7fd964000f30
m_a++ = 7
Go in test...
Get instance
tp location:0x7fd964000f30
m_a++ = 8
main end...
Deconstructor called
看到構(gòu)造函數(shù)只調(diào)用了一次,并且類A實例化對象的地址始終相同文章來源:http://www.zghlxwxcb.cn/news/detail-571885.html
上面的兩個示例程序中都用到了lambda表達式,call_once通常結(jié)合lambda一起使用文章來源地址http://www.zghlxwxcb.cn/news/detail-571885.html
到了這里,關(guān)于C++并發(fā)編程(6):單例模式、once_flag與call_once、call_once實現(xiàn)單例的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!