1. 程式人生 > >標準庫中的智慧指標

標準庫中的智慧指標

  智慧指標的出現是為了能夠更加方便的解決動態記憶體的管理問題。注:曾經記得有本書上說可以通過vector來實現動態分配的記憶體的自動管理,但是經過試驗,在gcc4.8.5下是不行的。這個是容易理解的,vector是個模板,它不能辨別傳入的資料型別是否是指標,從而也不能進行自動的釋放記憶體操作。如果對非new出的物件進行delete操作,反而還會引起一些不必要的問題。

  C++11標準庫為了能夠使程式設計師能夠更安全的使用動態記憶體,提供了兩種智慧指標型別來管理動態物件。

shared_ptr類

  智慧指標也是模板,所以當我們建立一個智慧指標時也需要提供額外的資訊——指標可以指向的型別。例如:

  shared_ptr<string> sp1;  //sp1是這個智慧指標的名字,尖括號裡的string表示這個智慧指標指向的是一個string型別的變數(記住,雖然這裡沒有我們熟悉的  * ,但sp1是一個指標),預設初始化的智慧指標中儲存者一個空指標。

  智慧指標的使用和普通指標一致,解引用一個指標返回它所指向的物件,如果在一個條件判斷中使用智慧指標效果就是檢測它是否為空。

shared_ptr<string> sp1;
if(sp1)
{
    std::cout << "判斷智慧指標sp1是否為空指標" << std::endl;
}
if(sp1!= nullptr ) { if(sp1->empty()) {
     *sp1 = "Hi Smart Pointer!";    //給該智慧指標指向的字串賦值 std::cout
<< "判斷該智慧指標指向的字串是否為空!" << std::endl; } }

  shared_ptr和unique_ptr都支援的操作:

shared_ptr<T> sp / unique_ptr<T> up 宣告一個智慧指標,預設初始化時,該智慧指標中儲存著一個空指標
p  將p作為一個條件判斷,若p指向一個物件,則為true(和普通指標一樣,就是判斷指標本身是否為nullptr)
*p 解引用p,獲得它指向的物件。和普通指標的*p語義一樣。
p->mem 等價於(*p).mem
p.get() 返回p中儲存的指標,但是這個方法使用起來要小心,如果智慧指標釋放了其物件,返回的指標所指向的物件也就消失了
swap(p.q) 交換p和q中的指標
p.swap(q) 和swap(p.q)的作用是一樣的

  shared_ptr獨有的操作

make_shared<T>(args) args:這種表達在C++primer裡就是引數列表的意思。make_shared<T>(args)返回一個shared_ptr指標,指向一個動態分配的型別為T的物件,使用args來初始化此物件(相當於是給建構函式傳參)
shared_ptr<T>p(q) p是shared_ptr q的拷貝,此操作會遞增q中的引用計數器。q中的指標必須要能轉換成T*
p = q p和q都是shared_ptr,所儲存的指標必須能夠相互轉換,此操作會遞減p的引用計數,遞增q的引用計數;若p的一用計數變為0,則將其管理的原記憶體釋放
p.unique()  若p.use_count()為1則返回true;否則返回false
p.use_count() 返回與p共享物件的智慧指標的數量;可能很慢,主要用於除錯。

  shared_ptr和unique_ptr的區別:shared_ptr允許多個指標指向同一個物件,unique_ptr則“獨佔”所指向的物件。weak_ptr是一種伴隨類,是一種弱引用,指向shared_ptr所管理的物件。這三種類型都定義在標頭檔案memory中。

  示例:

#include <iostream>
#include <string>
#include <memory>

