C++智能指標

來源:互聯網
上載者:User

標籤:har   object   names   實現   src   cells   參數   image   color   

智能指標模板類

智能指標是行為類似於指標的類對象,但這種對象還有其他功能。本文章介紹三個可協助管理動態記憶體分配的只能指標模板。先來看需要哪些功能以及這些功能是如何?的。請看下面的函數

void remodel(std::string & str)

{

         std::string *ps = new std::string(str);

         ...

         str = ps;

         return ;

}

你可能發現了其中的缺陷。每當調用時,該函數都分配堆中的記憶體,,但從不回收,從而導致記憶體泄露。你可能也知道解決之道------只要別忘了在return語句前添加下面的語句,以釋放分配的記憶體即可:

delete ps;

然而,但凡涉及“別忘了”的解決方案,很少是最佳的。因為你有時可能忘了,有時可能記住了,但可能在不經意間刪除或注釋了這些代碼。即使確實沒有忘記,也可能有問題。請看下面的變體:

void remodel(std::string & str)

{

         std::string *ps = new std::string(str);

         ...

         if (weird_thing())

         {

                   throw exception();

         }

         str = *ps;

         delete ps;

         return;

}

 當出現異常時,delete將不被執行,因此也將導致記憶體泄露。

當remodel()這樣的函數終止(不管是正常終止,還是由於出現了異常而終止),本地變數都將從棧記憶體中刪除------因此指標ps佔據的記憶體將被釋放。如果ps指向的記憶體也被釋放,那該有多好啊。如果ps有一個解構函式,該解構函式在ps到期時釋放他指向的記憶體。因此,ps的問題在於,它只是一個常規指標,不是有解構函式的類對象。如果它是對象,則可以在對象到期時,讓它的解構函式刪除指向的記憶體。這正是auto_ptr、unique_ptr、shared_ptr背後的思想。模板auto_ptr是C++98提供的解決方案,C++已將其摒棄,並提供了另外兩種解決方案。然而,雖然auto_ptr被摒棄,但它已使用了多年同時,如果您的編譯器不支援其他兩種解決方案,auto_ptr將是唯一的選擇。

使用智能指標

這三個智能指標模板(auto_ptr,unique_ptr,shared_ptr)都定義了類似指標的對象,可以將new獲得(直接或間接)的地址賦給這個對象。當智能指標到期時,其解構函式將使用delete來釋放記憶體。因此,如果將new返回的地址賦給這些對象,將無需記住稍後釋放這些記憶體;在智能指標到期時,這些記憶體將自動被釋放。說明了auto_ptr和常規指標在行為方面的差別;shared_ptr和unique_ptr的行為與auto_ptr相同。

 

要建立智能指標對象,必須包含標頭檔memory,該檔案模板定義。然後使用通常的模板文法來執行個體化所需類型的指標。例如,模板auto_ptr包含如下建構函式:

template<class X>

class auto_ptr

{

         explicit auto_ptr(X *p = 0)throw();

         ...

        

};

throw()意味這建構函式不會引發異常:與auto_ptr一樣,throw()也被摒棄。因此,請求X類型的auto_ptr將獲得一個指向X類型的auto_ptr:

auto_ptr<double>pd(new double);//pd an auto_ptr to double

                                                                    //(use in place of double *pd)

auto_ptr<string>ps(new string);//ps an auto_ptr to string

                                                                    //(use in place of string *ps)

new double是new返回的指標,指向新分配的記憶體塊。它是建構函式auto_ptr<double>的參數,即對應於原型中形參p的實參。同樣,new string也是建構函式的實參。其他兩種智能指標使用同樣的文法:

unique_ptr<double> pdu(new double);//pdu an unique_ptr to double

shared_ptr<string> pss(new string);//pss a shared_ptr to string

因此。要轉換remodel()函數,應按下面3個步驟進行:

  1. 包含標頭檔memory;
  2. 將指向string的指標替換為指向string的智能指標對象;
  3. 刪除delete語句。

下面是使用auto_ptr修改該函數的結果:

#include <memory>

void remodel(std::string & str)

{

         std::auto_ptr<std::string>ps (new std::string(str));

         …

         if (weird_thing())

{

         throw exception();

}

str = *ps;

//delete ps; NO LONGER NEEDED

return ;

}

注意到智能指標模板位於命名空間std中。下面是一個簡單的程式,示範了如何使用全部三種智能指標。每個智能指標都放在一個代碼塊內,這樣離開代碼塊時,指標將到期。Report類使用方法報告對象的建立和銷毀。

