<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      C++智能指針的enable_shared_from_this和shared_from_this機制

      前言

      之前學習muduo網絡庫的時候,看到作者陳碩用到了enable_shared_from_thisshared_from_this,一直對此概念是一個模糊的認識,隱約記著這個機制是在計數器智能指針傳遞時才會用到的,今天對該機制進行梳理總結一下吧。

      如果不熟悉C++帶引用計數的智能指針shared_ptrweak_ptr,可參考這篇文章:??深入掌握智能指針

      這篇文章主要介紹C++11提供的智能指針相關的enable_shared_from_thisshared_from_this機制。

      問題代碼

      我們先給出兩個智能指針的應用場景代碼,這些代碼都有問題,仔細思考下問題原因。

      代碼清單1

      #include <iostream>
      #include <memory>
      using namespace std;
      // 智能指針測試類
      class A {
      public:
      	A() : m_ptr(new int) { cout << "A()" << endl; }
      	~A() {
      		cout << "~A()" << endl;
      		delete m_ptr; 
      		m_ptr = nullptr;
      	}
      private:
      	int *m_ptr;
      };
      int main() {
      	A *p = new A(); // 裸指針指向堆上的對象
      
      	shared_ptr<A> ptr1(p); // 用shared_ptr智能指針管理指針p指向的對象
      	shared_ptr<A> ptr2(p); // 用shared_ptr智能指針管理指針p指向的對象
      	// 下面兩次打印都是1,因此同一個new A()被析構兩次,邏輯錯誤
      	cout << ptr1.use_count() << endl; 
      	cout << ptr2.use_count() << endl;
      
      	return 0;
      }
      

      代碼打印結果如下:

      A()
      1
      1
      ~A()
      ~A()
      free(): double free detected in tcache 2
      Aborted (core dumped)
      

      main函數中,雖然用了兩個智能指針shared_ptr,但是它們管理的都是同一個資源,資源的引用計數應該是2,為什么打印出來是1呢?導致出main函數把A對象析構了兩次,不正確!如果你有這樣的疑問,說明對于shared_ptr的底層原理還沒有完全搞清楚。

      代碼清單2

      #include <iostream>
      using namespace std;
      // 智能指針測試類
      class A {
      public:
      	A() : m_ptr(new int) { cout << "A()" << endl; }
      	~A() {
      		cout << "~A()" << endl;
      		delete m_ptr; 
      		m_ptr = nullptr;
      	}
      	 
      	// A類提供了一個成員方法,返回指向自身對象的shared_ptr智能指針。
      	shared_ptr<A> getSharedPtr() { 
      		/*注意:不能直接返回this,在多線程環境下,根本無法獲知this指針指向
      		的對象的生存狀態,通過shared_ptr和weak_ptr可以解決多線程訪問共享		
      		對象的線程安全問題,參考我的另一篇介紹智能指針的博客*/
      		return shared_ptr<A>(this); 
      	}
      private:
      	int *m_ptr;
      };
      int main() {
      	shared_ptr<A> ptr1(new A());
      	shared_ptr<A> ptr2 = ptr1->getSharedPtr();
      
      	/* 按原先的想法,上面兩個智能指針管理的是同一個A對象資源,但是這里打印都是1
      	導致出main函數A對象析構兩次,析構邏輯有問題*/
      	cout << ptr1.use_count() << endl; 
      	cout << ptr2.use_count() << endl;
      
      	return 0;
      }
      

      代碼運行結果打印如下:

      A()
      1
      1
      ~A()
      ~A()
      free(): double free detected in tcache 2
      Aborted (core dumped)
      

      代碼同樣有錯誤,A對象被析構了兩次,而且看似兩個shared_ptr指向了同一個A對象資源,但是資源計數并沒有記錄成2,還是1,不正確。

      shared_ptr原理分析

      如果你能夠理解上面代碼的問題所在,那么直接跳到下一節看上面錯誤代碼的解決方案;如果不明白問題的所在,通過下面的源碼介紹,仔細理解shared_ptr的實現原理。

      源碼上shared_ptr的定義如下:

      template<class _Ty>
      	class shared_ptr
      		: public _Ptr_base<_Ty>
      
      

      shared_ptr是從_Ptr_base繼承而來的,作為派生類,shared_ptr本身沒有提供任何成員變量,但是它從基類_Ptr_base繼承來了如下成員變量(只羅列部分源碼):

      template<class _Ty>
      class _Ptr_base
      {	// base class for shared_ptr and weak_ptr
      protected:
      	void _Decref()
      		{	// decrement reference count
      		if (_Rep)
      			{
      			_Rep->_Decref();
      			}
      		}
      
      	void _Decwref()
      		{	// decrement weak reference count
      		if (_Rep)
      			{
      			_Rep->_Decwref();
      			}
      		}
      private:
      	// _Ptr_base的兩個成員變量,這里只羅列了_Ptr_base的部分代碼
      	element_type * _Ptr{nullptr}; // 指向資源的指針
      	_Ref_count_base * _Rep{nullptr}; // 指向資源引用計數的指針
      };
      

      _Ref_count_base記錄資源的類是怎么定義的呢?如下(只羅列部分源碼):

      class __declspec(novtable) _Ref_count_base
      	{	// common code for reference counting
      private:
      	/**
      	 * _Uses記錄了資源的引用計數,也就是引用資源的shared_ptr的個數;
      	 * _Weaks記錄了weak_ptr的個數,相當于資源觀察者的個數,都是定義成基于CAS操作的原子類型,增減引用計數時時線程安全的操作
      	 **/
      	_Atomic_counter_t _Uses;
      	_Atomic_counter_t _Weaks;
      }
      

      也就是說,當我們定義一個shared_ptr<int> ptr(new int)的智能指針對象時,該智能指針對象本身的內存是8個字節,如下圖所示:

      那么把智能指針管理的外部資源以及引用計數資源都畫出來的話,就是如下圖的展示:

      當你做這樣的代碼操作時:

      shared_ptr<int> ptr1(new int);
      shared_ptr<int> ptr2(ptr1);
      cout << ptr1.use_count() << endl;
      cout << ptr2.use_count() << endl;
      

      這段代碼沒有任何問題,ptr1和ptr2管理了同一個資源,引用計數打印出來的都是2,出函數作用域依次析構,最終new int資源只釋放一次,邏輯正確!這是因為shared_ptr ptr2(ptr1)調用了shared_ptr的拷貝構造函數(源碼可以自己查看下),只是做了資源的引用計數的改變,沒有額外分配其它資源,如下圖所示:

      注意:兩個shared_ptr對象引用的是同一個引用計數對象_Ref_count_base,依次析構的時候,最終資源new int只釋放一次,正確

      但是當你做如下代碼操作時:

      int *p = new int;
      shared_ptr<int> ptr1(p);
      shared_ptr<int> ptr2(p);
      cout << ptr1.use_count() << endl;
      cout << ptr2.use_count() << endl;
      

      這段代碼就有問題了,因為shared_ptr<int> ptr1(p)shared_ptr<int> ptr2(p)都調用了shared_ptr的構造函數,在它的構造函數中,都重新開辟了引用計數的資源,導致ptr1ptr2都記錄了一次new int的引用計數,都是1,析構的時候它倆都去釋放內存資源,導致釋放邏輯錯誤,如下圖所示:

      image

      注意:兩個shared_ptr對象都開辟了自己的引用計數對象_Ref_count_base,都記錄new int資源的引用計數為1,析構的時候引用計數減到0,都認為自己該釋放new int資源,錯誤!

      上面兩個代碼段,分別是shared_ptr的構造函數和拷貝構造函數做的事情,導致雖然都是指向同一個new int資源,但是對于引用計數對象的管理方式,這兩個函數是不一樣的,構造函數是新分配引用計數對象,拷貝構造函數只做引用計數增減

      相信說到這里,大家知道最開始的兩個代碼清單上的代碼為什么出錯了吧,因為每次調用的都是shared_ptr的構造函數,雖然大家管理的資源都是一樣的,_Ptr都是指向同一個堆內存,但是_Rep卻指向了不同的引用計數對象,并且都記錄引用計數是1,出作用域都去析構,使得同一塊內存被析構多次,導致問題發生!

      問題修改

      代碼清單1修改

      那么清單1的代碼修改很簡單,就是在產生同一資源的多個shared_ptr的時候,通過拷貝構造函數或者賦值operator=函數進行,不要重新構造,避免產生多個引用計數對象,代碼修改如下:

      int main() {
      	A *p = new A(); // 裸指針指向堆上的對象
      
      	shared_ptr<A> ptr1(p); // 用shared_ptr智能指針管理指針p指向的對象
      	shared_ptr<A> ptr2(ptr1); // 用ptr1拷貝構造ptr2
      	// 下面兩次打印都是2,最終隨著ptr1和ptr2析構,資源只釋放一次,正確!
      	cout << ptr1.use_count() << endl; 
      	cout << ptr2.use_count() << endl;
      
      	return 0;
      }
      

      代碼清單2修改 enable_shared_from_this和shared_from_this

      那么清單2代碼怎么修改呢?注意我們有時候想在類里面提供一些方法,返回當前對象的一個shared_ptr強智能指針,做參數傳遞使用(多線程編程中經常會用到)。

      首先肯定不能像上面代碼清單2那樣寫return shared_ptr<A> (this)這會調用shared_ptr智能指針的構造函數,對this指針指向的對象,又建立了一份引用計數對象,加上main函數中的shared_ptr<A> ptr1(new A());已經對這個A對象建立的引用計數對象,又成了兩個引用計數對象,對同一個資源都記錄了引用計數,為1,最終兩次析構對象釋放內存,錯誤!

      那如果一個類要提供一個函數接口,返回一個指向當前對象的shared_ptr智能指針怎么辦?方法就是繼承enable_shared_from_this類,然后通過調用從基類繼承來的shared_from_this()方法返回指向同一個資源對象的智能指針shared_ptr

      修改如下:

      #include <iostream>
      #include <memory>
      using namespace std;
      // 智能指針測試類,繼承enable_shared_from_this類
      class A : public enable_shared_from_this<A> {
      public:
      	A() : m_ptr(new int) { cout << "A()" << endl; }
      	~A() {
      		cout << "~A()" << endl;
      		delete m_ptr;
      		m_ptr = nullptr;
      	}
      
      	// A類提供了一個成員方法,返回指向自身對象的shared_ptr智能指針
      	shared_ptr<A> getSharedPtr() {
      		/*通過調用基類的shared_from_this方法得到一個指向當前對象的智能指針*/
      		return shared_from_this();
      	}
      private:
      	int *m_ptr;
      };
      

      一個類繼承enable_shared_from_this會怎么樣?看看enable_shared_from_this基類的成員變量有什么,如下:

      template<class _Ty>
      	class enable_shared_from_this
      	{	// provide member functions that create shared_ptr to this
      public:
      	using _Esft_type = enable_shared_from_this;
      
      	_NODISCARD shared_ptr<_Ty> shared_from_this()
      		{	// return shared_ptr
      		return (shared_ptr<_Ty>(_Wptr));
      		}
      	// 成員變量是一個指向資源的弱智能指針
      	mutable weak_ptr<_Ty> _Wptr;
      };
      

      也就是說,如果一個類繼承了enable_shared_from_this,那么它產生的對象就會從基類enable_shared_from_this繼承一個成員變量_Wptr,當定義第一個智能指針對象的時候shared_ptr<A> ptr1(new A()),調用shared_ptr的普通構造函數,就會初始化A對象的成員變量_Wptr,作為觀察A對象資源的一個弱智能指針觀察者(在shared_ptr的構造函數中實現,有興趣可以自己調試跟蹤源碼實現)。

      然后代碼如下調用shared_ptr<A> ptr2 = ptr1->getSharedPtr()getSharedPtr函數內部調用shared_from_this()函數返回指向該對象的智能指針,這個函數怎么實現的呢,看源碼:

      shared_ptr<_Ty> shared_from_this()
      {	// return shared_ptr
      return (shared_ptr<_Ty>(_Wptr));
      }
      

      shared_ptr<_Ty>(_Wptr),說明通過當前A對象的成員變量_Wptr構造一個shared_ptr出來,看看shared_ptr相應的構造函數:

      shared_ptr(const weak_ptr<_Ty2>& _Other)
      {	// construct shared_ptr object that owns resource *_Other
      if (!this->_Construct_from_weak(_Other)) // 從弱智能指針提升一個強智能指針
      	{
      	_THROW(bad_weak_ptr{});
      	}
      }
      

      接著看上面調用的_Construct_from_weak方法的實現如下:

      template<class _Ty2>
      bool _Construct_from_weak(const weak_ptr<_Ty2>& _Other)
      {	// implement shared_ptr's ctor from weak_ptr, and weak_ptr::lock()
      // if通過判斷資源的引用計數是否還在,判定對象的存活狀態,對象存活,提升成功;
      // 對象析構,提升失敗!之前的博客內容講過這些知識,可以去參考!
      if (_Other._Rep && _Other._Rep->_Incref_nz())
      	{
      	_Ptr = _Other._Ptr;
      	_Rep = _Other._Rep;
      	return (true);
      	}
      
      return (false);
      }
      

      綜上所說,所有過程都沒有再使用shared_ptr的普通構造函數,沒有在產生額外的引用計數對象,不會存在把一個內存資源,進行多次計數的過程;更關鍵的是,通過weak_ptrshared_ptr的提升,還可以在多線程環境中判斷對象是否存活或者已經析構釋放,在多線程環境中是很安全的,通過this裸指針進行構造shared_ptr,不僅僅資源會多次釋放,而且在多線程環境中也不確定this指向的對象是否還存活。

      最終代碼清單2修改如下:

      #include <iostream>
      #include <memory>
      using namespace std;
      // 智能指針測試類,繼承enable_shared_from_this類
      class A : public enable_shared_from_this<A> {
      public:
      	A() : m_ptr(new int) { cout << "A()" << endl; }
      	~A() {
      		cout << "~A()" << endl;
      		delete m_ptr;
      		m_ptr = nullptr;
      	}
      
      	// A類提供了一個成員方法,返回指向自身對象的shared_ptr智能指針
      	shared_ptr<A> getSharedPtr() {
      		/*通過調用基類的shared_from_this方法得到一個指向當前對象的智能指針*/
      		return shared_from_this();
      	}
      private:
      	int *m_ptr;
      };
      int main() {
      	shared_ptr<A> ptr1(new A());
      	shared_ptr<A> ptr2 = ptr1->getSharedPtr();
      
      	// 引用計數打印為2
      	cout << ptr1.use_count() << endl;
      	cout << ptr2.use_count() << endl;
      
      	return 0;
      }
      

      代碼打印結果如下:

      A()
      2
      2
      ~A()
      

      打印完全正確,A對象構造一次,析構一次,引用計數為2。

      總結

      以上就是對enable_shared_from_this和shared_from_this機制的介紹。這東西主要解決了該問題:當返回某對象時,由于智能指針調用常規的構造函數導致引用計數類的多次構造,從而導致在釋放內存時,多個智能指針對同一塊內存進行多次釋放,出現Core dump。使用該機制則可返回指向某對象的智能指針,這樣就調用的是智能指針的拷貝構造函數而非常規的構造函數,使得引用計數類不會被多次構造,避免出現同一內存多次釋放的情況。

      posted @ 2022-10-08 23:55  miseryjerry  閱讀(1176)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 久久精品蜜芽亚洲国产AV| 色综合久久中文字幕综合网| 狠狠色综合久久丁香婷婷| 亚洲天堂成人黄色在线播放| 欧美性猛交xxxx乱大交丰满| 内射视频福利在线观看| 国产亚洲精品久久777777| 国产成人精品亚洲一区二区| 亚洲国产美女精品久久久| 国产永久免费高清在线| 亚洲av色一区二区三区| 国产一区二区三区小说| 欧美一本大道香蕉综合视频| 国产一区二区亚洲一区二区三区| 久久久久免费看少妇高潮A片| 色综合一本到久久亚洲91| 日产国产精品亚洲系列| 黑人精品一区二区三区不| 国产999久久高清免费观看 | 午夜国产精品福利一二| 黄色A级国产免费大片视频| 娱乐| 无码熟妇人妻av影音先锋| 视频二区中文字幕在线| 亚洲日本精品一区二区| 综合色天天久久| 91久久久久无码精品露脸| 国产成人高清亚洲一区二区| 国产精品久久久久av福利动漫| 丰满少妇特黄一区二区三区| jizzjizz少妇亚洲水多| 四虎亚洲精品高清在线观看| 性色欲情网站| 黑人精品一区二区三区不| 国产精品一区中文字幕| 少妇人妻偷人精品系列| 国产乱码日产乱码精品精| 永久免费无码av在线网站| 91孕妇精品一区二区三区| 荥阳市| 精品少妇av蜜臀av|