程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++11之 unique_ptr

C++11之 unique_ptr

編輯:C++入門知識

C++11之 unique_ptr


 

在C++11中加入了很多的新特性,unique_ptr一枝獨秀,對於動態分配的內存對象,它簡單有效。雖然它不是萬能的,但是它做的已經夠好了:利用簡單的語法便可以管理動態分配的對象。

 

基本語法:

unique_ptr 是一個模板類,你可以很簡單地構造一個unique_ptr的對象,如下:

 

std::unique_ptr p( new foo(42) );

構造完成之後,你便可以像通過一般的指針一樣來操作對象。比如operator*以及operator->操作符還是一樣如你所預期的那樣工作。

 

 

正如你可以像使用一般地指針那樣使用unique_ptr類,最最重要的還是你可以unique_ptr會在超出作用域時自動的銷毀該對象。你不必擔心在作用域的某個出口忘記delete導致內存洩露,甚至在出現異常之後它也可以自動的銷毀對象。

 

unique_ptr與容器

到目前為止,一切都是幸運的,根據標准C++的語法你同樣可以實現上面的那些種種功能,實際上,auto_ptr這個不幸者(C++11已經將其廢棄掉了)便是可以實現上述功能的,作為一個RAII的包裹器。

不行的是,auto_ptr並不能適當的工作,即使對於一些基本的操作,auto_ptr的表現也不盡人意。例如,如果你需要創建一個存放auto_ptr的容器,那麼這將是一個大問題.

 

補充:關於對象與容器的關系,下面我自己寫了一段簡單的代碼以作理解:

 

class A
{
public:
    A() {cout << A ctor called... << endl;}
    A(const A&) {cout < vec;
    for(int i=0;i<5;++i)
    {
        cout <<  i =  << i << endl;
        vec.push_back(A());  //構造該對象並且復制該對象。見下面代碼的運行結果
    }
    return 0;
}

上述代碼的執行結果是: \

 

 

可以看到,上述代碼的執行結果中調用了拷貝構造函數。

 

[continue] 步入正題:

C++11加入了右值引用(rvalue reference) 和 move語意(move semantic) 來解決這些問題。幸運的是,經過修復,unique_ptr可以存儲在容器中,即使容器被resize並且或者是被move都有正確的語意。並且當容器被銷毀時,這些指針管理的資源也可以被正常的銷毀。

 

唯一性與move語意:

unique這個詞到底意味著什麼呢?就如其字面意思一樣,當你創建一個unique_ptr時,你就宣稱這個指針就是獨一份的,沒有歧義的,就只有你可以擁有它,別人不可能也不會不經意的復制它。

比如,對於一個一般的指針,有如下代碼:

foo *p = new foo(useful object);

make_use(p) ; // make_use函數的參數是一個對象指針

這裡,我分配了一個對象並且有一個指針p指向它,當我在調用make_use函數的時,指針p會發生什麼呢?make_use會為該指針做一份拷貝嗎?在調用完畢之後會釋放掉內存嗎?或者說它就只是簡單的借用一會兒該指針就原封不動的還回來,讓調用者去釋放空間呢?

 

上面的問題我們一個也無法回答, 是因為C++本身並沒有對怎麼使用指針這件事情作任何的約定,你只有通過查看自己的代碼,查看自己的把內存以及文檔來解決。

 

所幸的是,有了unique_ptr,這些問題都不是問題了,如果你傳了一個指針給另外一個例程(權當函數理解了)。你不會對該指針做一份copy(因為它是unique的),即使你那樣做,編譯器也是不答應的。

 

指針的擁有者:

首先來一個簡單的例子:創建一個unique_ptr,將其存放在一個容器中。作為一個unique_ptr的新手,你可能寫出下面的代碼:

 

std::unique_ptr q( new foo(42) );
v.push_back( q );

這似乎是合理的,但是這樣做會讓我進入一個灰色地帶:誰是這個指針的擁有者,這個容器會在它生命周期的某個時刻釋放該指針嗎?或者是還得由創建者來自己釋放它?

 

 

面對這些糾結,unique_ptr 禁止這樣的代碼。編譯這樣的代碼將會導致編譯錯誤。

 

Anyway,這裡的問題就是我們只允許有該指針的一份拷貝。如果你想要將該對象交給另一個對象,就必須調用move函數,也就是說你必須放棄掉該對象的擁有權。

如:

v.push_back( std::move(q) );

 

執行完上述語句之後,q已經變成空的了,因為q已經放棄了該對象的擁有權,將擁有權交給了容器。

 

move語意可以用在任何你需要創建一個“右值引用”的地方。例如下面的代碼:

return q;

返回一個unique_ptr則不需要任何特殊的代碼就可以完成。

還有,創建一個臨時的對象給一個需要unique_ptr的函數也是不需要特殊處理的。如:

process( std::unique_ptr( new foo(41) ) );

 

Legacy Code: 老程序,其實也就是兼容性啦。

當你在使用unqiue_ptr的時候。你發現你現在需要的是一個底層的指針,那麼有兩種方式:

 

do_something( q.get() );          //retain ownership
do_something_else( q.release() ); //give up ownership

get函數是不會轉交擁有權的。 因此在大多數情況下get 函數是不提倡使用的。因為你一旦將unique_ptr包裹的真正的指針釋放給函數使用了,那麼你就很難控制該函數到底會對這個指針做些什麼操作。也就是說你必須對你的函數謹慎再謹慎,以保證該函數只是簡單的借用一下該指針而已。

 

而release函數則是一個比較靠譜的方式了,當你向上述一樣對指針q調用 release時,其實你就已經宣稱說:該對象已經不歸我管了,現在就是你的了。

 

當你的代碼寫的比較成熟的時候,這樣的話就不會再頻繁的出現了。

還有,當unique_ptr作為引用對象傳遞給函數的時候,如下:

 

void inc_baz( std::unique_ptr &p )
{
    p->baz++;
}

因為是傳引用,所以你完全不必要擔心該指針會被復制或者模糊了擁有者之類的事情了。

 

 

關於auto_ptr的使用,其實我們只需要在代碼中多多的使用auto關鍵字來做類型推斷,那麼實際上我們在改寫自己的代碼來使用unqiue_ptr的時候,我們不需要改變更多的用戶代碼。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved