1. 程式人生 > >“智能指針”的成長

“智能指針”的成長

繼續 數值 就會 方式 包含 something 意思 結束 賦值

  智能指針是什麽,他的真面目就是一個類生成的對象,這個類中包含了基本的重載->、*等一些指針形態的用法,最主要的是這個類裏面有一個指針參數:所有智能指針類中都有一個explicit構造函數,以指針作為參數。比如auto_ptr的類模板原型為:

template<class T>

class auto_ptr{

  explicit auto_ptr(T* p = 0);

};

代碼中構造函數用了explicit關鍵字主要是防止隱式轉換,舉個例子:

auto_ptr<double> pd;

double *p_reg = new double;

pd = p_reg;       //error

pd = shared_ptr<double>(p_reg);    //error

shared_ptr<double> pshared = p_reg;     //error

shared_ptr<double> pshared(p_reg);

上面解釋了一下智能指針的本質是什麽,那它有什麽作用呢,看代碼看不出來,但仔細想,作為一個類對象,創建時需要構造,那待生命周期結束,也是需要自動析構的,所以就帶出了他的功能,就是為了保證其指向的New出來的對象,可以在生命周期結束時自動Delete,作為程序員我們都知道,Delete這個東西我們經常忘寫不說,也在很多return的地方忽略了寫Delete,這就導致了內存泄漏,很致命。

所以C++就提供了一種智能指針的模式去防止這種現象,現有最常用的智能指針,分別有 std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_arrayboost::weak_ptr這6個智能指針都有各自的使用範疇,話不多說,研究下就知道。

⑴ std::auto_ptr

  首先auto_ptr這個智能指針,屬於STL,同屬於STL的還有unique_ptr、shared_ptr等,感興趣的可以去查查,但本文就不進行介紹了。先來看一段代碼

class Simple {

public:

  Simple(int param = 0) {

    number = param;

    std::cout << "Simple: " << number << std::endl;

  }

  ~Simple() {

    std::cout << "~Simple: " << number << std::endl;

  }

  void PrintSomething() {

    std::cout << "PrintSomething: " << info_extend.c_str() << std::endl;

  }

  std::string info_extend;

  int number;

};

void fun()

{

  std::auto_ptr<Simple> ptr (new Simple(1));

  if(ptr.get())

  {

    ptr->PrintSomething();

    ptr.get()->info_extend = "Hello";

    (*prt)->info_extend = "World";

  }

}

上一段代碼運行的結果為:

  Simple:1

  PrintSometing:

  ~Simple:1

這是最標準的一種智能指針的使用方法,對於auto_ptr這種智能指針也是完全能駕馭的,但假如我們在代碼中加入另外幾句

std::auto_ptr<Simple> ptr2;

ptr2 = ptr;

pt2->PrintSomething();

pt1->PrintSomething(); // 崩潰

如果將上面的代碼加到程序中就會崩潰,為什麽呢?看源碼中,重載=時,進行了

reset(_Right.release());

繼續向裏執行release的代碼

技術分享

可以看到,這裏是將原本的指針控制權交給了別人,自身已經被置為NULL了。所以我們進行調用,不崩潰等什麽呢?

然後加另一端代碼:

if (ptr.get()) {

  ptr.release();

}

我們可以直接調用release函數,這就很尷尬了,直接返回值為當前指向的控制權,但沒有指針接收,直接丟棄了,這就更尷尬了,導致只進行了構造,沒有析構。。所以真要使用release函數的話,必須這樣

Simple* ptr2 = ptr.release();

delete ptr2;

這惡心的用法總給人一種多此一舉的做法,所以我release函數的直接使用是沒有任何意義的。對於這種手動的釋放內存,直接調用

ptr.reset();

所以我們對auto_ptr進行一個總結:

  1,盡量不要使用"="對智能指針進行賦值,如果使用,那先前對象就時一個空指針的。

  2,release函數單獨使用沒有任何意義,不會釋放對象,還有可能導致對象丟失。他的作用僅僅是返回所有權。

  3,不要把智能指針對象當做參數傳遞進入函數,因為也會調用release,導致調用完函數,回來對象已經被析構。

  上述3個僅僅是個人發現的,對於auto_ptr本身就有很多不合理的地方,雖然還有人使用,但具體項目中用的已經不多了,所以就在auto_ptr的基礎上,增加了各種其他的智能指針,用以修復這個使用不合理的地方。

⑵ boost::scoped_ptr

  scoped_ptr屬於boost庫,他與auto_ptr相比,也是避免了上述幾個問題,主要的原因就是scoped的設計為獨享所有權。主要的體現就是調用release函數和‘=’的時候是會報錯的。雖然表面上的確是避免了auto_ptr中出現的問題,但種感覺是一種逃避的方法,那就有了下面的另一種智能指針。

⑶ boost::shared_ptr

  scoped_ptr屬於boost庫,與上述的scoped_ptr相比,它添加了共享所有權的功能,也就是可以使用‘=’方法,關於做法,原理就是在類中使用了一個引用計數,當進行賦值的時候對該數字加1,寫一段代碼看看:

void TestSharedPtr(boost::shared_ptr<Simple> ptr)

{

  std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

}

boost::shared_ptr<Simple> ptr(new Simple(1));

std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

{

  boost::shared_ptr<Simple> ptr2 = ptr;

  std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

  TestSharedPtr(ptr);

  std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

}

std::cout << "TestSharedPtr2 UseCount: " << ptr.use_count() << std::endl;

這一段代碼運行下來的打印結果是:

TestSharedPtr2 UseCount: 1

TestSharedPtr2 UseCount: 2

TestSharedPtr2 UseCount: 3

TestSharedPtr2 UseCount: 2

TestSharedPtr2 UseCount: 1

大概解釋一下,在開始只有一個ptr時,打印出來的引用計數為1,當另外使用一個ptr2時,再次打印出來為2,意思就是當前共有兩個智能指針同時使用當前對象。進入函數後,又進行了一次拷貝構造到臨時變量,所以打印值又變成3,後續退出函數和退出ptr2的生命周期,引用計數的值各剪了1,當代碼執行完畢後,引用u計數值歸0,調用析構函數時使用Delete釋放空間。

boost::scoped_array

以數組的方式管理多個對象,但屬性與scoped_ptr完全一致,獨享所有權,而其使用方法也與數組沒有太大的區別:

boost::scoped_array<Simple> ptr_array(new Simple[2]);

ptr_array[0].PrintSomething();

boost::shared_array

以數組的方式管理多個對象,屬性與shared_ptr王權一致,共享所有權,內部使用引用計數

boost::weak_ptr

weak_ptr是一中比較特殊的智能指針,其應用的場景比較單一,主要是對shared_ptr進行觀察的一種對象,也就是說用weak_ptr類型的對象對shared_ptr類型的對象進行引用,是可以正常訪問,但不會改變後者的引用計數值,當後者被銷毀後,前者也就不能使用了。現在說說它的應用場景,如果子類中有一個shared_ptr類型的對象,那基類中就可以定義一個weak_ptr類型的對象,通過訪問基類的weak_ptr對象是否為空,就可以判斷子類是否對自己進行賦值。

本文有部分函數名和編碼方式是采用網上其他博主的風格(因為覺得這種講解方法特別清晰,先簡單介紹,然後上代碼直接理解),所以有些抄襲的嫌疑,但全部均本人理解,並重新編碼之後寫上去的,很多地方也是本人通過理解源碼,寫出來的部分東西,如果有誤,請及時溝通修正。

“智能指針”的成長