標簽(空格分隔): 學習筆記
傳統指針存在諸多的問題,比如指針所指向的對象的生命周期問題,掛起引用(dangling references),以及內存泄露(memory leaks). 如下代碼是一個傳統指針的使用過程
void Foo(){ int * ptr = new int[5]; //... //... delete[] ptr;}以上代碼將正常運行且內存被合理釋放,但是在我們編寫較為復雜的程序時,申請了過多的東戴指針和動態內存,往往會忘了delete,或者在錯誤的時間錯誤的地點提前delete掉了指針,這無疑是會導致程序崩潰或者內存泄露的。 智能指針是RAII(Resource Acquisition is initialization)用來動態的分配內存。它提供了普通指針的所有接口外加少數異常處理。在構造階段,它將分配內存,而在非其作用域內將自動釋放所占有的內存。 在C++98中,使用 auto_ptr來解決上述問題。
auto_ptr在所指對象作用域結束之后會自動調用其析構函數,這樣就不用手動的delete,看如下代碼:
#include <iostream>#include <memory>using namespace std;class autoPtrTest{public: //constructor function autoPtrTest(int a = 0) :m_a(a){} //deconstructor function ~autoPtrTest() { cout << "calling deconstructor" << endl; getchar(); }public: int m_a;};int main(){ //create an auto_ptr(p) to point a autoPtrTest class, and initialize with 5 auto_ptr<autoPtrTest> p(new autoPtrTest(5)); cout << p->m_a << endl; return 0;}運行結果如下圖所示: 當類型為auto_ptr的指針p指向的對象作用域結束后,自動調用了該類的析構函數. 但是,使用auto_ptr時,無可避免的會出現以下幾個問題,這是由auto_ptr自身的性質決定的 1)auto_ptr會傳遞它本身的ownership,當其被賦值給另一個auto_ptr對象。正如下述程序所示,一個auto_ptr對象傳遞給函數Fun()中的auto_ptr對象時,其ownership,或者說是smartness將不再返回給原auto_ptr所指向的p。
程序運行結果如下圖所示: 這是因為在調用函數Fun時,main中創建的指針p在函數中將自己的所有權給了p1,所以離開p1的作用域時,編譯器自動調用了析構函數,此時main中p為一個空指的野指針,所以程序報錯。 2)auto_ptr不能使用于數組對象。 這里的意思是不能使用于操作符new[] 3)auto_ptr不能使用于一些標準的容器庫。比如vector,list,map等等 C++11提出了新型的智能指針,并且都賦予了其相應的意圖。
shared_ptr設計的目的很簡單:多個共享指針可以指向同一個對象,而當最后一個共享指針在作用域范圍內結束時,內存才會被自動的釋放。
int main(){ // share_ptr 常規的創建過程 shared_ptr<int> sptr1(new int); // 使用make_shared 來加速創建過程 // shared_ptr 自動分配內存,并且保證引用計數 // 而make_shared則是按照這種方法來初始化 shared_ptr<int> sptr2 = make_shared<int>(100); // 可以通過use_count() 來查看引用計數 cout << "sptr2 referenced count: " << sptr2.use_count() << endl; shared_ptr<int> sptr3 = sptr2; cout << "sptr2 referenced count: " << sptr2.use_count() << endl; cout << "*sptr2 = " << *sptr2 << endl; getchar(); return 0;}上述代碼的運行結果為: 上述代碼創建了一個shared_ptr指針指向了一個裝著整型值且值為100的內存塊,并且引用計數為1,。當其他共享指針通過sptr1來創建時,引用計數將為2。
用戶可以顯式的調用函數,lambda表達式,函數對象來調用對于shared_ptr為數組對象的析構函數delete[]。 同時,shared_ptr為用戶提供了以下方便接口: shared_ptr提供解引用*, 以及->來普通指針的相關操作。同時,還提供了以下的接口: get()
: To get the resource associated with the shared_ptr. reset()
: To yield the ownership of the associated memory block. If this is the last shared_ptrowning the resource, then the resource is released automatically. unique
: To know whether the resource is managed by only this shared_ptr instance. Operator bool
: To check whether the shared_ptr owns a memory block or not. Can be used with an if condition. 但是,shared_ptr同樣也存在問題: 1)當一個內存塊與shared_ptr綁定相關,并且屬于不同組時,將會發生錯誤。所有的shared_ptr共享一個組的同一個共享引用。
以下表格給出了相應的引用計數:
pointer | count |
---|---|
shared_ptrstPR1(new int) | 1 |
shared_ptr sptr2 = sptr1 | 2 |
sptr3 = sptr2 | 3 |
when main ends->sptr3 goes out of scope | 2 |
sptr2 goes out of scope | 1 |
sptr1 goes out of scope | 0->the resources is released |
以上代碼運行正常,然而當運行以下代碼是:
int main(){ int *p = new int; shared_ptr<int>sptr1(p); shared_ptr<int>sptr2(p); return 0;}pointer | count |
---|---|
shared_ptrsptr1(p) | 1 |
shared_ptrsptr2(p) | 1 |
when main ends->sptr1 goes out of scope | 0 p is destroyed |
sptr2->goes out of scope | 0 and crash |
為了避免這種情況發生,最好不用從裸指針中建立共享指針。 2)問題2:另一個問題是,正如上述問題,如果從一個裸指針中創建一個共享指針,只有一個共享指針時,可以正常運行,但是當裸指針被釋放時,共享指針也會crash。 3)循環引用時,如果資源被非恰當釋放,也會出現問題。
#include <iostream>#include <memory>using namespace std;class B;class A{public: A() : m_sptrB(nullptr) {} ; ~A() { cout << "A is destroyed" << endl; } shared_ptr<B> m_sptrB;};class B{public: B() : m_sptrA(nullptr) {}; ~B() { cout << "B is destroyed" << endl; } shared_ptr<A> m_sptrA;};int main(){ shared_ptr<B> sptrB(new B); shared_ptr<A> sptrA(new A); sptrB->m_sptrA = sptrA; sptrA->m_sptrB = sptrB; return 0;}當類A包含了指向B的共享指針,而類B包含了指向A 的恭喜那個指針時,sptrA和SptrB相關的資源都將不會被釋放。結果如下圖:
pointer | count |
---|---|
shared_ptr sptrB(new B) | 1 |
shared_ptr sptrA(new A) | 1 |
sptrB->m_sptrA = sptrA | sptrA->2 |
sptrA->m_sptrB = sptrB | sptrB->2 |
main ends->sptrA goes out of scope | sptrA->1 |
main ends->sptrB gos out of scope | sptrB->1 |
一個弱指針,提供的是一種共享語義定義而不是擁有語義定義。這就意味著一個弱指針可以通過shared_ptr共享資源。所以要創建弱指針,必須是已經擁有資源但是是一個共享指針。
一個弱指針并不允許諸如普通指針所提供的*和->。因為他并不是資源的擁有者。
那么如何利用弱指針呢? **weak_ptr只能用于跟蹤一個共享的資源,但并不實際擁有,也不會阻礙資源的釋放。讀取共享資源前需要先執行lock,得到shared_ptr后才能進行訪問。 當兩個對象需要互相引用時,我們總希望其中一個對象擁有另一個對象的強引用,而另一個對象擁有自己的弱引用,如果兩個對象都是強引用,則容易引起循環引用,導致兩個對象都無法正確釋放。**
用weak_ptr作為一個類似share_ptr但卻能懸浮的指針 有一個矛盾,一個靈巧指針可以像shared_ptr 一樣方便,但又不參與管理被指對象的所有權。換句話說,需要一個像shared_ptr但又不影響對象引用計數的指針。這類指針會有一個shared_ptr沒有的問題:被指的對象有可能已經被銷毀。一個良好的靈巧指針應該能處理這種情況,通過跟蹤什么時候指針會懸浮,比如在被指對象不復存在的時候。這正是weak_ptr這類型靈巧指針所能做到的。
weak_ptr一般是通過shared_ptr來構造的。當使用shared_ptr來初始化weak_ptr時,weak_ptr就指向了相同的地方,但是不改變所指對象的引用計數。 從上圖可以看書,通過將一個weak_ptr賦值給另一個時會增加其弱引用計數。
如果弱引用指針所指向的資源,被其共享指針所釋放時,這時候弱指針將會過期。如何檢測一個弱指針是否指向一個合法的資源呢?有以下兩種途徑。
調用use_count()
來得到引用計數。注意這里返回的是強引用計數。 調用expired()
函數,這比調用use_count要快的多。 同時,我們可以通過對一個weak_ptr調用函數lock()來得到一個shared_ptr?;蛘咧苯訉σ粋€weak_ptr進行強制轉換。
以上方法將增加強引用計數 以下例子將展示如何使用weak_ptr解決循環引用問題
#include <iostream>#include <memory>using namespace std;class B;class A{public: A() : m_a(5) {} ; ~A() { cout << "A is destroyed" << endl; } void PrintSpB() ; weak_ptr<B> m_sptrB; int m_a;};class B {public: B() : m_b(10) {} ; ~B() { cout << "B is destroyed" << endl; } weak_ptr<A> m_sptrA; int m_b;};void A::PrintSpB(){ if( !m_sptrB.expired() ) cout << m_sptrB.lock()->m_b << endl;}int main(){ shared_ptr<B> sptrB(new B); shared_ptr<A> sptrA(new A); sptrB->m_sptrA = sptrA; sptrA->m_sptrB = sptrB; sptrA->PrintSpB(); return 0;}unique_ptr幾乎是易出錯的auto_ptr的另一種形式。unique_ptr遵循專用所有權語義。在任何時刻,資源只被唯一的一個unique_ptr所占有。當auto_ptr不在作用域范圍內時,資源就會被釋放。當一個資源被其他資源重寫時,如果先前的資源已經被釋放,這保證了相關的資源也會被釋放。 creation(創建) unique_ptr創建的過程和shared_ptr創建的過程大同小異,所不同的是創建的數組形式的對象。 unique_ptr提供了專用創建數組對象的析構調用delete[]而不是delete當其不在作用域范圍內。
unique_ptr<int[]>unptr(new int[5]);對于資源的擁有權(ownership)可以從一個unique_ptr通過另一個進行賦值來傳遞。
需要記住的是:unique_ptr并不提供復制機制copy semantics(包括復制賦值copy assignment以及復制構造函數copy construction)而是一種移動機制。
Interface(接口)
unique_ptr提供的接口與普通常規指針的接口非常相似,但是并不提供指針運算。 unique_ptr提供release()函數來進行yield the ownership。release()和reset()函數的區別在于,reset()會對資源進行銷毀。
參考文獻:
新聞熱點
疑難解答
圖片精選