欧美在线专区-欧美在线伊人-欧美在线一区二区三区欧美-欧美在线一区二区三区-pornodoxxx中国妞-pornodoldoo欧美另类

position>home>Basketball

淺析C++智能指針和enable

[導讀]今天跟大家聊一下 BAT 面試 C++ 開發(fā)工程師必問的淺析一個考點:智能指針。

大家好,智針和我是淺析小牛,今天跟聊一下 BAT 面試 C++ 開發(fā)工程師必問的智針和一個考點:智能指針。

淺析C++智能指針和enable

小艾:你昨晚面 C++ 去了?

小牛:對啊,淺析不是智針和這個廠主要技術棧都是 C++ 嘛,我就面去了。淺析

小艾:問了點啥啊?

小牛:BAT 這 C++ 問的智針和都差不多,又問智能指針了。淺析

小艾:那來講講唄。智針和

小牛:來。淺析

智能指針的智針和引入

大家都知道,指針是淺析 C++ 中非常重要的一部分,大家在初期學習 C++ 的智針和時候一定學過類似這樣的指針方式。

int *ptr;

這種指針也被稱為裸指針。淺析但是使用裸指針會存在一些不足

  1. 如果使用裸指針分配內(nèi)存后,忘記手動釋放資源,會出現(xiàn)內(nèi)存泄漏。

  2. 如果使用多個裸指針指向同一資源,其中一個指針對資源進行釋放,其它指針成為空懸指針,如果再次釋放會存在不可預測的錯誤。上圖中當 sp1 把資源釋放后,sp2 成了空懸指針。空懸指針指的是指針所指向的對象已經(jīng)釋放的時候自身卻沒有被置為 nullptr。sp1 通過 free/delete釋放資源的內(nèi)存時,內(nèi)存不會立刻被系統(tǒng)回收,而是狀態(tài)改變?yōu)榭杀黄渌胤缴暾埖臓顟B(tài)。這時當再次操作 sp2,這塊內(nèi)存可能被其它地方申請了,而具體被誰申請了是不確定的,因此可能導致的錯誤也是不可預測的。

  3. 如果程序異常退出時,裸指針的釋放資源的代碼未能執(zhí)行,也會造成內(nèi)存泄漏。

為了改善裸指針的不足,確保資源的分配和釋放是配對的,開發(fā)者提出了智能指針。智能指針主要是對裸指針進行了一次面向?qū)ο蟮姆庋b,在構造函數(shù)中初始化資源地址,在析構函數(shù)中釋放資源。當資源應該被釋放時,指向它的智能指針可以確保自動地釋放它。

C++ 庫中,為智能指針提供了不帶引用計數(shù)和帶引用計數(shù)的兩種方案。

引用計數(shù)用于表示有多少智能指針引用同一資源。不帶引用計數(shù)的智能指針采用獨占資源的方式,而帶引用計數(shù)的智能指針則可以同時多個指向同一資源。下面介紹一下它們的主要特點區(qū)別

智能指針的分類

不帶引用計數(shù)的智能指針

不帶引用計數(shù)的智能指針包括 auto_ptr、scoped_ptr和 unique_ptr三種指針。

不帶引用計數(shù)的智能指針

1. auto_ptr:

我們先來看個例子:

#include int main() {  auto_ptrptr(new int(6));//定義auto_ptr指針ptr auto_ptrptr1(ptr); //拷貝構造ptr定義ptr1 *ptr=8;//對空指針ptr賦值會產(chǎn)生不可預料的錯誤 return 0;}

開始時 ptr 指向資源,一個整型數(shù)字6,當用 ptr1 拷貝構造 ptr 時,ptr1 指向資源,而 ptr 則指向 nullptr。下一行程序中如果對空指針 ptr 賦值 8,將會產(chǎn)生不可預料的錯誤。

下圖表示 auto_ptr指針對資源的指向過程。

auto_ptr

使用拷貝構造時,如果只有最后一個 auto_ptr持有資源,其余 auto_ptr持有的資源會被置為 nullptr。

