std::智能指針

 

auto_ptrc++

用於解決資源自動釋放問題,見以下代碼:
void Function()
{
 Obj*p = new Obj(20);
 ...
 If (error occor)
  throw ... 或者 retrun;
 delete p;
}
在函數遇到錯誤以後,通常會拋異常,或者返回,可是這時可能泄露以前申請的資源,使用auto_ptr會在析夠函數中進行資源釋放。使用auto_ptr代碼以下
void Function(){
  auto_ptr<Obj> ptr(new Obj(20));
  ...程序員

  if (error occur)
   throw exception...
}
這樣不管函數是否發生異常,在何處返回,資源都會被自動釋放。
注:auto_ptr是被c++11標準廢棄的一個智能指針,爲何會被廢棄,見如下面代碼:
auto_ptr<Obj> ptr1( new Obj() );
ptr1->FuncA();
auto_ptr<Obj> ptr2 = ptr1;
ptr2->FuncA();
ptr1->FuncA();  // 這句話會異常
爲何在將ptr1複製給ptr2以後ptr1使用致使異常?這正是它被拋棄的主因。由於auto_ptr複製構造函數中把真是引用內存指針進行了轉移,就是從ptr1轉移給ptr2,此後,ptr2引用了Obj內存地址,而ptr1引用內存地址爲空,此時再使用ptr1則異常。安全

unique_ptr函數

unique_ptr做爲auto_ptr替代品。它對引用對象全部權專注,所以得名unique
1) 沒法進行復制構造和賦值操做
auto_ptr與unique_ptr的對比:
auto_ptr<Obj> ap(new Obj() );
auto_ptr<Obj> one (ap) ; // ok
auto_ptr<Obj> two = one; //ok

spa

unique_ptr<Obj> ap(new Obj() );
unique_ptr<Obj> one (ap) ; //error指針

unique_ptr<Obj> two = one; //errorc++11

也就是unique_ptr對對象的引用專注,不容許隨意轉移。對象

2)  可進行移動構造和移動賦值操做
unique_ptr<Obj> GetObj()
{
  unique<Obj> ptr( new Obj() );
  return ptr;
}
unique<Obj> ptr = GetObj();
上面的代碼可順利執行!但萬一須要將一個unique_ptr智能指針賦值給另一個怎麼辦呢?可以使用移動函數:什麼是移動構造和移動賦值呢?(見C++11的標準規範)。
unique<Obj> ptr1(new Obj());
unique<Obj> ptr2(std::move(ptr1));
該效果與auto_ptr直接賦值同樣,ptr1再也不擁有Obj對象,以後ptr1不能再操做內存中Obj對象。 但由於移動操做爲程序猿主動操做,所以程序員會留意使用。遞歸

shared_ptr內存

auto_ptr和unique_ptr只能用一個智能指針引用對象,而shared_ptr則是能夠多個智能指針同時擁有一個對象。shared_ptr實現方式是採用引用計數。引用計數原理即多個智能指針同時引用一個對象,每當引用一次,引用計數加1,當一個智能指針銷燬,引用計數減1,當引用計數減小到0的時釋放引用對象。這種引用計數增減發生在智能指針的構造函數,複製構造函數,賦值操做符,析構函數中。這種方式使得多個智能指針同時對所引用的對象有擁有權,同時在引用計數減到0以後自動釋放資源,也實現了auto_ptr和unique_ptr的資源釋放功能。
void Function()
 {
  shared_ptr<Obj> ptr1(new Obj() ); // 引用計數爲1
  {
   shared_ptr<Obj> ptr2(ptr1); // 引用計數爲2
   {
     shared_ptr<Obj> ptr3 = ptr2; // 引用計數爲3
     int e = 0;

   }
   //引用計數爲2
  }
  //引用計數爲1
}
//函數返回以後引用計數爲0,new 出來的Obj內存被釋放。

