智能指針的使用與陷阱


在包含指針的類中需要注意復制控制,復制指針時只復制指針中的地址,不會復制指針指向的對象。

 大多數c++類采用三種方法管理指針成員:

1)指針成員采用常規指針型行為。

 2)采用智能指針

3)采取值型行為

 常規指針缺陷:可能會出現懸垂指針。當一個指針復制到另一個指針,兩個指針指向同一個對象,當一個指針刪除對象時,另一個指針不知道,所以出現懸垂指針。即使使用默認合成復制構造函數也會出現,類本身無法避免。

 

 智能指針:加入了引用計數。引用計數跟蹤該類有多少對象共享同一指針。當引用計數為0 時,刪除對象。創建新類時,初始化指針並將引用計數置為1.進行復制時,增加相應引用計數值。賦值時,減少左操作數所指對象的引用計數的值(減至0,就刪除對象),增加右操作數所指對象的引用計數的值。最后,調用析構函數時,減少引用計數的值。如果減至0,就刪除對象。

值型類:復制時會new一個新的副本,指針所指向的對象是唯一的,每個類對象獨立管理。

為了管理具有指針成員的類,必須定義三個復制控制成員:復制構造函數,賦值操作符和析構函數。這些成員可以定義指針成員的指針型行為或者值型行為。

c++出現內存問題的地方一般:

1)緩沖區溢出

2)懸垂指針/野指針

3)重復釋放

4)內存泄漏

5)不配對的 new[]/delete

都可以通過智能指針很好的解決這些問題,比如:

1)->用vector/string或者自己寫的buffer類管理,自動增加緩沖區大小,用成員函數操作,不直接通過野指針操作

 2),3),4)->可以通過智能指針解決,只在對象析構的時候釋放一次內存,引用計數為0的時候才刪除指針,自動釋放

5)->使用vector,自己釋放內存

 

智能指針的陷阱:

這樣的一個引用計數型智能指針目的是為了防止資源泄漏,但是只需要一個很小巧的代碼就可以讓這樣的初衷化為烏有……

class A
{
public:
    A() {cout<<"A CON"<<endl;}
    ~A() {cout<<"A DES"<<endl;}
   void hold(CountedPtr<A> ptr)
    {
       m_ptr = ptr;
    }
private:
    CountedPtr<A> m_ptr;
};

void self_cir_area()
{
    CountedPtr<A> pA(new A());
    pA->hold(pA);
}

可以看見,一個對象A中有一個引用計數型智能指針,這樣的設計可能會很常見(指向自身類型的結構體——鏈表)

但是,當自身循環引用發生的時候會怎么樣呢? 下面就來看看這么兩句代碼

CountedPtr<A> pA(new A());

 這里我們新建一個資源,並且把這個資源的管理權移交給pA這個引用計數型智能指針對象管理。如此,pA中的引用計數被初始化為1。

pA->hold(pA);

 這里,我們把pA對象傳入給實例化的A對象中的引用計數型智能指針m_ptr,m_ptr執行這樣的一個成員函數:

//assignment (unshare old and share new value)
CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() {
           if (this != &p) {
                dispose();
                ptr = p.ptr;
                count = p.count;
                ++*count;
           }
           return *this;
}

 因為這里很明顯不是自身賦值,A中的m_ptr和pA不是同一個對象,所以進入if結構中調用下面的內容。dispose是用作清理,因為m_ptr並沒有指向任何東西,所以第一個函數並沒有真正的意義。

 然后

m_ptr.ptr = pA.ptr;
m_ptr.count = pA.count;
++(*m_ptr.count);  //(*pA.count)也被++

到此,pA的引用計數為2

 嗯,下面就pA這個對象理所當然的離開了作用域,調用其析構函數:

~CountedPtr () throw() {
     dispose();
}

噢,是一個轉向,調用其private成員函數dispose():

很簡單,將引用計數-1,由2變成1,不為0,所以if結構內的語句不被執行。

由此,我們又制造了一個完美的太空垃圾……

void dispose() {
      if (--*count == 0) {
           delete count;
           delete ptr;
      }
}

這樣的循環引用問題應該是在設計的過程中就應該避免的,如果用UML語言描述

 A中持有一個 引用計數型智能指針 的語義就是 這個 持有關系 是需要在 A消失的時候所持有的對象也隨之消失(這正是智能指針的作用,在脫離作用域自動清除其持有的資源)。如此就構成了 組合 關系。如果要表示 聚合 關系,即有 部分-整體 關系但是部分不隨整體的消失而消失,這就不是 智能指針 所表達的語義。

 

還有可能遇見的循環引用就是 A1 持有 A2, A2 持有 A1 的情況……

 這樣A1,A2中對雙方的引用計數都是2,當一方“銷毀”的時候,雙方的應用計數都變為1,實際上並沒有銷毀任何東西,制造了兩個完美無暇的太空垃圾~

 這里又引發出一個問題,這樣的資源泄漏問題實際上還是由程序員自身引起的。

 C++之所以是一個很容易出錯的語言,很大一部分在於其資源的管理權力全權交給了程序員。這樣的權力到底是造福了程序員還是迷惑了程序員呢?


免責聲明!

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



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