智能指針shared_ptr新特性shared_from_this及weak_ptr


 enable_shared_from_this是一個模板類,定義於頭文件<memory>,其原型為:

template< class T > class enable_shared_from_this;
       std::enable_shared_from_this 能讓一個對象(假設其名為 t ,且已被一個 std::shared_ptr 對象 pt 管理)安全地生成其他額外的 std::shared_ptr 實例(假設名為 pt1, pt2, ... ) ,它們與 pt 共享對象 t 的所有權。
       若一個類 T 繼承 std::enable_shared_from_this<T> ,則會為該類 T 提供成員函數: shared_from_this 。 當 T 類型對象 t 被一個為名為 pt 的 std::shared_ptr<T> 類對象管理時,調用 T::shared_from_this 成員函數,將會返回一個新的 std::shared_ptr<T> 對象,它與 pt 共享 t 的所有權。
一.使用場合

       當類A被share_ptr管理,且在類A的成員函數里需要把當前類對象作為參數傳給其他函數時,就需要傳遞一個指向自身的share_ptr。

1.為何不直接傳遞this指針

       使用智能指針的初衷就是為了方便資源管理,如果在某些地方使用智能指針,某些地方使用原始指針,很容易破壞智能指針的語義,從而產生各種錯誤。

2.可以直接傳遞share_ptr<this>么?

       答案是不能,因為這樣會造成2個非共享的share_ptr指向同一個對象,未增加引用計數導對象被析構兩次。例如:

#include <memory>
#include <iostream>

class Bad
{
public:
std::shared_ptr<Bad> getptr() {
return std::shared_ptr<Bad>(this);
}
~Bad() { std::cout << "Bad::~Bad() called" << std::endl; }
};

int main()
{
// 錯誤的示例,每個shared_ptr都認為自己是對象僅有的所有者
std::shared_ptr<Bad> bp1(new Bad());
std::shared_ptr<Bad> bp2 = bp1->getptr();
// 打印bp1和bp2的引用計數
std::cout << "bp1.use_count() = " << bp1.use_count() << std::endl;
std::cout << "bp2.use_count() = " << bp2.use_count() << std::endl;
} // Bad 對象將會被刪除兩次
輸出結果如下:

當然,一個對象被刪除兩次會導致崩潰。

 

正確的實現如下:

#include <memory>
#include <iostream>

struct Good : std::enable_shared_from_this<Good> // 注意:繼承
{
public:
std::shared_ptr<Good> getptr() {
return shared_from_this();
}
~Good() { std::cout << "Good::~Good() called" << std::endl; }
};

int main()
{
// 大括號用於限制作用域,這樣智能指針就能在system("pause")之前析構
{
std::shared_ptr<Good> gp1(new Good());
std::shared_ptr<Good> gp2 = gp1->getptr();
// 打印gp1和gp2的引用計數
std::cout << "gp1.use_count() = " << gp1.use_count() << std::endl;
std::cout << "gp2.use_count() = " << gp2.use_count() << std::endl;
}
system("pause");
}
輸出結果如下:


二.為何會出現這種使用場合

       因為在異步調用中,存在一個保活機制,異步函數執行的時間點我們是無法確定的,然而異步函數可能會使用到異步調用之前就存在的變量。為了保證該變量在異步函數執期間一直有效,我們可以傳遞一個指向自身的share_ptr給異步函數,這樣在異步函數執行期間share_ptr所管理的對象就不會析構,所使用的變量也會一直有效了(保活)。

C++智能指針 weak_ptr

  weak_ptr 是一種不控制對象生命周期的智能指針, 它指向一個 shared_ptr 管理的對象. 進行該對象的內存管理的是那個強引用的 shared_ptr. weak_ptr只是提供了對管理對象的一個訪問手段. 
  weak_ptr 設計的目的是為配合 shared_ptr 而引入的一種智能指針來協助 shared_ptr 工作, 它只可以從一個 shared_ptr 或另一個 weak_ptr 對象構造, 它的構造和析構不會引起引用記數的增加或減少. 
  定義在 memory 文件中(非memory.h), 命名空間為 std.

  weak_ptr 使用:

std::shared_ptr<int> sp(new int(10));
std::weak_ptr<int> wp(sp);
wp = sp;
printf("%d\n", wp.use_count()); // 1
wp.reset();
printf("%d\n", wp); // 0

// 檢查 weak_ptr 內部對象的合法性.
if (std::shared_ptr<int> sp = wp.lock())
{
}

成員函數

weak_ptr 沒有重載*和->但可以使用 lock 獲得一個可用的 shared_ptr 對象. 注意, weak_ptr 在使用前需要檢查合法性.