因爲shared_ptr支持複製構造,因此它可做爲標準庫容器中的元素
vector<shared_ptr<Obj>> vect;
for (int i = 0; i < 10; ++i){
  vect.push_back( shared_ptr<Obj>( new Obj() ) );
}
vector<shared_ptr<Obj>> vect2 = vect;
這些操做是auto_ptr和unique_ptr不能實現的。注意,智能指針默認使用delete來釋放資源,若是資源是FILE*怎麼辦?釋放的時候就須要用fclose了。如何實現呢? 
shared_ptr構造函數能夠傳遞一個刪除器。
FILE* pStm = fopen(...);
shared_ptr<FILE> fileRes(pStm, &fclose);
或者使用一個仿函數
class FileCloser { 
     public: 
        void operator()(FILE* file) { 
         std::cout << "The FileCloser has been called with a FILE*, " 
           "which will now be closed.\n"; 
         if (file!=0)  
           fclose(file); 
       } 
     }; 
shared_ptr<FILE> fileRes(pStm, FileCloser);

weak_ptr

shared_ptr是一種強引用的關係,智能指針直接引用對象。那麼這個代碼有一個隱含的問題,就是循環引用,從而形成內存泄漏。示例看一個循環引用的例子。
class Parent{
public:
    shared_ptr<Child> child;
};
class Child{
public:
    shared_ptr<Parent> parent;
};
void Function() {
  shared_ptr<Parent> pA(new Parent);
  shared_ptr<Child> pB(new Child);
  pA->child = pB;
  pB->parent = pA;
}
如今來分析一下Function函數的執行過程:
1. 第一條語句使得pA引用了Parent一個指針,Parent引用計數爲1
2. 第二條語句使得pB引用了Child一個指針,Child引用計數爲1
3. 第三條語句,調用了shared_ptr<Child>類賦值操做符,使得Child引用計數變爲2
4. 第四條語句,調用了shared_ptr<Parent>類賦值操做符,使得Parent引用計數變爲2
5. 函數返回以前調用了shared_ptr<Parent>和shared_ptr<Child>類的析夠函數,使得Child引用計數變爲1,Parent引用計數變爲1
看!函數執行完以後new出來的Parent和Child並無釋放,因此出現了內存泄漏。
出現泄漏的緣由就是pA和pB相互引用了,致使二者所引用對象的引用計數不能減小到0,形成泄漏。
若是把第三條語句或者第四條語句任意刪除一個,則不會泄漏了。這就是強引用所致問題。
weak_ptr字面意思即一個弱指針,並不指該指針能力弱,而是指對它所引用對象全部權弱,它並不擁有所引用對象全部權,且不能直接使用所引用對象。在stl中,weak_ptr是和shared_ptr配合使用的,在實現shared_ptr的時候也就考慮了weak_ptr的因素。
weak_ptr是shared_ptr的觀察者,它不會干擾shared_ptr所共享對象的全部權,
當一個weak_ptr所觀察的shared_ptr要釋放它的資源時,它會把相關的weak_ptr的指針設置爲空,防止weak_ptr持有懸空的指針。
注意:weak_ptr並不擁有資源的全部權,因此不能直接使用資源。
能夠從一個weak_ptr構造一個shared_ptr以取得共享資源的全部權。
void Function()
{
 shared_ptr<int> sp( new Obj() );
 assert(sp.use_count() == 1);
 weak_ptr<int> wp(sp); //從shared_ptr建立weak_ptr
 assert(wp.use_count() == 1);
 if (!wp.expired())//判斷weak_ptr觀察的對象是否失效
 {
  shared_ptr<int> sp2 = wp.lock();//得到一個shared_ptr
  *sp2 = 100;
  assert(wp.use_count() == 2);
 }
 assert(wp.use_count() == 1);
 return 0;
}
weak_ptr並無重載-> 和 * 操做符,因此咱們不能經過他來直接使用資源,咱們能夠經過lock來得到一個shared_ptr對象來對資源進行使用,若是引用的資源已經釋放,lock()函數將返回一個存儲空指針的shared_ptr。 expired函數用來判斷資源是否失效。使用weak_ptr並不會增長資源的引用計數。因此對資源的引用是弱引用,利用這個特性能夠解決前面所說的循環依賴問題。

