天天看點

動态記憶體與智能指針1 shared_ptr 類2 unique_ptr類3 weak_ptr類

在C++中,動态記憶體的管理通過一對運算符來完成的:new, 在動态記憶體中為對象配置設定空間并傳回一個指向該對象的指針,我們可以選擇對對象進行初始化;delete,接受一個對象的指針,銷毀該對象,并釋放與之關聯的記憶體。

動态記憶體的使用很容易出現問題,我們假設這樣一種應用環境。假設程式通過工廠函數(factory function)供應我們特定的Investment對象。

class Investment{ ... };         // 投資類型,root class

Investment* createInvestment();  // 傳回指針,指向Investment對象   

void f( )
{
    Investment *pInv = createInvestment( );   // 調用factory函數
    ...
    delete pInv;                              // 釋放pInv所指對象
}
           

這看起來妥當,但若幹情況下f可能無法删除它得自createInvestment 的投資對象——或許因為”…”區域内的一個過早的return語句。如果這樣一個return語句被執行起來,控制流不會觸及delete語句。類似情況發生在對createInvestment的使用及delete動作位于某循環内,而該循環由于某個continue或goto語句過早退出。最後一種可能是”…”區域内的語句抛出異常,果真如此控制流再次不會臨幸delete,無論delete如何被略過去,我們洩漏的不隻是内含投資對象的那塊記憶體,還包括哪些投資對象所儲存的任何資源。

謹慎地編寫程式可以防止這一類錯誤,但為了更容易地使用動态記憶體,并防止此類錯誤的發生,C++新的标準庫提出了兩種智能指針(smart pointer)類型來管理動态對象。智能指針的行為類似正常指針,重要的差別是它負責自動釋放所指向的對象。新的标準庫提供的兩種智能指針的差別在于管理底層指針的方式:shared_ptr允許多個指針指向同一個對象;unique_ptr 則”獨占“所指向的對象。标準庫還定義了一個名為 weak_ptr 的伴随類,它是一種弱引用,指向 shared_ptr所管理的對象。這三種類型都在memory頭檔案中。

1 shared_ptr 類

智能指針是一個模闆。

shared_ptr<string> p1;        // 可以指向string
    shared_ptr<list<int>> p2;     // 可以指向int的list
           

預設初始化的智能指針中儲存着一個空指針。

表1.1 shared_ptr 和 unique_ptr 都支援的操作

操作 解釋

share_ptr<T> sp

空智能指針

unique_ptr<T> up

空智能指針

p

将p用作條件判斷,若p指向一個對象,則為true

*p

解引用p,獲得它指向的對象

p->mem

等價于

(*p).mem

p.get()

傳回p中儲存的指針。

swap(p, q); p.swap(q)

交換p和q中的指針

表1.2 shared_ptr 獨有的操作

操作 解釋

make_shared<T>(args)

傳回一個

shared_ptr

,指向一個動态配置設定的類型為T的對象。使用args初始化對象

shared_ptr<T> p(q)

p是

shared_ptr

的拷貝

p = q

p和q都是shared_ptr,所儲存的指針必須能互相轉換。此操作會遞減p的引用次數,遞增q的引用次數。若p的引用次數為0,則将其管理的記憶體釋放

p.unique()

p.use_count()

為1,傳回true,否則傳回false

p.use_count()

傳回p共享對象的智能指針數量,可能很慢,主要用于調試

1.1 make_shared函數

最安全的配置設定和使用動态記憶體的方法是調用一個名為make_shared的标準庫函數。此函數在動态記憶體中配置設定一個對象并初始化它,傳回指向對象的

shared_ptr

。當要用make_shared時, 必須指定想要建立的對象類型,其用法如下:

// 指向一個值為42的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>();
// p4指向一個值為"999"的string
shared_ptr<string> p4 = make_shared<string>("999");
// p5指向一個值初始化的int,即,值為0
shared_ptr<int> p5 = make_shared<int>();
           

1.2 shared_ptr的拷貝和指派

當進行拷貝或指派時,每個shared_ptr都會記錄有多少個其他shared_ptr指向相同對象;

auto p = make_shared<int> ();  // p指向的對象隻有一個引用者
auto q(p);  // p和q指向相同對象,此對象有兩個引用
           

我們可以認為每個shared_ptr都有一個關聯的計數器,通常稱其為引用計數。無論何時我們拷貝一個shared_ptr,計數器都會遞增。當我們給一個shared_ptr賦予一個新值或是shared_ptr被銷毀時,計數器就會遞減。

一旦一個shared_ptr的計數器變為0,它就會自動釋放自己所管理的對象:

auto r = make_shared<int> ();  // r指向的int隻有一個引用者
r = q; // 給r指派,令它指向另一個位址
       // 遞增q指向的對象的引用計數
       // 遞減r原來指向的對象的引用計數
       // r原來指向的對象已沒有引用者,會自動釋放
           

