專屬所有權(quán):unique_ptr
我們大多數(shù)場景下用到的應(yīng)該都是 unique_ptr。
// C++11
unique_ptr<string> p1;
p1.reste(new string("hello world"));
// C++11
unique_ptr<string> p1(new string("hello world"));
// C++14
auto w = make_unique<Wight>();
unique_ptr 代表的是專屬所有權(quán),即由 unique_ptr 管理的內(nèi)存,只能被一個(gè)對象持有,所以,unique_ptr 不支持復(fù)制和賦值,如下:
auto w = std::make_unique<Wight>();
auto w2 = w; // 編譯錯(cuò)誤
unique_ptr 在默認(rèn)情況下和裸指針的大小是一樣的。
unique_ptr指針本身的生命周期:從unique_ptr指針創(chuàng)建時(shí)開始,直到離開作用域。離開作用域時(shí),若其指向?qū)ο?,則將其所指對象銷毀(默認(rèn)使用delete操作符,用戶可自定義刪除器可指定其他操作)。
在unique_ptr生命周期內(nèi),可以改變unique_ptr所指對象:
1、通過reset方法重新指定、
2、通過release方法釋放所有權(quán)、
3、通過移動語義轉(zhuǎn)移所有權(quán)。
u = nullptr; // 釋放u指向的對象,并將u置為空
u.release() // u放棄對指針的控制權(quán),返回指針,并將u置為空
unique_ptr<string> p2(p1.release()) // 將p1置為空,并將資源所有權(quán)轉(zhuǎn)移給p2
u.reset() // 釋放u指向的對象
u.reset(q) // 令u釋放原管理的對象,重新管理q資源
p2.reset(p1.release())
unique_ptr<string> p2 = std::move(p1);
共享所有權(quán):shared_ptr
代表的是共享所有權(quán),即多個(gè) shared_ptr 可以共享同一塊內(nèi)存,shared_ptr使用引用計(jì)數(shù),每一個(gè)shared_ptr的拷貝都指向相同的內(nèi)存,每使用它一次,內(nèi)部的引用計(jì)數(shù)加1,每析構(gòu)一次,內(nèi)部的引用計(jì)數(shù)減1,減為0時(shí),自動刪除所指向的堆內(nèi)存。
shared_ptr內(nèi)部的引用計(jì)數(shù)是線程安全的,但是對象的讀取需要加鎖。
智能指針是個(gè)模板類,可以指定類型,傳入指針通過構(gòu)造函數(shù)初始化
shared_ptr p(new int(11));
也可以使用make_shared函數(shù)初始化。
auto w = make_shared<Wight>();
不能將指針直接賦值給一個(gè)shared_ptr指針,一個(gè)是類,一個(gè)是指針。
std::shared_ptr<int> p4 = new int(1) // 錯(cuò)誤寫法
-
get函數(shù)獲取原始指針,注意不要使用get初始化另一個(gè)智能指針或?yàn)橹悄苤羔樫x值
shared_ptr<int> p(new int(42)); int *q = p.get(); shared_ptr<int> p(p.get()) // 錯(cuò)誤 ,原因同下 shared_ptr<int> p2(q) // 錯(cuò)誤, 原因同下 -
注意不要用一個(gè)原始指針初始化多個(gè)shared_ptr,否則會造成二次釋放同一內(nèi)存。
int *p = new int(10); shared_ptr<int> shp(p); shared_ptr<int> shp2(p); // 兩次釋放同一內(nèi)存 -
注意不要混合使用普通指針和智能指針
int *x (new int(42)); process(shared_ptr<int>(x)); // 臨時(shí)變量,內(nèi)存會被釋放掉 int j = *x; 注意避免循環(huán)引用,shared_ptr的一個(gè)最大的陷阱是循環(huán)引用,循環(huán)引用會導(dǎo)致堆內(nèi)存無法正確釋放,導(dǎo)致內(nèi)存泄漏。循環(huán)引用稍后介紹。
weak_ptr的使用
weak_ptr是為了配合shared_ptr而引入的一種智能指針,因?yàn)樗痪哂衅胀ㄖ羔樀男袨?,沒有重載operator*和->,它的最大作用在于協(xié)助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。
-
weak_ptr可以從一個(gè)shared_ptr或者另一個(gè)weak_ptr對象構(gòu)造,獲得資源的觀測權(quán)。但weak_ptr沒有共享資源,它的構(gòu)造不會引起指針引用計(jì)數(shù)的增加。使用weak_ptr的成員函數(shù)use_count()可以觀測資源的引用計(jì)數(shù),另一個(gè)成員函數(shù)expired()的功能等價(jià)于use_count()==0,但更快,表示被觀測的資源(也就是shared_ptr的管理的資源)已經(jīng)不復(fù)存在。
shared_ptr<int> sp = make_shared<int>(10); wak_ptr<int> w(sp); w = sp; -
weak_ptr可以使用一個(gè)非常重要的成員函數(shù)lock()從被觀測的shared_ptr獲得一個(gè)可用的shared_ptr對象, 從而操作資源。但當(dāng)expired()==true的時(shí)候,lock()函數(shù)將返回一個(gè)存儲空指針的shared_ptr。
if (shared_ptr<int> np = wp.lock()) { ...... }
循環(huán)引用
TODO
智能指針與多線程
有兩個(gè)指針p1 和 p2,都指向同一個(gè)堆上的對象object,假設(shè)線程A通過p1指針將對象銷毀了,那p2就成了空懸指針,這時(shí)一種典型的C/C++內(nèi)存錯(cuò)誤。

