<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
unique_ptr因為其侷限性(獨享所有權),一般很少用於多執行緒操作。在多執行緒操作的時候,既可以共用資源,又可以自動釋放資源,這就引入了shared_ptr。
shared_ptr為了支援跨執行緒存取,其內部有一個參照計數(執行緒安全),用來記錄當前使用該資源的shared_ptr個數,在結束使用的時候,參照計數為-1,當參照計數為0時,會自動釋放其關聯的資源。
特點 相對於unique_ptr的獨享所有權,shared_ptr可以共用所有權。其內部有一個參照計數,用來記錄共用該資源的shared_ptr個數,當共用數為0的時候,會自動釋放其關聯的資源。
對比unique_ptr,shared_ptr不支援陣列,所以,如果用shared_ptr指向一個陣列的話,需要自己手動實現deleter,如下所示:
std::shared_ptr<int> p(new int[8], [](int *ptr){delete []ptr;});
template<class T> class shared_ptr { public: using element_type = remove_extent_t<T>; using weak_type = weak_ptr<T>; // 建構函式 constexpr shared_ptr() noexcept; constexpr shared_ptr(nullptr_t) noexcept : shared_ptr() { } template<class Y> explicit shared_ptr(Y* p); template<class Y, class D> shared_ptr(Y* p, D d); template<class Y, class D, class A> shared_ptr(Y* p, D d, A a); template<class D> shared_ptr(nullptr_t p, D d); template<class D, class A> shared_ptr(nullptr_t p, D d, A a); template<class Y> shared_ptr(const shared_ptr<Y>& r, element_type* p) noexcept; template<class Y> shared_ptr(shared_ptr<Y>&& r, element_type* p) noexcept; shared_ptr(const shared_ptr& r) noexcept; template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept; shared_ptr(shared_ptr&& r) noexcept; template<class Y> shared_ptr(shared_ptr<Y>&& r) noexcept; template<class Y> explicit shared_ptr(const weak_ptr<Y>& r); template<class Y, class D> shared_ptr(unique_ptr<Y, D>&& r); // 解構函式 ~shared_ptr(); // 賦值 shared_ptr& operator=(const shared_ptr& r) noexcept; template<class Y> shared_ptr& operator=(const shared_ptr<Y>& r) noexcept; shared_ptr& operator=(shared_ptr&& r) noexcept; template<class Y> shared_ptr& operator=(shared_ptr<Y>&& r) noexcept; template<class Y, class D> shared_ptr& operator=(unique_ptr<Y, D>&& r); // 修改函數 void swap(shared_ptr& r) noexcept; void reset() noexcept; template<class Y> void reset(Y* p); template<class Y, class D> void reset(Y* p, D d); template<class Y, class D, class A> void reset(Y* p, D d, A a); // 探察函數 element_type* get() const noexcept; T& operator*() const noexcept; T* operator->() const noexcept; element_type& operator[](ptrdiff_t i) const; long use_count() const noexcept; explicit operator bool() const noexcept; template<class U> bool owner_before(const shared_ptr<U>& b) const noexcept; template<class U> bool owner_before(const weak_ptr<U>& b) const noexcept; };
shared_ptr多個指標指向相同的物件。shared_ptr使用參照計數,每一個shared_ptr的拷貝都指向相同的記憶體。每使用他一次,內部的參照計數加1,每解構一次,內部的參照計數減1,減為0時,自動刪除所指向的堆記憶體。shared_ptr內部的參照計數是執行緒安全的,但是物件的讀取需要加鎖。
std::shared_ptr<int> p4 = new int(1);
的寫法是錯誤的,是不能隱式轉換。所有智慧指標類都有一個explicit建構函式,該建構函式將指標作為引數。因此不需要自動將指標轉換為智慧指標物件:
std::shared_ptr<int> pi; int* p_reg = new int; //pi = p_reg; // not allowed(implicit conversion) pi = std::shared_ptr<int>(p_reg); // allowed(explicit conversion) //std::shared_ptr<int> pshared = p_reg; // not allowed(implicit conversion) //std::shared_ptr<int> pshared(g_reg); // allowed(explicit conversion)
下面我們看一個簡單的例子:
#include <iostream> #include <memory> using namespace std; int main() { std::shared_ptr<int> sp = std::make_shared<int>(10); cout << sp.use_count() << endl;//1 std::shared_ptr<int> sp1(sp);//再次被參照則計數+1 cout << sp1.use_count() << endl;//2 }
從上面可以看到,多次被參照則會增加計數,我們可以通過使用use_count方法列印具體的計數。
#include <iostream> #include <memory> struct C {int* data;}; int main () { auto deleter = [](int* ptr){ std::cout << "custom deleter calledn"; delete ptr; };//Labmbda表示式 //預設構造,沒有獲取任何指標的所有權,參照計數為0 std::shared_ptr<int> sp1; std::shared_ptr<int> sp2 (nullptr);//同1 //擁有指向int的指標所有權,參照計數為1 std::shared_ptr<int> sp3 (new int); //同3,但是擁有自己的解構方法,如果指標所指向物件為複雜結構C //結構C裡有指標,預設解構函式不會將結構C裡的指標data所指向的記憶體釋放, //這時需要自己使用自己的解構函式(刪除器) std::shared_ptr<int> sp4 (new int, deleter); //同4,但擁有自己的分配器(建構函式), //如成員中有指標,可以為指標分配記憶體,原理跟淺拷貝和深拷貝類似 std::shared_ptr<int> sp5 (new int, [](int* p){delete p;}, std::allocator<int>()); //如果p5參照計數不為0,則參照計數加1,否則同樣為0, p6為0 std::shared_ptr<int> sp6 (sp5); //p6的所有權全部移交給p7,p6參照計數變為為0 std::shared_ptr<int> sp7 (std::move(sp6)); //p8獲取所有權,參照計數設定為1 std::shared_ptr<int> sp8 (std::unique_ptr<int>(new int)); std::shared_ptr<C> obj (new C); //同6一樣,只不過擁有自己的刪除器與4一樣 std::shared_ptr<int> sp9 (obj, obj->data); std::cout << "use_count:n"; std::cout << "p1: " << sp1.use_count() << 'n'; //0 std::cout << "p2: " << sp2.use_count() << 'n'; //0 std::cout << "p3: " << sp3.use_count() << 'n'; //1 std::cout << "p4: " << sp4.use_count() << 'n'; //1 std::cout << "p5: " << sp5.use_count() << 'n'; //2 std::cout << "p6: " << sp6.use_count() << 'n'; //0 std::cout << "p7: " << sp7.use_count() << 'n'; //2 std::cout << "p8: " << sp8.use_count() << 'n'; //1 std::cout << "p9: " << sp9.use_count() << 'n'; //2 return 0; }
給shared_ptr賦值有三種方式,如下
#include <iostream> #include <memory> int main () { std::shared_ptr<int> foo; std::shared_ptr<int> bar (new int(10)); //右邊是左值,拷貝賦值,參照計數加1 foo = bar; //右邊是右值,所以是移動賦值 bar = std::make_shared<int> (20); //unique_ptr 不共用它的指標。它無法複製到其他 unique_ptr, //無法通過值傳遞到函數,也無法用於需要副本的任何標準模板庫 (STL) 演演算法。只能移動unique_ptr std::unique_ptr<int> unique (new int(30)); // move from unique_ptr,參照計數轉移 foo = std::move(unique); std::cout << "*foo: " << *foo << 'n'; std::cout << "*bar: " << *bar << 'n'; return 0; }
看下面make_shared的用法:
#include <iostream> #include <memory> int main () { std::shared_ptr<int> foo = std::make_shared<int> (10); // same as: std::shared_ptr<int> foo2 (new int(10)); //建立記憶體,並返回共用指標,只建立一次記憶體 auto bar = std::make_shared<int> (20); auto baz = std::make_shared<std::pair<int,int>> (30,40); std::cout << "*foo: " << *foo << 'n'; std::cout << "*bar: " << *bar << 'n'; std::cout << "*baz: " << baz->first << ' ' << baz->second << 'n'; return 0; }
效率提升 std::make_shared(比起直接使用new)的一個特性是能提升效率。使用std::make_shared允許編譯器產生更小,更快的程式碼,產生的程式碼使用更簡潔的資料結構。考慮下面直接使用new的程式碼:
std::shared_ptr<Test> sp(new Test);
很明顯這段程式碼需要分配記憶體,但是它實際上要分配兩次。每個std::shared_ptr都指向一個控制塊,控制塊包含被指向物件的參照計數以及其他東西。這個控制塊的記憶體是在std::shared_ptr的建構函式中分配的。因此直接使用new,需要一塊記憶體分配給Widget,還要一塊記憶體分配給控制塊。如果使用std::make_shared來替換:
auto sp = std::make_shared<Test>();
一次分配就足夠了。這是因為std::make_shared申請一個單獨的記憶體塊來同時存放Widget物件和控制塊。這個優化減少了程式的靜態大小,因為程式碼只包含一次記憶體分配的呼叫,並且這會加快程式碼的執行速度,因為記憶體只分配了一次。另外,使用std::make_shared消除了一些控制塊需要記錄的資訊,這樣潛在地減少了程式的總記憶體佔用。
對std::make_shared的效率分析可以同樣地應用在std::allocate_shared上,所以std::make_shared的效能優點也可以擴充套件到這個函數上。
異常安全
另外一個std::make_shared的好處是異常安全,我們看下面一句簡單的程式碼:
callTest(std::shared_ptr<Test>(new Test), secondFun());
簡單說,上面這個程式碼可能會發生記憶體漏失,我們先來看下上面這個呼叫中幾個語句的執行順序,可能是順序如下:
new Test() secondFun() std::shared_ptr<Test>()
如果真是按照上面這樣的程式碼順序執行,那麼在執行期,如果secondFun()中產生了一個異常,程式就會直接返回了,則第一步new Test分配的記憶體就洩露了,因為它永遠不會被存放到在第三步才開始管理它的std::shared_ptr中。但是如果使用std::make_shared則可以避免這樣的問題。呼叫程式碼將看起來像這樣:
callTest(std::make_shared<Test>(), secondFun());
在執行期,不管std::make_shared或secondFun哪一個先被呼叫。如果std::make_shared先被呼叫,則在secondFun呼叫前,指向動態分配出來的Test的原始指標能安全地被存放到std::shared_ptr中。如果secondFun之後產生一個異常,std::shared_ptr的解構函式將發現它持有的Test需要被銷燬。並且如果secondFun先被呼叫併產生一個異常,std::make_shared就不會被呼叫,因此這裡就不需要考慮動態分配的Test了。
我們上面一直說shared_ptr中的計數是執行緒安全的,其實shared_ptr中的計數是使用了我們前面文章介紹的std::atomic特性,參照計數加一減一操作是原子性的,所以執行緒安全的。參照計數器的使用等價於用 std::memory_order_relaxed 的 std::atomic::fetch_add 自增(自減要求更強的順序,以安全銷燬控制塊)。
#include <iostream> #include <memory> #include <thread> #include <chrono> #include <mutex> struct Test { Test() { std::cout << " Test::Test()n"; } ~Test() { std::cout << " Test::~Test()n"; } }; //執行緒函數 void thr(std::shared_ptr<Test> p) { //執行緒暫停1s std::this_thread::sleep_for(std::chrono::seconds(1)); //賦值操作, shared_ptr參照計數use_cont加1(c++11中是原子操作) std::shared_ptr<Test> lp = p; { //static變數(單例模式),多執行緒同步用 static std::mutex io_mutex; //std::lock_guard加鎖 std::lock_guard<std::mutex> lk(io_mutex); std::cout << "local pointer in a thread:n" << " lp.get() = " << lp.get() << ", lp.use_count() = " << lp.use_count() << 'n'; } } int main() { //使用make_shared一次分配好需要記憶體 std::shared_ptr<Test> p = std::make_shared<Test>(); //std::shared_ptr<Test> p(new Test); std::cout << "Created a shared Testn" << " p.get() = " << p.get() << ", p.use_count() = " << p.use_count() << 'n'; //建立三個執行緒,t1,t2,t3 //形參作為拷貝, 參照計數也會加1 std::thread t1(thr, p), t2(thr, p), t3(thr, p); std::cout << "Shared ownership between 3 threads and releasedn" << "ownership from main:n" << " p.get() = " << p.get() << ", p.use_count() = " << p.use_count() << 'n'; //等待結束 t1.join(); t2.join(); t3.join(); std::cout << "All threads completed, the last one deletedn"; return 0; }
輸出:
Test::Test()
Created a shared Test
p.get() = 0xa7cec0, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:
p.get() = 0xa7cec0, p.use_count() = 4
local pointer in a thread:
lp.get() = 0xa7cec0, lp.use_count() = 5
local pointer in a thread:
lp.get() = 0xa7cec0, lp.use_count() = 4
local pointer in a thread:
lp.get() = 0xa7cec0, lp.use_count() = 3
All threads completed, the last one deleted
Test::~Test()
在某些場合下,會遇到一種情況,如何安全的獲取物件的this指標,一般來說我們不建議直接返回this指標,可以想象下有這麼一種情況,返回的this指標儲存在外部一個區域性或全域性變數,當物件已經被解構了,但是外部變數並不知道指標指向的物件已經被解構了,如果此時外部繼續使用了這個指標就會發生程式奔潰。既要像指標操作物件一樣,又能安全的解構物件,很自然就想到,智慧指標就很合適!我們來看下面這段程式:
#include <iostream> #include <memory> class Test{ public: Test(){ std::cout << "Test::Test()" << std::endl; } ~Test(){ std::cout << "Test::~Test()" << std::endl; } std::shared_ptr<Test> GetThis(){ return std::shared_ptr<Test>(this); } }; int main() { std::shared_ptr<Test> p(new Test()); std::shared_ptr<Test> p_this = p->GetThis(); std::cout << p.use_count() << std::endl; std::cout << p_this.use_count() << std::endl; return 0; }
編譯執行後程式輸出如下:
free(): double free detected in tcache 2
Test::Test()
1
1
Test::~Test()
Test::~Test()
從上面的輸出可以看到,建構函式呼叫了一次,解構函式卻呼叫了兩次,很明顯這是不正確的。而std::enable_shared_from_this
正是為了解決這個問題而存在。
std::enable_shared_from_this 能讓一個物件(假設其名為 t ,且已被一個 std::shared_ptr 物件 pt 管理)安全地生成其他額外的 std::shared_ptr 範例(假設名為 pt1, pt2, ... ) ,它們與 pt 共用物件 t 的所有權(這個是關鍵,直接使用this無法達到該效果)。
std::enable_shared_from_this是模板類,內部有個_Tp
型別weak_ptr指標,std::enable_shared_from_this的建構函式都是protected,因此不能直接建立std::enable_from_shared_from_this類的範例變數,只能作為基礎類別使用,通過呼叫shared_from_this成員函數,將會返回一個新的 std::shared_ptr<T>
物件,它與 pt 共用 t 的所有權。因此使用方法如下程式碼所示:
#include <iostream> #include <memory> // 這裡必須要 public繼承,除非用struct class Test : public std::enable_shared_from_this<Test> { public: Test(){ std::cout << "Test::Test()" << std::endl; } ~Test(){ std::cout << "Test::~Test()" << std::endl; } std::shared_ptr<Test> GetThis(){ std::cout << "shared_from_this()" << std::endl; return shared_from_this(); } }; int main() { std::shared_ptr<Test> p(new Test()); std::shared_ptr<Test> p_this = p->GetThis(); std::cout << p.use_count() << std::endl; std::cout << p_this.use_count() << std::endl; return 0; }
在類內部通過 enable_shared_from_this
定義的 shared_from_this()
函數構造一個 shared_ptr<Test>
物件, 能和其他 shared_ptr 共用 Test 物件。一般我們使用在非同步執行緒中,在非同步呼叫中,存在一個保活機制,非同步函數執行的時間點我們是無法確定的,然而非同步函數可能會使用到非同步呼叫之前就存在的變數。為了保證該變數在非同步函數執期間一直有效,我們可以傳遞一個指向自身的share_ptr給非同步函數,這樣在非同步函數執行期間share_ptr所管理的物件就不會解構,所使用的變數也會一直有效了(保活)。
不要把一個原生指標給多個shared_ptr管理;不要主動刪除 shared_ptr
所管理的裸指標;
BigObj *p = new BigObj(); std::shared_ptr<BigObj> sp(p); std::shared_ptr<BigObj> sp1(p); delete p;
不要把this指標給shared_ptr,像上面一樣使用enable_shared_from_this;
不要不加思考地把指標替換為shared_ptr來防止記憶體漏失,shared_ptr並不是萬能的,而且使用它們的話也是需要一定的開銷的;
共用擁有權的物件一般比限定作用域的物件生存更久,從而將導致更高的平均資源使用時間;
在多執行緒環境中使用共用指標的代價非常大,這是因為你需要避免關於參照計數的資料競爭;
如果你使用智慧指標管理的資源不是new分配的記憶體,記住傳遞給它一個刪除器。
智慧指標是模板類而不是指標。建立一個智慧指標時,必須指標可以指向的型別,<int>
,<string>
……等。 智慧指標實質就是過載了->
和*
操作符的類,由類來實現對記憶體的管理,確保即使有異常產生,也可以通過智慧指標類的解構函式完成記憶體的釋放。具體來說它利用了參照計數技術和 C++ 的 RAII(資源獲取就是初始化)特性。
到此這篇關於C++智慧指標之shared_ptr的具體使用的文章就介紹到這了,更多相關C++ shared_ptr內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45