//smrtptrs.cpp -- using three kinds of smart pointers

//requires support of C++11 shared_ptr and unique_ptr

#include <iostream>

#include <string>

#include <memory>

 

using namespace std;

 

class Report

{

public:

         Report(const string s) : str(s)

         {

                   cout << "Object created!" << endl;

         }

         ~Report()

         {

                   cout << "Object deleted!" << endl;

         }

         void comment() const

         {

                   cout << str << endl;

         }

private:

         string str;

};

 

int main()

{

         {

                   auto_ptr<Report> ps(new Report("using auto_ptr"));

                   ps->comment();

         }

         {

                   shared_ptr<Report> ps(new Report("using shared_ptr"));

                   ps->comment();

         }

         {

                   unique_ptr<Report> ps(new Report("using unique_ptr"));

                   ps->comment();

         }

 

         system("pause");

         return 0;

}

程式輸出結果:

 

 

所有智能指標類都一個explicit建構函式,該建構函式將指標作為參數。因此不需要自動將指標轉換為智能指標對象:

shared_ptr<double> pd;

double *p_reg = new double;

pd = p_reg; //not allowed (implicit conversion)

pd = shared_ptr<double>(p_reg);//allowed(explicit conversion)

shared_ptr<double> pshared = p_reg;//not allowed(implicit conversion)

shared_ptr<double> pshared(p_reg);//allowed(explicit conversion)

由於智能指標模板類的定義方式,智能指標對象的很多方面都類似於常規指標。例如,如果ps是一個智能指標對象,則可以對它執行解除引用操作(*ps)、用它來訪問結構成員(ps->puffIndex)、將他賦給指向相同類型的常規指標,還可以將智能指標對象賦給另一個同類型智能指標對象,但將引起一個問題,這將在下面進行討論。

但在此之前,先說說對全部三種智能指標都應避免的一點:

string vacation(“I wandered lonely as a cloud”);

shared_ptr<string> pvac(&vacation);//NO;

 

pvac到期時,程式將把delete運算子用於非堆記憶體,這是錯誤的。

有關智能指標的注意事項

先來看下面的指派陳述式:

auto_ptr<string> ps(new string(“I reigned lonely as a cloud”));

auto_ptr<string> vocation;

vocation = ps;

上述指派陳述式將完成什麼工作呢?如果ps和vocation是常規指標,則兩個指標將指向同一個string對象這是不能接受的,因為程式將試圖刪除同一個同對象兩次——一次是ps到期時,一次vocation到期時。要避免這種問題,方法有多種。

l  定義賦值運算子,使之執行深複製。這樣兩個指標將指向不同的對象,其中的一個對象是另一個對象的副本。

l  建立所有權(ownership)概念,對於特定的對象,只能有一個智能指標可擁有它,這樣只有擁有和unique_ptr的策略,但unique_ptr的策略更嚴格。

l  建立智能更高的指標,跟蹤引用特定對象的智能指標數。這稱為引用計數(reference counting)。這是shared_ptr採用的策略

當然,同樣的策略也適用於複製建構函式。

每種方法都有其用途。如下程式不適合使用auto_ptr的執行個體

#include <iostream>

#include <string>

#include <memory>

 

using namespace std;

 

int main()

{

         auto_ptr<string> films[5] =

         {

                   auto_ptr<string> (new string("Fowl Balls")),

                   auto_ptr<string> (new string("Duck Walks")),

                   auto_ptr<string> (new string("Chicken Run")),

                   auto_ptr<string> (new string("Turkey Errors")),

                   auto_ptr<string> (new string("Goose Eggs"))

         };

 

         auto_ptr<string> pwin;

         pwin = films[2];

 

         cout << "The nominees for best avian baseball film are \n";

         for (int i = 0; i < 5; i++)

         {

                   cout << *films[i] << endl;

         }

         cout << "The winner is " << *pwin << endl;

         cin.get();

 

         return 0;

}

該程式的輸出:

 

這裡的問題在於,下面的語句所有權從film[2]轉讓給pwin:

pwin = films[2];//films[2] loses ownership

這導致films[2]不再引用該字串。在auto_ptr放棄對象的所有權後,便可能使用它來訪問該對象。當程式列印films[2]指向的字串時,卻發現這是一個null 指標,這顯然討厭的意外。

如果在上面程式中使用shared_ptr代替auto_ptr,則程式將正常運行,其輸出如下:

 