expired 用於檢測所管理的對象是否已經釋放, 如果已經釋放, 返回 true; 否則返回 false.
lock 用於獲取所管理的對象的強引用(shared_ptr). 如果 expired 為 true, 返回一個空的 shared_ptr; 否則返回一個 shared_ptr, 其內部對象指向與 weak_ptr 相同.
use_count 返回與 shared_ptr 共享的對象的引用計數.
reset 將 weak_ptr 置空.
weak_ptr 支持拷貝或賦值, 但不會影響對應的 shared_ptr 內部對象的計數.

使用 weak_ptr 解決 shared_ptr 因循環引有不能釋放資源的問題

使用 shared_ptr 時, shared_ptr 為強引用, 如果存在循環引用, 將導致內存泄露. 而 weak_ptr 為弱引用, 可以避免此問題, 其原理:
  對於弱引用來說, 當引用的對象活着的時候弱引用不一定存在. 僅僅是當它存在的時候的一個引用, 弱引用並不修改該對象的引用計數, 這意味這弱引用它並不對對象的內存進行管理.
  weak_ptr 在功能上類似於普通指針, 然而一個比較大的區別是, 弱引用能檢測到所管理的對象是否已經被釋放, 從而避免訪問非法內存。
注意: 雖然通過弱引用指針可以有效的解除循環引用, 但這種方式必須在程序員能預見會出現循環引用的情況下才能使用, 也可以是說這個僅僅是一種編譯期的解決方案, 如果程序在運行過程中出現了循環引用, 還是會造成內存泄漏.

例子

#include <iostream>
#include <boost/smart_ptr.hpp>
using namespace std;
using namespace boost;

class BB;
class AA
{
public:
    AA() { cout << "AA::AA() called" << endl; }
    ~AA() { cout << "AA::~AA() called" << endl; }
    shared_ptr<BB> m_bb_ptr;  //!
};

class BB
{
public:
    BB() { cout << "BB::BB() called" << endl; }
    ~BB() { cout << "BB::~BB() called" << endl; }
    shared_ptr<AA> m_aa_ptr; //!
};

int main()
{
    shared_ptr<AA> ptr_a (new AA);
    shared_ptr<BB> ptr_b ( new BB);
    cout << "ptr_a use_count: " << ptr_a.use_count() << endl;
    cout << "ptr_b use_count: " << ptr_b.use_count() << endl;
    //下面兩句導致了AA與BB的循環引用,結果就是AA和BB對象都不會析構
    ptr_a->m_bb_ptr = ptr_b;
    ptr_b->m_aa_ptr = ptr_a;
    cout << "ptr_a use_count: " << ptr_a.use_count() << endl;
    cout << "ptr_b use_count: " << ptr_b.use_count() << endl;
}

結果造成崩潰

可以看到由於AA和BB內部的shared_ptr各自保存了對方的一次引用,所以導致了ptr_a和ptr_b銷毀的時候都認為內部保存的指針計數沒有變成0,所以AA和BB的析構函數不會被調用。解決方法就是把一個shared_ptr替換成weak_ptr。

#include <iostream>
#include <boost/smart_ptr.hpp>
using namespace std;
using namespace boost;

class BB;
class AA
{
public:
    AA() { cout << "AA::AA() called" << endl; }
    ~AA() { cout << "AA::~AA() called" << endl; }
    weak_ptr<BB> m_bb_ptr;  //!
};

class BB
{
public:
    BB() { cout << "BB::BB() called" << endl; }
    ~BB() { cout << "BB::~BB() called" << endl; }
    shared_ptr<AA> m_aa_ptr; //!
};

int main()
{
    shared_ptr<AA> ptr_a (new AA);
    shared_ptr<BB> ptr_b ( new BB);
    cout << "ptr_a use_count: " << ptr_a.use_count() << endl;
    cout << "ptr_b use_count: " << ptr_b.use_count() << endl;
    //下面兩句導致了AA與BB的循環引用,結果就是AA和BB對象都不會析構
    ptr_a->m_bb_ptr = ptr_b;
    ptr_b->m_aa_ptr = ptr_a;
    cout << "ptr_a use_count: " << ptr_a.use_count() << endl;
    cout << "ptr_b use_count: " << ptr_b.use_count() << endl;
}

  

 因此,如果代碼比較復雜,我們在使用shared_ptr的時候其實很難知道是否會有循環引用,所以即使有weak_ptr來解決這個問題,我們也不太容易知道何時能用到,除非清楚非常清楚類之間的關系,所以,在我們編程的時候盡量使用一個指針控制生命周期(即使用shared_ptr),而多個指針控制訪問(即使用weak_ptr)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM