天天看點

C++智能指針:weak_ptr實作詳解

文章目錄

  • ​​weak_ptr描述​​
  • ​​聲明​​
  • ​​作用​​
  • ​​原理實作​​
  • ​​函數成員使用​​
  • ​​總結​​

weak_ptr描述

聲明

頭檔案:​

​<memory>​

​​ 模版類:​

​template <class T> class weak_ptr​

​ 聲明方式:​

​std::weak_ptr<type_id> statement​

作用

根據boost庫的官方描述,weak_ptr是由shared_ptr管理的一種弱引用對象的模版類。​

​weak_ptr​

​​的對象能夠使用​

​shared_ptr​

​​指針的構造函數轉換為一個shared_ptr對象。但是這裡​

​shared_ptr​

​​的構造函數參數需要包含​

​weak_ptr​

​的lock成員,該成員是weak_ptr用來擷取shared_ptr的指針。

這樣當shared_ptr在多線程過程中被銷毀時​

​shared_ptr::reset​

​​,weak_ptr的lock成員仍然能夠保留shared_ptr的成員,直到目前shared_ptr正常終止,否則會出現非常危險的記憶體洩漏。關于lock()成員的作用如下描述:

如下代碼

shared_ptr<int> p(new int(5));
weak_ptr<int> q(p);

// some time later

if(int * r = q.get()) 
{
    // use *r
}      

多線程環境中shared_ptr是可以被多個線程共享,在r被使用之前p對象執行了​

​p.reset()​

​,正如我們上一篇文章中對shared_ptr的描述(​​C++智能指針:shared_ptr 實作詳解​​),reset成員會重置目前shared_ptr指針的指向。此時,目前線程如果繼續使用r指針,勢必會産生通路空位址的異常問題

根據以上問題,使用​

​weak_ptr::lock()​

​成員來解決該問題

shared_ptr<int> p(new int(5));
weak_ptr<int> q(p);

// some time later

//使用weak_ptr的lock成員來擷取shared_ptr的指針
if(shared_ptr<int> r = q.lock())
{
    // use *r
}      

關于lock()成員簡單說明一下,lock成員擷取到的shared_ptr p指針建立一個臨時對象(我們weak_ptr弱引用的展現),這個臨時對象同樣指向p,即使p執了reset這樣的delete引用的操作,弱引用對象仍然持有改智能指針的位址,直到r指針的生命周期結束才會釋放。不得不佩服C++語言設計者的腦洞,為了保持C++對記憶體操作的自由,即使耗費再大的精力也要實作這一目标,工匠精神才讓今天的C++越來越被底層程式員喜歡。

原理實作

源碼檔案​

​/boost/smart_ptr/weak_ptr.hpp​

template<class T> class weak_ptr
{
private:

    // Borland 5.5.1 specific workarounds
    typedef weak_ptr<T> this_type;      
  • ​constructor​

    ​ 構造函數
//預設構造函數
weak_ptr() BOOST_NOEXCEPT : px(0), pn() // never throws in 1.30+
{
}

//拷貝構造函數
weak_ptr( weak_ptr const & r ) BOOST_NOEXCEPT : px( r.px ), pn( r.pn )
{
}      
  • ​destructor​

    ​​析構函數,這裡weak_ptr使用的是預設析構函數,一般使用​

    ​expired​

    ​傳回空對象或者user_count()為0的情況則輔助shared_ptr釋放引用
  • ​operator=​

1. 
 weak_ptr & operator=( weak_ptr && r ) BOOST_NOEXCEPT
{
    this_type( static_cast< weak_ptr && >( r ) ).swap( *this );
    return *this;
}

//2.這裡會更加安全,使用lock成員擷取Y類型的指針
template<class Y>
weak_ptr & operator=( weak_ptr<Y> const & r ) BOOST_NOEXCEPT
{
    boost::detail::sp_assert_convertible< Y, T >();

    px = r.lock().get();
    pn = r.pn;

    return *this;
}

3.
template<class Y>
weak_ptr & operator=( shared_ptr<Y> const & r ) BOOST_NOEXCEPT
{
    boost::detail::sp_assert_convertible< Y, T >();

    px = r.px;
    pn = r.pn;

    return *this;
}      
  • ​weak_ptr::swap​

    ​成員,交換兩個weak_ptr所指内容以及位址
void swap(this_type & other) BOOST_NOEXCEPT
{
    //先交換位址,再交換内容
    std::swap(px, other.px);
    pn.swap(other.pn);
}      
  • ​weak_ptr::reset​

    ​成員,·重新指定對象位址和内容,就像是重新使用預設構造函數進行了初始化
void reset() BOOST_NOEXCEPT // never throws in 1.30+
{
    //使用預設構造函數構造的對象和目前對象進行swap操作
    this_type().swap(*this);
}      
  • ​weak_ptr::use_count​

    ​成員,擷取shared_ptr對象被引用的次數。如果為空,則傳回0
long use_count() const BOOST_NOEXCEPT
{
    return pn.use_count();
}      
  • ​weak_ptr::expired​

    ​成員,當根據use_count==0來傳回bool,其傳回為true的時候,使用lock擷取weak_ptr的指針隻能擷取到空指針
bool expired() const BOOST_NOEXCEPT
{
    return pn.use_count() == 0;
}      
  • ​weak_ptr::lock​

    ​成員,會向weak_ptr對象傳回一個shared_ptr。正如我們之前在weak_ptr作用中所描述的,防止多線程通路時的shared_ptr記憶體洩漏。此時weak_ptr對象擷取到的指針為臨時指針,會指向shared_ptr對象之前所指向的位址。
shared_ptr<T> lock() const BOOST_NOEXCEPT
{
    return shared_ptr<T>( *this, boost::detail::sp_nothrow_tag() );
}      

函數成員使用

  • 構造函數
#include <iostream>
#include <memory>

struct C {int* data;};

int main () {
  std::shared_ptr<int> sp (new int);

  std::weak_ptr<int> wp1;
  std::weak_ptr<int> wp2 (wp1);
  std::weak_ptr<int> wp3 (sp);

  std::cout << "use_count:\n";
  
  //weak_ptr對象如果為經shared_ptr初始化,
  //它是沒有引用計數的,是以這裡wp1和wp2引用計數都為0
  //隻有wp3經過了shared_ptr初始化,它的引用計數才為1
  
  std::cout << "wp1: " << wp1.use_count() << '\n';
  std::cout << "wp2: " << wp2.use_count() << '\n';
  std::cout << "wp3: " << wp3.use_count() << '\n';
  
  return 0;
}      
  • 輸出如下:
use_count:
wp1: 0
wp2: 0
wp3: 1      
  • ​weak_ptr::operator=​

    ​指派運算符
// weak_ptr::operator= example
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> sp1,sp2;
  std::weak_ptr<int> wp;
                                       // sharing group:
                                       // --------------
  sp1 = std::make_shared<int> (10);    // sp1
  wp = sp1;                            // sp1, wp

  sp2 = wp.lock();                     // sp1, wp, sp2
  sp1.reset();                         //      wp, sp2

  //通過lock保留的臨時指針,重新擷取到了shared_ptr共享的位址
  sp1 = wp.lock();                     // sp1, wp, sp2

  std::cout << "*sp1: " << *sp1 << '\n';
  std::cout << "*sp2: " << *sp2 << '\n';

  return 0;
}      
  • 輸出如下