這次pwin和films[2]指向同一個對象,而引用計數從1增加到2。在程式末尾,後申明的pwin首先調用其解構函式,該解構函式將引用技術降低到1.然後,shared_ptr數組的成員被釋放,對filmsp[2]調用解構函式時,將引用計數降低到0,並釋放以前分配的空間。

如果使用unique_ptr結果會怎麼樣呢?unique_ptr也採用所有權模型,但使用unique_ptr時。程式不會等到運行階段崩潰,而是出現語法錯誤,如下

pwin = films[2];

 

unique_ptr為何優於auto_ptr

請看下面的語句:

auto_ptr<string> p1(new string("auto"));//#1

auto_ptr<string> p2;//#2

p2 = p1;//#3

在語句#3中,p2接管string對象的所有權後,p1的所有權被剝奪。前面說過,這是件好事,可防止p1和p2的解構函式試圖刪除同一個對象;但如果程式隨後試圖使用p1,這將是件壞事,因為p1不再指向有效資料。

下面來看使用unique_ptr的情況:

unique_ptr<string> p3(new string("auto"));//#4

unique_ptr<string> p4;//#5

p3 = p4;//#6

編譯器認為語句#6非法,避免了p3不再指向有效資料的問題。因此,unique_ptr比auto_ptr更安全(編譯階段錯誤比潛在的程式崩潰更安全)

但有時候,有一個智能指標賦給另一個並不會留下危險的懸掛指標。假設有如下函數定義:

unique_ptr<string> demo(const char *s)

{

         unique_ptr<string> temp(new string(s));

         return temp;

}

並假設編寫了如下代碼:

unique_ptr<string> ps;

ps = demo("Uniquely special");

demo()返回一個臨時unique_ptr,然後ps接管了原本歸返回的unique_ptr所有的對象,而返回的unique_ptr被銷毀。這沒有問題,因為ps擁有了string對象的所有權。但這裡的另一個好處是,demo()返回的臨時unique_ptr很快被銷毀,沒有機會使用它來訪問無效的資料。換句話說,沒有理由禁止這種賦值。神奇的是,編譯器確實允許這種賦值!

總之,程式試圖講一個unique_ptr賦給另一個時,如果源unique_ptr是一個臨時右值,編譯器允許這樣做;如果源unique_ptr將存在一段時間,編譯器將禁止這樣做

         unique_ptr<string> pu1(new string("Hi ho!"));

         unique_ptr<string> pu2;

         pu2 = pu1;                              //#1 not allowed

         unique_ptr<string> pu3;

         pu3 = unique_ptr<string>(new string("Yo!"));   //#2 allowed

語句#1將留下懸掛的unique_ptr(pu1),這可能導致危害。語句#2不會留下的unique_ptr,因為它調用unique_ptr的建構函式,該建構函式建立的臨時對象在其所有權轉讓給pu3後就會被銷毀。這種隋情況而異的行為表明,unique_ptr優於允許兩種賦值的auto_ptr。這也是禁止(只是一個建議,編譯器並不禁止)在容器物件中使用auto_ptr,但允許使用unique_ptr的原因。如果容器演算法試圖對包含unique_ptr的容器執行類似於語句#1的操作,將導致編譯錯誤;如果演算法試圖執行類似於語句#2的操作,則不會有任何問題,而對於auto_ptr,類似於語句#1的操作可能導致不確定的行為和神秘的崩潰。

當然,你可能確實想執行類似於語句#1的操作。僅當以非智能的方式使用遺棄的智能指標(如解除引用時),這種賦值不安全。要安全重入這種指標,可給它賦新值。C++有一個標準庫函數std::move(),讓你能能夠將一個unique_ptr賦給另一個。下面是一個使用前述demo()函數的例子,該函數返回一個unique_ptr<string>對象:

         unique_ptr<string> ps1, ps2;

         ps1 = demo("Uniquely special");

         ps2 = move(ps1);

         ps1 = demo(" and more");

         cout << *ps2 << *ps1 << endl;

相比於auto_ptr,unique_ptr還有一個另一個優點。它有一個可用於數組的變體。別忘了,必須將delete和new配對,將delete []和new []配對。模板auto_ptr使用delete而不是delete[],因此只能與new一起使用,而不能與new[]一起使用。但unique_ptr有使用new []和delete []的版本:

unique_ptr<double []>pda(new double(5));//will use delete []

警告:使用new分配記憶體時,才能使用auto_ptr和shared_ptr,使用new []分配記憶體時,不能使用它們,不使用new分配記憶體時,不能使用auto_ptr或shared_ptr;不適用new []分配記憶體時,不能使用unique_ptr。

 

C++智能指標

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.