詳解C++中shared_ptr的應用教程。本站提示廣大學習愛好者:(詳解C++中shared_ptr的應用教程)文章只能為提供參考,不一定能成為您想要的結果。以下是詳解C++中shared_ptr的應用教程正文
shared_ptr是一種智能指針(smart pointer)。shared_ptr的感化有好像指針,但會記載有若干個shared_ptrs配合指向一個對象。
這就是所謂的援用計數(reference counting)。一旦最初一個如許的指針被燒毀,也就是一旦某個對象的援用計數變成0,這個對象會被主動刪除。這在非環形數據構造中避免資本洩漏很有贊助。
auto_ptr因為它的損壞性復制語義,沒法知足尺度容器對元素的請求,因此不克不及放在尺度容器中;假如我們願望當容器析構時能主動把它包容的指針元素所指的對象刪除時,平日采取一些直接的方法來完成,顯得比擬繁瑣。boost庫中供給了一種新型的智能指針shared_ptr,它處理了在多個指針間同享對象一切權的成績,同時也知足容器對元素的請求,因此可以平安地放入容器中。
總結下幾個應用shared_ptr須要留意的成績:
一. 互相援用鏈
class C; class B : public std::enable_shared_from_this<B> { public: ~B(){ cout << "~B" << endl; } void SetPC(std::shared_ptr<C>& pc){ _pc = pc; } private: std::shared_ptr<C> _pc; }; class C : public std::enable_shared_from_this<C> { public: ~C(){ cout << "~C" << endl; } void SetPB(std::shared_ptr<B>& pb){ _pb = pb; } private: std::shared_ptr<B> _pb; }; int main() { std::shared_ptr<C> pc = std::make_shared<C>(); std::shared_ptr<B> pb = std::make_shared<B>(); pc->SetPB(pb); pb->SetPC(pc); return 0; }
下面的代碼中,B和C均不克不及准確析構,准確的做法是,在B和C的釋放函數,如Close中,將其包括的shared_ptr置空。如許能力解開援用鏈。
二. 自援用
還有個比擬成心思的例子:
class C : public std::enable_shared_from_this < C > { public: ~C() { std::cout << "~C" << std::endl; } int32_t Decode(const char* data, size_t) { return 0; } void SetDecoder(std::function<int32_t(const char*, size_t)> decoder) { _decoder = decoder; } private: std::function<int32_t(const char*, size_t)> _decoder; }; int main() { { std::shared_ptr<C> pc = std::make_shared<C>(); auto decoder = std::bind(&C::Decode, pc, std::placeholders::_1, std::placeholders::_2); pc->SetDecoder(decoder); } // C不克不及准確析構 由於存在自援用 return 0; }
下面的C類包括了一個function,該function經由過程std::bind援用了一個std::shared_ptr,所以_decoder其實包括了一個對shared_ptr的援用。招致C自援用了本身,不克不及准確析構。須要在C的Close之類的履行封閉函數中,將_decoder=nullptr,以解開這類自援用。
三. 類中傳遞
上面的例子中有個更加隱藏的成績:
class Session : public std::enable_shared_from_this < Session > { public: ~Session() { std::cout << "~C" << std::endl; } void Start() { // 停止一些異步驟用 // 如 _socket.async_connect(..., boost::bind(&Session::ConnectCompleted, this), boost::asio::placeholders::error, ...) } void ConnectCompleted(const boost::system::err_code& err) { if(err) return; // ... 停止處置 // 如 _socket.async_read(..., boost::bind(&Session::ReadCompleted, this), boost::asio::placeholders::error, ...) } void Session::ReadComplete(const boost::system::error_code& err, size_t bytes_transferred) { if (err || bytes_transferred == 0) { DisConnect(); return; } // 處置數據 持續讀 // ProcessData(); // _socket.async_read(...) } private: std::function<int32_t(const char*, size_t)> _decoder; }; int main() { { std::shared_ptr<Session> pc = std::make_shared<Session>(); pc->Start(); } return 0; }
下面Session,在挪用Start時,挪用了異步函數,並回調本身,假如在回調函數的 boost::bind 中 傳入的是shared_from_this(),那末並沒有成績,shared_ptr將被一向傳遞下去,在收集處置正常時,Session將正常運轉,即便main函數中曾經沒有它的援用,然則它靠boost::bind”活了上去”,boost::bind會保留傳給它的shared_ptr,在挪用函數時傳入。當收集碰到毛病時,函數直接前往。此時不再有新的bind為其”續命”。Session將被析構。
而真實的成績在於,假如在全部bind鏈中,直接傳遞了this指針而不是shared_from_this(),那末現實受騙函數履行完成後,Session即會析構,包含其外部的資本(如 _socket)也會被釋放。那末當boost底層去履行收集IO時,天然會碰到毛病,而且依然會”正常”回調到對應函數,如ReadCompleted,然後在err中告知你:”由當地體系終止收集銜接”(或:”An attempt to abort the evaluation failed. The process is now in an indeterminate state.” )。讓人誤認為是收集成績,很難調試。而現實上此時全部對象都曾經被釋放失落了。
注:因為C++對象模子完成而至,成員函數和通俗函數的重要差別以下:
也就是說,成員函數其實不屬於對象,非靜態數據成員才屬於對象。
是以以下挪用在編譯期是正當的:
((A*)nullptr)->Func();
而假如成員函數A::Func()沒有拜訪A的非靜態成員變量,這段代碼乃至能准確運轉,如:
class Test { public: void Say() { std::cout << "Say Test" << std::endl; } void Set(int data) { _data = data; } private: int _data; }; int main() { // 運轉勝利 ((Test*)nullptr)->Say(); // 運轉會崩失落,測驗考試拜訪空指針所指內存(_data) ((Test*)nullptr)->Set(1); return 0; }
正由於這類特征,有時刻在成員函數中糾結半天,也不會留意到這個對象曾經”不正常了”,被釋放失落了。
四. shared_ptr 應用總結
盡可能不要環援用或自援用,可經由過程weak_ptr來防止環援用:owner持有child的shared_ptr child持有owner的weak_ptr
假如存在環援用或自援用,記得在釋放時解開這個援用鏈
關於經由過程智能指針治理的類,在類中經由過程shared_from_this()而不是this來傳遞自己
在類釋放時,盡可能手動置空其一切的shared_ptr成員,包含function