using namespace std;
int main(int argc,char *argv[])
{
    shared_ptr<int> sp1;
    if(nullptr == sp1)
    {
        std::cout << "shared_ptr預設初始化時,其內儲存著一個空指標" << std::endl;
    }
    if(sp1.use_count() == 0)
    {
        std::cout << "預設初始化時,這個智慧指標的引用計數值是:" << sp1.use_count() << std::endl;
    }

    shared_ptr<int> sp2 = make_shared<int>(42);                 //宣告一個指向int型別的智慧指標,並將其初始化為42,sp2這個智慧指標的引用計數值應該是1
    std::cout << "這個智慧指標內儲存的值是:" << *sp2 << ",它的引用計數器的值是:" << sp2.use_count() << std::endl;
    sp1 = sp2;    //無論何時,拷貝一個shared_ptr,它的引用計數器都會遞增,例如將用一個shared_ptr初始化另外一個shared_ptr,或者將它作為引數傳遞給一個函式,以及作為函式的返回值
                  //當我們給shared_ptr賦予了一個新值或者是shared_ptr被銷燬,它所關聯的引用計數器就會遞減,一旦一個shared_ptr的計數器變為0,它就會自動釋放自己所管理的物件.
    std::cout << "sp1的引用計數值是:" << sp1.use_count() << ",sp2的引用計數值是:" << sp2.use_count() << std::endl;
    shared_ptr<int> sp3 = make_shared<int>(32);
    sp2 = sp3;  //給sp2賦予一個新的值,這裡sp2裡關聯到原來指向的那個物件的引用計數器值會遞減,但同時,它又被指向了新的物件,這個關聯到新物件的引用計數值又會增加。
    std::cout << "sp2關聯的計數器值是:" << sp2.use_count() << ",sp3關聯的引用計數器值是:" << sp3.use_count() << "sp1關聯的應用計數器值是:" << sp1.use_count() << std::endl;
    //當指向一個物件的最後一個shared_ptr被銷燬時,shared_ptr類會自動銷燬此物件,通過解構函式完成此工作。shared_ptr的解構函式會遞減它所指向的物件的引用計數。如果引用計數變為0,則shared_ptr
    //的解構函式就會銷燬物件,並釋放它佔用的記憶體,

    {
        shared_ptr<int> sp4 = make_shared<int>(3);
        sp1 = sp4;
    }
    if(sp1!= nullptr)
    {
        std::cout << "上面的sp4雖然被銷燬了,但是由於有sp1=sp4這個賦值操作,導致指向sp4原本指向的物件的指標對於0個,那麼它申請的記憶體就不會隨著sp4的析構而銷燬。" << "sp1當前所儲存的值是: " << *sp1 << std::endl;
    }
    //shared_ptr在無用之後仍然保留的一種情況是,你將shared_ptr存放在一個容器中,隨後重排了容器,從而不再需要某些元素,在這種情況下,你應該確保erase刪除那些不需要的shared_ptr元素。
    return (0);
}

  分配動態記憶體的幾個理由:

  1.不知道自己想要多大的空間;

  2.不知道物件的型別是什麼,(void *)

  3.需要多個物件共享資料

  程式非自由空間被耗盡的情況還是有可能發生的,一旦一個程式用光了它所有可用的記憶體,new表示式就會失敗。預設情況下,如果new不能分配所要求的的記憶體空間,它會丟擲一個型別為bad_alloc的異常。可以改變使用new的方式來阻止它丟擲異常。

int *p2 = new (nothrow) int; //此時如果分配記憶體失敗,那麼new就會返回一個空指標,這種形式的new稱為定位new,定位new表示式允許我們向new傳遞額外引數。這個例子中傳入的是一個由標準庫定義的名為nothrow的物件,意思就是不丟擲異常。這個都定義在標頭檔案new中

  new運算子包含兩個動作:分配記憶體,構造物件。

  delete運算子負責釋放new運算子分配的記憶體,把它還給作業系統,delete也包含兩個動作,銷燬給定的指標指向的物件,釋放對應的記憶體。

  傳給delete的指標必須是動態分配的記憶體或者是一個空指標,釋放一塊並非new分配的記憶體,或者將相同的指標釋放多次,其行為是未定義的。

  動態物件的生存期直到被釋放時為止。(new/delete  new出來的指標被稱為內建指標)

動態記憶體的管理裡容易出現的幾個問題:

1.忘記delete記憶體。忘記釋放動態記憶體常會導致“記憶體洩漏”問題,因為這種記憶體永遠不可能歸還給自由空間了。

2.使用已經釋放掉的物件。通過在釋放記憶體後將指標置為空,有時可以檢測出這種錯誤。如果不置為空,那麼會產生空懸指標(野指標)。這會造成災難性的後果。

3.同一塊記憶體釋放兩次,當有兩個指標指向相同的動態分配物件時,可能發生這種錯誤。如果對其中一個指標進行了delete操作,物件的記憶體就被歸還給自由空間了,如果隨後又對第二個指標進行delete操作,自由空間就可能被破壞。