class Parent {
public:
    weak_ptr<Child> child;
};
class Child {
public:
    weak_ptr<Parent> parent;
};
void Function()
{
 shared_ptr<Parent> pA(new Parent);
 shared_ptr<Child> pB(new Child);
 pA->child = pB;
 pB->parent = pA;
}
此時第三和第四條語句的執行並無增長引用計數,從而在函數執行完成只有能自動釋放內存。以上分析可見,weak_ptr是一種輔助shared_ptr的智能指針,通常不單獨使用,而是結合shared_ptr一塊兒使用。

應用總結

儘可能使用unique_ptr而不是auto_ptr

shared_ptr知足大部分需求;

weak_ptr避免遞歸的依賴關係;

 

make_shared

1. 在初始化unique_ptr或者shared_ptr時,優先使用std::make_unique和std::make_shared。緣由:

1)異常安全性

假設有以下函數聲明:

intcomputePriority();
void processInvestment(std::shared_ptr<Investment> ptr,int priority);
調用processInvestment的代碼以下所示:

processInvestment(std::shared_ptr<Investment>(newInvestment()),computePriority());

因爲在C++中函數參數的執行順序不固定,因此在上面對函數processInvestment調用中,函數參數的執行順序極可能是:

 new Investment

 computePriority()

 std::shared_ptr constructor

這種執行順序的風險是,若是在第二步,執行computePriority的過程當中出現異常,那麼在第一步中new出來的對象將變得不可訪問,從而形成內存泄漏。
經過make_ptr的方式,將new操做和shared_ptr的構造放在一塊兒執行,就不會出現內存泄漏問題了。

2)執行效率(對於shared_ptr而言)

std::shared_ptr<Investment>ptr(new Investment); //方式1,new的方式在方式1中,會涉及到兩次動態內存分配:

第1次是new Investment時,爲Investment對象分配空間; 第2次是爲控制塊(Control Block)分配空間。

auto pIn = std::make_shared<Investment>();//方式2,make_shared的方式

在方式二中,一次動態內存分配就足夠了,這是因爲make_shared會爲Investment對象和控制塊(Control block)一次性分配一大塊內存。因爲只有一次內存分配,於是方式二提升了程序的執行效率。

二、make_xxx函數的弊端

既然使用make_xxx有這麼多好處,是否應到處使用make函數而徹底放棄new方式?固然不是,make函數存在如下限制:

1) make函數不支持用戶自定義釋放器。因爲make函數有本身的內存分配和析構規則,因此不適用於自定義分配器和釋放器的對象。

2) make函數不支持大括號初始化方式。對於下面這句代碼:

auto spv = std::make_shared<vector<int>>(10,20);

意爲spv指向一個vector,該vector有10個元素,每一個元素值爲20。若是想實現的是這個vector有兩個元素,分別爲10,20的話,只能用new方式。

3) 內存釋放不夠靈活。

在使用new的方式中,有兩塊獨立的堆內存,一塊存放資源對象,一塊存放控制塊,當資源對象的引用計數爲0的時候,資源對象會被銷燬,它所佔用的內存也會被隨之銷燬。

在使用make函數的方式中,make函數一次性爲資源對象和控制塊分配了一塊內存,當資源對象的引用計數爲0是,對象被銷燬,可是資源對象佔用的內存卻不會被銷燬,只有當控制塊佔用的內存被銷燬是纔會將資源對象所佔內存一併釋放。那麼,控制塊內存何時被釋放呢?這就涉及到控制塊中另外一個引用計數了,這個引用計數被稱爲「Weak Count」,其做用是用來計數指向該資源的weak_ptr的數量。當這個weak count的值爲0時,控制塊纔會被釋放。當資源對象很是龐大時,使用make函數的方式將形成不小的資源浪費。

相關文章
相關標籤/搜索