此例中我們配置設定了一個int,将其指針儲存在r中。接下來,我們将一個新值賦予r。在此情況下,r是唯一指向此int的shared_ptr,在把q賦給r的過程中,此int被自動釋放。

1.3 shared_ptr自動銷毀所管理的對象

當指向一個對象的最後一個shared_ptr被銷毀時,shared_ptr類會自動銷毀此對象。它是通過另一個特殊的成員函數——析構函數完成銷毀工作的。

shared_ptr的析構函數會遞減它所指向的對象的引用計數。如果引用計數變為0,shared_ptr的析構函數就會銷毀對象,并釋放它所占用的記憶體。

當動态對象不再被使用時,shared_ptr類會自動釋放動态對象,這一特性使得動态記憶體的使用變得非常容易。

2 unique_ptr類

一個unique_ptr”擁有”它所指向的對象。與shared_ptr不同,某個時刻隻能有一個unique_ptr指向一個給定對象。當unique_ptr被銷毀時,它所指向的對象也被銷毀。表2.1 列出了unique_ptr特有的操作。與shared_ptr相同的操作在表1.1中。

表2.1 unique_ptr 獨有的操作

操作 解釋

unique_ptr<T> u1

空unique_ptr,可以指向類型為T的對象。u1會使用delete來釋放它的指針

unique_ptr<T, D> u2

空unique_ptr,可以指向類型為T的對象。u2會使用一個類型為D的可調用對象來釋放它的指針

unique_ptr<T, D> u(d)

空unique_ptr,指向類型為T的對象,用類型為D的對象d代替delete

u = nullptr

釋放u指向的對象,将u置為空

u.release()

u放棄對指針的控制權,傳回指針,并将u置為空

u.reset()

釋放u指向的對象

u.reset(q)

如果提供了内置指針q,令u指向這個對象;否則将u置為空

u.reset(nullptr)

與shared_ptr不同,沒有類似的make_shared的标準庫函數傳回一個unique_ptr。當我們定義一個unique_ptr時,需要将其綁定到一個new傳回的指針上。類似shared_ptr,初始化unique_ptr必須采用直接初始化形式:

unique_ptr<double> p1;               // 可以指向一個double的unique_ptr
unique_ptr<int> p2(new int());     // p2指向一個值為42的int
           

由于一個unique_ptr擁有它指向的對象,是以它不支援普通的拷貝或指派操作:

unique_ptr<string> p1(new string("hello"));
unique_ptr<string> p2(p1);    // 錯誤:unique_ptr不支援拷貝
unique_ptr<string> p3;
p3 = p2;                      // 錯誤:unique_ptr不支援指派
           

雖然不支援拷貝或指派unique_ptr,但可以通過調用release或reset将指針的所有權從一個(非cons)unique_ptr轉移給另一個unique_ptr:

unique_ptr<string> p2(p1.release());     // release将p1置為空
unique_ptr<string> p3(new string("world"));
// 将所有權從p3轉移到p2
p2.reset(p3.release() );    // reset釋放了p2原來指向的記憶體
           

2.1傳遞unique_ptr參數和傳回unique_ptr

不能拷貝unique_ptr的規則有一個例外:我們可以拷貝或指派一個将要銷毀的unique_ptr。最常見的例子是從函數傳回一個unique_ptr:

unique_ptr<int> clone(int p)  {
    //  正确:從一個int*建立一個unique_ptr<int>
    return unique_ptr<int> ret(new int(p));
    }
           

還可以傳回一個局部對象的拷貝:

unique_ptr<int> clone(int p)  {
    unique_ptr<int> ret(new int(p));
    // ...
    return ret;
    }
           

在這兩個例子中,編譯器都知道要傳回的對象将要被銷毀,在此情況下,編譯器執行一種特殊的拷貝。

unique_ptr向後相容auto_ptr

标準庫的較早版本包含了一個名為auto_ptr的類,它具有unique_ptr的部分特性,但不是全部。特别是,我們不能在容器中儲存auto_ptr,也不能從函數傳回auto_ptr。雖然auto_ptr仍是标準的一部分,但編寫程式是應該用unique_ptr。

3 weak_ptr類

weak_ptr是一種不控制所指向對象生存期的智能指針,它指向由一個shared_ptr管理的對象。将一個weak_ptr綁定到一個shared_ptr不會改變shared_ptr的引用計數。一旦最後一個指向對象的shared_ptr被銷毀,對象就會被釋放。即使有weak_ptr指向對象,對象也還是會被釋放,是以,weak_ptr的名字抓住了這種智能指針”弱“共享對象的特點。

auto p = make_shared<int>();
weak_ptr<int> wp(p);      // wp弱共享p;p的引用計數為改變
           
本文轉自C++primer(第五版)。

繼續閱讀