C++ TR1 智能指針shared_ptr的使用(轉)


在 c++ 98 里面只有一種智能指針,就是 std::auto_ptr,因為具有唯一所有權的特征,所以限制了它的使用范圍,比如你無法在容器中使用它。而我們知道 stl 容器是值語義的,如果不能用智能指針管理的話,只有兩種辦法來使用。

一種是類似這樣:

std::vector<std::string> names;
names.push_back("cyberscorpio");
std::string name("news818");
names.push_back(name);

每次向容器中添加內容的時候,實際上產生了該內容的另一份拷貝,對於簡單的內容(或對象)來說,問題不大,但如果是很復雜的內容,很大的對象,調用拷貝構造函數的成本會比較高,同時亦不利於統一維護。

另一種做法就是在容器中存指針,這樣當然就避免了上面的問題,但是,這需要你在容器對象銷毀的時候,顯式的釋放容器中的每一個元素。略有些不方便,但還可以接受。

更大的問題在於,當程序比較復雜的時候,對象的生命周期管理對程序員來說,會是一個比較頭疼的事情,比如你有一個指針(對象),在程序的很多地方都用到它,如果你在某個地方釋放了它而沒有通知它的其他使用者的話,就會造成非法的內存訪問,從而程序崩潰。

shared_ptr 通過指針的引用計數,很好的解決了這個問題,和 COM 組件生存期管理機制類似,只有當引用計數為 0 的時候,才會釋放這個對象。而 shared_ptr 不需要程序員手工調用 AddRef 和 Release 函數,進一步減小了出錯的可能性。

但是,引用計數有一個麻煩,它不能解決所謂循環引用的問題,舉個例子,有對象 A 和 B,A 和 B 中,各有一個智能指針指向對方。

#include <stdio.h>
#include <memory>
class A;
class B;
typedef std::tr1::shared_ptr<a> APtr;
typedef std::tr1::shared_ptr<b> BPtr; 

class A {
public:
  BPtr b;
  ~A () {
    printf ("A released\n");
  }
};

class B {
public:
  APtr a;
  ~B () {
    printf ("B released\n");
  }
};

int main () {
  APtr a(new A());
  BPtr b(new B());

  a->b = b; // 1
  b->a = a; // 2

  return 0;

}
我們編譯運行這段程序,會發現 A 和 B 的析構函數都沒有被調用,因為在只能指針 a 的生命周期結束的時候,它手中對 A 對象的引用計數還剩下 1 (即 b 手中的引用),所以對象 A 不會被釋放。而指針 b 生命結束的時候哦,它手中 B 對象的引用也還有 1,導致沒有對象被釋放。

上面的例子里面,注釋為 1 和 2 的兩句,任意去掉一句,就打破了這個循環的引用,從而 A 和 B 都能正確的釋放。那么,假如我真的需要 A 和 B 之間互相引用,難道就沒有別的辦法了嗎?辦法是有的,就是使用 std::tr1::weak_ptr。weak_ptr,顧名思義,是一個 “弱” 一點的智能指針,它不會增加引用計數,當你需要使用這個對象的時候,可以從 weak_ptr 臨時生出一個 shared_ptr 來 (通過 lock 函數),這個臨時的 shared_ptr 生命結束以后,就會把引用計數減小 1,這樣就不會出現互相死鎖的情況了。

#include <stdio.h>
#include <memory> 
class A; 
class B; 

typedef std::tr1::shared_ptr<A> APtr; 
typedef std::tr1::shared_ptr<B> BPtr; 
typedef std::tr1::weak_ptr<A> AWeakPtr; 
typedef std::tr1::weak_ptr<B> BWeakPtr; 

class A { 
public: 
  BWeakPtr b; // 注意這里 
  ~A () { 
    printf ("A released\n"); 
  } 

};

class B { 
public: 
  AWeakPtr a; // 注意這里 
  ~B () { 
    printf ("B released\n"); 
  }

  void output () { 
    printf ("I'm B\n"); 
  } 
};

int main () { 
  APtr a(new A()); 
  BPtr b(new B());

  a->b = b; 
  b->a = a;

  BPtr b2(a->b.lock()); 
  b2->output();

  return 0; 
} 
編譯運行,我們會欣喜的看到,A 和 B 都正確的被釋放了。
I'm B
B released
A released

所以,在使用 shared_ptr 第一個要注意的,就是可能會引起循環引用的地方,使用 weak_ptr

還有一個問題也很常見,當使用了 shared_ptr 的時候,我們可能需要在所有的地方都使用它,否則就不容易達到管理生存期的目的了。但有的時候,我們手頭上只有對象的原始指針,比如在對象的函數內部,我們只有 this。這就迫切的需要一個功能:如何從對象的裸指針中,生成我們需要的 shared_ptr。

有人可能會覺得這個簡單,shared_ptr<T> a(this); 不就行了么?很遺憾的告訴你,這樣不行,會出問題。為什么呢?因為這里的 a,手中對 this 的引用計數只有 1,它無法知道其他地方智能指針對 this 這個指針(就是這個對象)的引用情況,因此當 a 的生命周期結束(比如函數返回)的時候,this 就會被它毫不留情的釋放掉,其他地方的相關智能指針,手中拿着的該對象指針已經變成非法。

那么怎樣解決呢?也很簡單,使用 std::tr1::enable_shared_from_this 作為基類。比如:

class A : public std::tr1::enable_shared_from_this<A> 
{ 
public: 
  std::tr1::shared_ptr<A> getSharedPtr() { 
    return shared_from_this(); 
  } 
}; 

這樣就可以從 this 中生出具有統一引用計數的 shared_ptr 了。只有一個問題是需要注意的,就是 getSharedPtr 函數,或者說 shared_from_this 不能在對象 A 的構造函數中調用。因為 enable_shared_from_this 這個基類的內部,是通過一個對自己的 weak_ptr 的引用來返回 this 的 shared_ptr 的,而在對象的構造函數中,第一個 shared_ptr 尚未獲得對象的指針,所以 weak_ptr 是空的,直接導致 shared_from_this() 返回失敗。除此以外,shared_from_this 可以隨便使用。

 
 


免責聲明!

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



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