*sp1: 10
*sp2: 10      
  • ​std::weak_ptr::swap​

    ​交換指針位址以及對應的内容
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> sp1 (new int(10));
  std::shared_ptr<int> sp2 (new int(20));

  std::weak_ptr<int> wp1(sp1);
  std::weak_ptr<int> wp2(sp2);
  
  std::cout << "wp1 -> " << *wp1.lock() << " " << wp1.lock() << '\n';
  std::cout << "wp2 -> " << *wp2.lock() << " " << wp2.lock() << '\n';

  //這裡的swap僅僅是交換wp2的各自的指向位址
  //并不會直接導緻對應智能指針原始指針的位址交換
  //根據輸出,是以很明顯,交換完成之後weak_ptr對象指向發生了變化,但是并未導緻share_ptr指針的指向變化
  wp1.swap(wp2);

  std::cout << "sp1 -> " << *sp1 << " " << sp1 << '\n';
  std::cout << "sp2 -> " << *sp2 << " " << sp2 << '\n';
  std::cout << "wp1 -> " << *wp1.lock() << " " << wp1.lock() << '\n';
  std::cout << "wp2 -> " << *wp2.lock() << " " << wp2.lock() << '\n';

  return 0;
}      
  • 輸出如下:
wp1 -> 10 0x11daf90
wp2 -> 20 0x11dafd0
sp1 -> 10 0x11daf90
sp2 -> 20 0x11dafd0
wp1 -> 20 0x11dafd0
wp2 -> 10 0x11daf90      
  • ​std::weak_ptr::reset​

    ​成員,執行之後weak_ptr對象就像是重新執行了預設構造函數,又變成了一個空的對象
// weak_ptr::reset example
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> sp (new int(10));

  std::weak_ptr<int> wp(sp);

  std::cout << "1. wp " << (wp.expired()?"is":"is not") << " expired\n";
  std::cout << "4. wp " << wp.use_count() << " *wp " << *wp.lock() << '\n';

  wp.reset();

  std::cout << "2. wp " << (wp.expired()?"is":"is not") << " expired\n";
  std::cout << "3. sp " << sp.use_count() << " *sp " << *sp << '\n';

  return 0;
}      
  • 輸出如下
1. wp is not expired
2. wp 2 *wp 10
3. wp is expired
4. sp 1 *sp 10      

總結

  1. shared_ptr對象能夠初始化實際指向一個位址内容而weak_ptr對象沒辦法直接初始化一個具體位址,它的對象需要由shared_ptr去初始化
  2. weak_ptr不會影響shared_ptr的引用計數,因為它是一個弱引用,隻是一個臨時引用指向shared_ptr。即使用shared_ptr對象初始化weak_ptr不會導緻shared_ptr引用計數增加。依此特性可以解決shared_ptr的循環引用問題。
  3. weak_ptr沒有解引用*和擷取指針->運算符,它隻能通過lock成員函數去擷取對應的shared_ptr智能指針對象,進而擷取對應的位址和内容。