專屬所有權(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)存,只能被一個對象持有,所以糜芳,unique_ptr 不支持復(fù)制和賦值,如下:
auto w = std::make_unique<Wight>();
auto w2 = w; // 編譯錯誤
unique_ptr 在默認(rèn)情況下和裸指針的大小是一樣的魄衅。
unique_ptr指針本身的生命周期:從unique_ptr指針創(chuàng)建時開始峭竣,直到離開作用域。離開作用域時徐绑,若其指向?qū)ο笮巴裕瑒t將其所指對象銷毀(默認(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),即多個 shared_ptr 可以共享同一塊內(nèi)存由境,shared_ptr使用引用計數(shù)棚亩,每一個shared_ptr的拷貝都指向相同的內(nèi)存,每使用它一次虏杰,內(nèi)部的引用計數(shù)加1讥蟆,每析構(gòu)一次,內(nèi)部的引用計數(shù)減1纺阔,減為0時瘸彤,自動刪除所指向的堆內(nèi)存。
shared_ptr內(nèi)部的引用計數(shù)是線程安全的笛钝,但是對象的讀取需要加鎖质况。
智能指針是個模板類低零,可以指定類型,傳入指針通過構(gòu)造函數(shù)初始化
shared_ptr p(new int(11));
也可以使用make_shared
函數(shù)初始化拯杠。
auto w = make_shared<Wight>();
不能將指針直接賦值給一個shared_ptr
指針掏婶,一個是類,一個是指針潭陪。
std::shared_ptr<int> p4 = new int(1) // 錯誤寫法
-
get函數(shù)獲取原始指針雄妥,注意不要使用get初始化另一個智能指針或為智能指針賦值
shared_ptr<int> p(new int(42)); int *q = p.get(); shared_ptr<int> p(p.get()) // 錯誤 ,原因同下 shared_ptr<int> p2(q) // 錯誤依溯, 原因同下
-
注意不要用一個原始指針初始化多個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)); // 臨時變量黎炉,內(nèi)存會被釋放掉 int j = *x;
注意避免循環(huán)引用枝秤,shared_ptr的一個最大的陷阱是循環(huán)引用,循環(huán)引用會導(dǎo)致堆內(nèi)存無法正確釋放慷嗜,導(dǎo)致內(nèi)存泄漏淀弹。循環(huán)引用稍后介紹。
weak_ptr的使用
weak_ptr是為了配合shared_ptr而引入的一種智能指針庆械,因為它不具有普通指針的行為薇溃,沒有重載operator*和->,它的最大作用在于協(xié)助shared_ptr工作,像旁觀者那樣觀測資源的使用情況缭乘。
-
weak_ptr可以從一個shared_ptr或者另一個weak_ptr對象構(gòu)造沐序,獲得資源的觀測權(quán)。但weak_ptr沒有共享資源堕绩,它的構(gòu)造不會引起指針引用計數(shù)的增加策幼。使用weak_ptr的成員函數(shù)use_count()可以觀測資源的引用計數(shù),另一個成員函數(shù)expired()的功能等價于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可以使用一個非常重要的成員函數(shù)lock()從被觀測的shared_ptr獲得一個可用的shared_ptr對象绰寞, 從而操作資源到逊。但當(dāng)expired()==true的時候,lock()函數(shù)將返回一個存儲空指針的shared_ptr滤钱。
if (shared_ptr<int> np = wp.lock()) { ...... }
循環(huán)引用
TODO
智能指針與多線程
有兩個指針p1 和 p2觉壶,都指向同一個堆上的對象object,假設(shè)線程A通過p1指針將對象銷毀了件缸,那p2就成了空懸指針铜靶,這時一種典型的C/C++內(nèi)存錯誤。
下面的例子:展現(xiàn)了這種內(nèi)存錯誤。
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"; // 這里會段錯誤争剿,因為p1 所指的對象在t2線程已經(jīng)銷毀了已艰。
}
});
t2.join();
t3.join();
}
所以要想安全的銷毀對象,最好在別人(線程)都看不到的情況下蚕苇,偷偷的做哩掺,這也正是GC的原理,也符合shared_ptr 的 用法涩笤。
利用 shared_ptr 改造上面的錯誤嚼吞。
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();
}
實現(xiàn)
TODO