下面的例子:展現(xiàn)了這種內(nèi)存錯(cuò)誤。
struct client
{
public:
client() : m_name(new string("Bob")) { }
~client() { delete m_name; cout << "~client()" << endl; }
string *m_name;
};
class client_manager
{
public:
void and_client(int client_id)
{
lock_guard<mutex> lk(m_lock);
m_client_map.insert({client_id, new client()});
}
void remove_client(int client_id)
{
lock_guard<mutex> lk(m_lock);
auto it = m_client_map.find(client_id);
if (it != m_client_map.end())
{
m_client_map.erase(it);
delete it->second; // 銷毀
}
}
bool find_client(int client_id, client *&ptr)
{
bool result = false;
lock_guard<mutex> lk(m_lock);
auto it = m_client_map.find(client_id);
if (it != m_client_map.end())
{
result = true;
ptr = it->second;
}
return result;
}
private:
mutex m_lock;
map<int, client *> m_client_map;
};
int main()
{
client_manager manager;
int id = 1;
thread t1([&manager, &id]() {
manager.and_client(id++);
});
t1.join();
thread t2([&manager]() {
this_thread::sleep_for(chrono::seconds(2)); // 模擬OS調(diào)度
manager.remove_client(1);
});
thread t3([&manager]() {
client *p1 = NULL;
if (manager.find_client(1, p1))
{
this_thread::sleep_for(chrono::seconds(4)); // 模擬OS調(diào)度
(*p1->m_name) = "world"; // 這里會段錯(cuò)誤,因?yàn)閜1 所指的對象在t2線程已經(jīng)銷毀了。
}
});
t2.join();
t3.join();
}
所以要想安全的銷毀對象,最好在別人(線程)都看不到的情況下,偷偷的做,這也正是GC的原理,也符合shared_ptr 的 用法。
利用 shared_ptr 改造上面的錯(cuò)誤。
class client_manager
{
public:
void and_client(int client_id)
{
lock_guard<mutex> lk(m_lock);
shared_ptr<client> p_client(new client());
m_client_map.insert({client_id, p_client});
}
void remove_client(int client_id)
{
lock_guard<mutex> lk(m_lock);
auto it = m_client_map.find(client_id);
if (it != m_client_map.end())
{
m_client_map.erase(it);
}
}
bool find_client(int client_id, shared_ptr<client> &ptr)
{
bool result = false;
lock_guard<mutex> lk(m_lock);
auto it = m_client_map.find(client_id);
if (it != m_client_map.end())
{
result = true;
ptr = it->second;
}
return result;
}
private:
mutex m_lock;
map<int, shared_ptr<client>> m_client_map;
};
int main()
{
client_manager manager;
int id = 1;
thread t1([&manager, &id]() {
manager.and_client(id++);
});
t1.join();
thread t2([&manager]() {
this_thread::sleep_for(chrono::seconds(2)); // 模擬OS調(diào)度
manager.remove_client(1);
});
thread t3([&manager]() {
shared_ptr<client> p1;
if (manager.find_client(1, p1))
{
cout << p1.use_count() << endl;
this_thread::sleep_for(chrono::seconds(4)); // 模擬OS調(diào)度
cout << p1.use_count() << endl;
(*p1->m_name) = "world";
}
else
{
cout << "not found" << endl;
}
});
t2.join();
t3.join();
}
實(shí)現(xiàn)
TODO