因此需要注意,不能在容器中使用 auto_ptr,當容器發(fā)生拷貝時,原容器中 auto_ptr持有的資源會置 nullptr。

下面我們再來看一下 auto_ptr的部分源碼和部分解析:

templateclass auto_ptr {  public: typedef _Ty?element_type; explicit auto_ptr(_Ty?*?_Ptr=nullptr) noexcept :?_Myptr(_Ptr)//初始化列表 {  //構造函數(shù) } auto_ptr(auto_ptr&?_Right) noexcept :?_Myptr(_Right.release())??{  //拷貝構造函數(shù),會調(diào)用release()函數(shù) } _Ty?* release() noexcept {  /*使用拷貝構造時,最后一個auto_ptr持有資源,???其余被置為nullptr*/ _Ty?*?_Tmp?=?_Myptr;??_Myptr?= nullptr; return (_Tmp);??} private:?_Ty?*?_Myptr;//指向資源  };

當試圖調(diào)用 auto_ptr的拷貝構造函數(shù)時,在初始化列表中調(diào)用了 release()函數(shù),release()函數(shù)用一個 _Tmp指針保存資源并返回用于初始化當前的 auto_ptr的類成員 _Myptr,而 _Right對應的 _Myptr被置為 nullptr。

2. scoped_ptr

scoped_ptr和 auto_ptr有些不同,它私有化了拷貝構造函數(shù)和賦值函數(shù),資源的所有權無法進行轉(zhuǎn)移,也無法在容器中使用。

下面使用一段代碼表現(xiàn) scoped_ptr的特性,如果不規(guī)范使用會發(fā)生錯誤。

正確用法:

scoped_ptrsp1(new int(6));//初始化sp1指針 

錯誤用法:

scoped_ptrsp2(sp1);//錯誤,無法拷貝構造 

這種方法是錯誤的,因為scoped_ptr私有化了拷貝構造函數(shù),無法顯式調(diào)用。

scoped_ptrsp3(new int(5))//初始化sp2指針 sp1=sp3;//錯誤,無法賦值 

這種方法是錯誤的,因為scoped_ptr私有化了賦值構造函數(shù),無法顯式調(diào)用。

有時候面試官會問到,scoped_ptr是如何保證資源的所有權的?

這時候就可以按照上面的講解來回答:

scoped_ptr私有化了拷貝構造函數(shù)和賦值函數(shù),資源的所有權無法進行轉(zhuǎn)移,所以保證了資源的所有權。

然后再來看一下 scoped_ptr的部分源碼和部分解析:

templateclass scoped_ptr {  private:????T?*?px;?????scoped_ptr(scoped_ptr const &);//拷貝構造函數(shù) scoped_ptr?& operator=(scoped_ptr const &);//賦值構造函數(shù) public: typedef T?element_type; explicit scoped_ptr(?T?*?p?= nullptr ): px(?p?) { ????}????~scoped_ptr() //析構函數(shù) };

scoped_ptr通過私有化拷貝構造函數(shù)和賦值構造函數(shù)來拒絕淺拷貝的發(fā)生。

值得注意的是,auto_ptr是通過將除最后一個以外的其它 auto_ptr置 nullptr來避免淺拷貝的發(fā)生,它的資源所有權是可以轉(zhuǎn)移的。

scoped_ptr是直接禁止了拷貝與賦值,資源所有權無法轉(zhuǎn)移。

3. unique_ptr

unique_ptr刪除了拷貝構造函數(shù)和賦值函數(shù),因此不支持普通的拷貝或賦值操作。如下所示:

unique_ptrp1(new int(6));//正確寫法 unique_ptrp2(p1); //這么寫是錯誤的: //?unique_ptr不支持拷貝 unique_ptr?p3;p3=p2;//這么寫是錯誤的:unique_ptr不支持賦值 

再來看一下 unique_ptr的部分源碼和部分解析:

templateclass unique_ptr: public _Unique_ptr_base<_Ty, _Dx>{  public: typedef _Unique_ptr_base<_Ty, _Dx>?_Mybase; typedef typename _Mybase::pointer?pointer; typedef _Ty?element_type; typedef _Dx?deleter_type; unique_ptr(unique_ptr&&?_Right) noexcept :?_Mybase(_Right.release(),???_STD?forward(_Right.get_deleter()))??{  //?右值引用的拷貝構造函數(shù) } unique_ptr& operator=(unique_ptr&&?_Right) noexcept {  //提供了右值引用的operator=賦值構造函數(shù) if (this !=?_STD?addressof(_Right))???{ ????reset(_Right.release()); this->get_deleter()?=?_STD?forward(_Right.get_deleter());???} return (*this);??} /*?刪除了unique_ptr的拷貝構造和賦值函數(shù),拒絕淺拷貝?*/ unique_ptr(const unique_ptr&)?= delete; unique_ptr& operator=(const unique_ptr&)?= delete;?};

unique_ptr和scoped_ptr一樣禁止了拷貝構造和賦值構造,引入了帶右值引用的拷貝構造和賦值。可以把 unique_ptr作為函數(shù)的返回值。

不帶引用計數(shù)的智能指針總結(jié):

相同點:最終只有一個智能指針持有資源。

不同點:

  1. auto_ptr進行拷貝構造時,會對之前的auto_ptr的資源置nullptr操作;
  2. scoped_ptr通過私有化了拷貝構造和賦值函數(shù)杜絕淺拷貝;
  3. unique_ptr通過刪除了拷貝構造和賦值函數(shù)函數(shù)杜絕淺拷貝,但引入了帶右值引用的拷貝構造和賦值函數(shù)。

帶引用計數(shù)的智能指針

當需要多個智能指針指向同一個資源時,使用帶引用計數(shù)的智能指針。

每增加一個智能指針指向同一資源,資源引用計數(shù)加一,反之減一。當引用計數(shù)為零時,由最后一個指向資源的智能指針將資源進行釋放。

下圖表示帶引用計數(shù)智能指針的工作過程。sp1 對象和 sp2 對象通過指針指向同一資源,引用計數(shù)器記錄了引用資源的對象個數(shù)。

智能指針的工作過程

當 sp1 對象發(fā)生析構時,引用計數(shù)器的值減 1,由于引用計數(shù)不等于 0,資源并未釋放,如下圖所示:

sp1 對象發(fā)生析構

當 sp2 對象也發(fā)生析構,引用計數(shù)減為 0,資源釋放,如下圖所示:

sp2 對象也發(fā)生析構

即引用計數(shù)可以保證多個智能指針指向資源時資源在所有智能對其取消引用再釋放,避免過早釋放產(chǎn)生空懸指針。帶引用計數(shù)的智能指針包括 shared_ptr和 weak_ptr。

資源釋放

1. shared_ptr

shared_ptr一般稱為強智能指針,一個 shared_ptr對資源進行引用時,資源的引用計數(shù)會增加一,通常用于管理對象的生命周期。只要有一個指向?qū)ο蟮?shared_ptr存在,該對象就不會析構。

上圖中引用計數(shù)的工作過程就使用了 shared_ptr。

2. weak_ptr

weak_ptr一般被稱為弱智能指針,其對資源的引用不會引起資源的引用計數(shù)的變化,通常作為觀察者,用于判斷資源是否存在,并根據(jù)不同情況做出相應的操作。

比如使用 weak_ptr對資源進行弱引用,當調(diào)用 weak_ptr的 lock()方法時,若返回 nullptr,則說明資源已經(jīng)不存在,放棄對資源繼續(xù)操作。否則,將返回一個 shared_ptr對象,可以繼續(xù)操作資源。

另外,一旦最后一個指向?qū)ο蟮?shared_ptr被銷毀,對象就會被釋放。即使有 weak_ptr指向?qū)ο螅瑢ο笠策€是會被釋放。


小艾問:既然它這引用都不算數(shù),那它有什么用呢?

小牛答:別急,我們來慢慢講。


enable_shared_from_this 機制

小牛:考慮下面這樣一個場景:

在多線程環(huán)境中,假設有一個對象池類 ObjectPool和一個對象類 Object。ObjectPool類主要實現(xiàn)通過不同的 key 返回對應 Object 對象。

要求同一程序中由 Object 類實例出的不同對象只有一個,即當多處用到同一個對象,Object 對象應該被共享。同時當對象不再需要時應該被析構,并刪除對應的 key。

多線程應用場景

小艾說:這還不簡單,看我的。代碼刷的一下就寫完了。

//場景代碼 #include class ObjectPool:boost::noncopyable{  public: shared_ptrget(const string&?key) {  shared_ptrshObject; MutexLockGuard lock(mutex);?weak_ptr&?wkObject=object[key];?shObject=wkObject.lock(); //對象存在,提升成功并返回 if(!shObject){  /*對象不存在,提升失敗,shOject重新??指向新創(chuàng)建的Object對象,??并綁定回調(diào)函數(shù),讓對象Oject需要析構時??調(diào)用OjectPool對象的成員函數(shù)*/ shObject.reset(new Object(key),????????????????????boost::bind(&????????ObjectPool::deleteObject,this,????????_1));???????wkObject=shObject;?} return shObject;?} private: void deleteObject(Object*?obj) {  /*回調(diào)函數(shù),在對象需要析構時調(diào)用,從map中?刪除對象和對應的key*/ if(obj){  MutexLockGuard lock(mutex);???object.erase(obj->key());??} delete obj;?} mutable MutexLock?mutex; std::map?object; /*map中不能使用shared_ptr,這會導致Oject對象永遠不會被銷?毀*/ };

小牛說:你這有問題啊?

小艾答:有什么問題?為了實現(xiàn) Object 類析構時調(diào)用 ObjectPool的回調(diào)函數(shù),代碼中把 ObjectPool的 this 指針保存在了 boost::function處。

小牛說:那線程安全問題就來了。如果 ObjectPool先于 Object 對象析構,就會發(fā)生 core dump。因為 ObjectPool對象已經(jīng)不存在了,也就沒有辦法調(diào)用其成員方法。

小艾問:那怎么解決呢?

小牛說:簡單啊,只需將 this 指針替換成指向當前對象的 shared_ptr,從而保證在 Object 對象需要調(diào)用 ObjectPool::deleteObject時 ObjectPool還活著。你要不試試實現(xiàn)一下?

小艾說:那我寫一個吧。

shared_ptr getSharedPtr() {  return shared_ptr(this);?}

小牛答:問題來了,在多線程環(huán)境中,在需要返回 this 對象時是無法得知對象的生存情況的。因此不能直接返回 this 對象

給你普及個解決方法吧,你可以通過繼承 enable_shared_from_this模板對象,然后調(diào)用從基類繼承而來的 shared_from_this方法來安全返回指向同一資源對象的 shared_ptr。

小艾:為什么繼承 enable_shared_from_this 模板對象就可以安全返回?

小牛:在回答你的問題前,我們先來講講 shared_ptr的構造函數(shù)拷貝構造函數(shù)對資源和引用計數(shù)影響的區(qū)別。

下面從 shared_ptr的實現(xiàn)原理來看:

shared_ptr從 _Ptr_base繼承了 element_type和 _Ref_count_base類型的兩個成員變量。

templateclass _Ptr_base {  private:?element_type?*?_Ptr{ nullptr}; //?指向資源的指針 _Ref_count_base?*?_Rep{ nullptr}; //?指向資源引用計數(shù)的指針 };

_Ref_count_base中定義了原子類型的變量 _Uses和 _Weaks,它們分別記錄資源的引用個數(shù)和資源觀察者的個數(shù)。

class __declspec(novtable)?_Ref_count_base {  private:?_Atomic_counter_t?_Uses;//記錄資源引用個數(shù) _Atomic_counter_t?_Weaks;//記錄觀察者個數(shù) }

當要使用 shared_ptr管理同一資源,調(diào)用 shared_ptr的構造函數(shù)和拷貝構造函數(shù)是不一樣的,它們雖然使得不同 shared_ptr指向同一資源,但管理引用計數(shù)資源的方式卻不一樣。

下面給出兩個 shared_ptr管理同一資源(A對象)使用不同構造函數(shù)對引用計數(shù)對象的影響。

方式1:調(diào)用構造函數(shù)

class A {  public:??A(){ }??~A(){ }};A?*p?= new A(); shared_ptr ptr1(p);//調(diào)用構造函數(shù) shared_ptr ptr2(p);//調(diào)用構造函數(shù) 
方式1:調(diào)用構造函數(shù)

如上圖所示,方式1中 ptr1 和 ptr2 都調(diào)用了 shared_ptr的構造函數(shù),該構造方式使得 ptr1和 ptr2都開辟了自已的引用資源對象 _Ref_count_base,即 _Ref_count_base有兩個,都記錄了 A 對象的引用計數(shù)為 1,析構時 ptr1和 ptr2的引用計數(shù)各自減為 1,導致 A 對象析構兩次,出現(xiàn)邏輯錯誤。

方式2:調(diào)用拷貝構造函數(shù)

class A {  public:???A(){ }???~A(){ }}A?*p?= new A(); shared_ptr ptr1(p);//調(diào)用構造函數(shù) shared_ptr ptr2(ptr1);//調(diào)用拷貝構造函數(shù) 
方式2:調(diào)用拷貝構造函數(shù)

如上圖所示,方式2中由于 ptr2拷貝構造 ptr1,它們引用的 _Ref_count_base是同一個,因此引用計數(shù)為 2,析構的時候 A 對象只析構一次,正常運行。

在明白了 shared_ptr構造函數(shù)和拷貝構造函數(shù)的做的事情不同后,就能理解當需要返回一個需要 shared_ptr管理的對象為什么不能寫成 return shared_ptr< A >(this)了。

小艾:說的沒錯,因為這樣會調(diào)用 shared_ptr的構造函數(shù),對于 this 對象再創(chuàng)建一個新的引用計數(shù)對象,從而導致對象多次析構而出現(xiàn)邏輯錯誤。

小牛:再給你深入講講 enable_shared_from_this的實現(xiàn)機制。

如下所示,enable_shared_from_this類中包含一個作為觀察者的成員變量。

templateclass enable_shared_from_this {  public: mutable weak_ptr_Wptr;//指向資源 };

當一個類繼承了 enable_shared_from_this類,就繼承了 _Wptr這個成員變量。

當使用 shared_ptr< A >(new A())第一次構造智能指針對象時,就會初始化一個作為觀察者的弱智能指針 _Wptr指向A對象資源。

再通過 shared_from_this()方法代替 shared_ptr的普通構造函數(shù)來返回一個 shared_ptr對象,從而避免產(chǎn)生額外的引用計數(shù)對象。

shared_ptr getSharedPtr() {  return shared_from_this();?}

shared_from_this函數(shù)中,主要嘗試將弱智能指針提升為強智能指針來返回一個 shared_ptr對象。

這樣還能在多線程環(huán)境中判斷對象是否存活,存活即提升成功,安全返回。如果對象已經(jīng)析構,則放棄提升,即起到了保證線程安全的作用。

小牛:了解了enable_shared_from_this,要不再試試改代碼?

小艾:那我來改一下之前的代碼。

第一處修改:

class ObjectPool:boost::noncopyable //為 class ObjectPool:public boost::enable_shared_from_this,????????????????boost::noncopyable{ /*...*/};

第二處修改:

//改變 shared_ptrget(const string&?key) {  /*...*/ shObject.reset(new Object(key),?????????????????????boost::bind(&ObjectPool::deleteObject,this,_1)); /*...*/ } //為 shared_ptrget(const string&?key) {  /*...*/ shObject.reset(new Object(key),?????????????????????boost::bind(&ObjectPool::deleteObject,shared_from_this(),_1)); /*...*/ }

完整代碼:

#include class ObjectPool:public boost::enable_shared_from_this,????????????????boost::noncopyable{  public: shared_ptrget(const string&?key) {  shared_ptrshObject; MutexLockGuard lock(mutex);???weak_ptr&?wkObject=object[key];???shObject=wkObject.lock();//對象存在,提升成功并返回 if(!shObject){  /*對象不存在,提升失敗,shOject重新指向新創(chuàng)建的????Object對象,并綁定回調(diào)函數(shù),讓對象Oject需要析構時????調(diào)用OjectPool對象的成員函數(shù)*/ shObject.reset(new Object(key),??????????????????????boost::bind(&??????????ObjectPool::deleteObject,shared_from_this(),??????????_1));??????wkObject=shObject;???} return shObject;} private: void deleteObject(Object*?obj) {  /*回調(diào)函數(shù),在對象需要析構時調(diào)用,從map中刪除對象和對應的key*/ if(obj){  MutexLockGuard lock(mutex);?????object.erase(obj->key());????} delete obj;} mutable MutexLock?mutex; std::map?object; /*map中不能使用shared_ptr,這會導致Oject對象永遠不會被銷毀*/ };

小牛:不錯不錯,這下懂了 shared_ptr和 weak_ptr結(jié)合的用法了吧。

帶引用計數(shù)智能指針總結(jié):

  1. shared_ptr會增加資源的引用計數(shù),常用于管理對象的生命周期。
  2. weak_ptr不會增加資源的引用計數(shù),常作為觀察者用來判斷對象是否存活。
  3. 使用 shared_ptr的普通拷貝構造函數(shù)會產(chǎn)生額外的引用計數(shù)對象,可能導致對象多次析構。使用 shared_ptr的拷貝構造函數(shù)則只影響同一資源的同一引用計數(shù)的增減。
  4. 當需要返回指向當前對象的 shared_ptr時,優(yōu)先使用 enable_shared_from_this機制。

總結(jié)

今天我們了解了面試中常常會問到的C++ 智能指針的相關知識點,結(jié)合源碼和示例理清各種智能指針的特點。

并且結(jié)合一個實際的多線程應用場景,講解了enable_shared_from_this 機制,希望能對大家的學習有所幫助。

總結(jié)

參考

  1. https://blog.csdn.net/qiangweiyuan/article/details/88562935
  2. 《Linux多線程服務端編程使用muduo C++ 網(wǎng)絡庫》
  3. 《C++ Primer》
  4. 《More Effective C++》

免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

Popular articles

主站蜘蛛池模板: 欧美最猛黑人xxxx黑人猛交黄| 国产初次破初视频情侣| 亚洲免费一级视频| 久久精品人人做人人爽电影蜜月| 果冻传媒国产仙踪林欢迎你| 免费观看国产| 欧美3p大片在线观看完整版| 免费啪啪社区免费啪啪手机版| 四虎永久地址| 快点cao我要被cao烂了| 亚洲欧美精品一中文字幕| 女人是男人的未来1分29分| 日本乱子伦xxxx| 夫醉酒被公侵犯的电影中字版| а√最新版在线天堂| 我被继夫添我阳道舒服男男| 日韩欧美高清在线| а√最新版地址在线天堂| 午夜dj影院| yy6080欧美三级理论| 美国式的禁忌80版| 好吊妞视频这里有精品| 538在线视频观看| 亚洲剧场午夜在线观看| 四虎在线免费播放| 亚洲国产中文在线视频| 福利久草| 国产在线视频一区二区三区| 91蜜桃视频| 全肉高h动漫在线看 | 处破之轻点好疼十八分钟| 成人口工漫画网站免费| 最近免费中文字幕大全高清10| 国产视频精品久久| 亚洲二区电影| 免费污视频在线| 老师粗又长好猛好爽视频| 最近免费中文字幕大全高清片| 国产粗话肉麻对白在线播放| 粗大黑硬长爽猛欧美视频| 免费毛片a线观看|