指標 引用和auto_ptr,論介面參數設計的原則

來源:互聯網
上載者:User

noiile 2005-12-1

首先,我們來分析下面一段程式:

#include <iostream>#include <string>using namespace std;

class Obj{public: Obj() {cout << "Default Constructor/n";} Obj(const obj& o) {cout << "Copy Constructor/n";}

string getName() {return name;}

string name;};

void printObjName(Obj o){ cout << o.getName() << endl;}

int main(){ Obj myobj; myobj.name = "hello"; printObjName(myobj); return 0;}

以上程式實際上會構建多少個obj對象呢?
1個?錯,是兩個!
其執行出來的結果是:
Default Constructor
Copy Constructor

我們可以看到,第二個對象是調用copy constructor,但無論如何程式是建立了兩個對象,第二個對象是什麼時候建立的呢?很顯然,是將myobj傳進printObjName這個函數時建立的,因為printObjName的參數類型是obj,所以這裡編譯器調用複製建構函式,構造一個新的對象。
但是,printObjName僅僅是為了列印該對象的名字,為此而建立一個對象,對於C++程式員來說,這簡直是件不能忍受的事情,為此,需要浪費構建一個對象的記憶體和CPU時間。

實際上還不止如此,當調用obj的成員方法getName()的時候,實際上也對name這個字串對象進行了一次複製。別忘了,name也是個對象。而getName的傳回值是string。這種情況更容易讓程式員忽略,因為從代碼來看只調用getName這個方法而已,並沒有任何顯式的對象。但編譯器會為它隱式的產生一個臨時對象,因為getName()的傳回值是string。
更要命的是,這個get方法我們可能會頻繁的使用。

這種情況很容易避免,只要我們在函數的參數或傳回值的類型使用指標或引用。
例如前面那個程式,將函數的參數或傳回值改成常量引用。
將程式修改如下:

class obj{public:    obj() {cout << "Default Constructor/n";}    obj(const obj& o) {cout << "Copy Constructor/n";}

const string& getName() const {return name;}

string name;};

void printObjName(const obj& o){ cout << o.getName() << endl;}

int main(){ obj myobj; myobj.name = "hello"; printObjName(myobj); return 0;}

如此一來,程式再不會浪費半點鐘無用的記憶體或者CPU時間,看起來完美的多了。

但是,似乎換了指標,也能行的通,確實如此。那我們是應該什麼時候用指標什麼時候用引用呢?

在弄明白這個問題之前,得先瞭解指標和引用的區別:
引用保證了對象必須存在,不可為空,也不能更換,也不能delete。
指標允許為空白,也允許重新指向別的對象,而且有被別人delete的可能。

很顯然,如果我們希望必須有對象傳進來,那麼就用引用,如果我們不打算修改傳進來的對象,那麼我們就用常量引用,告訴使用介面的人,”hey,我不會修改你的對象,你放心傳進來吧”。

如果我們允許有Null 物件傳進來,那當然用指標了。當我們不會修改指標所指的對象時,就應該用常量指標。我們設計介面,必須要清晰的告訴使用介面的人,我們會對他傳進來的對象幹什麼,會修改它嗎?還是只是使用它而已,還是會delete它?
但是,僅用指標似乎不能夠表達這一切。試想下,假如介面函數因為只有你才知道的原因,需要獲得傳進來的指標對象的生命控制權,也就是說,你的介面函數有可能delete使用者傳進來的指標。怎麼能通過介面告訴使用者呢?
或許你會說,既然常量指標,表示我們不能修改對象的內容,那當然也不能delete它了,相反,非常量指標,就表示我們要獲得對象的生命控制權啦。

void testGetObj(const Obj* obj){    cout << obj->getName() << endl;}

void testGetObj2(Obj* obj){ cout << obj->getName() << endl; delete obj;}

但是,還不足夠,如果我們的介面函數既要修改對象的內容(不能使用const),但又不需要對對象的生命週期負責,那怎麼辦?如果用非常量指標,使用者如果按照之前所說的原則判斷,認為介面會delete對象,而實際上介面函數沒有delete對象,那會照成很頭痛的後果——記憶體泄露!

實際上,我們還應該盡量遵循另一個原則,對象由誰產生(new),就應該由誰銷毀(delete)。除非你能很清晰的告訴使用介面的人,你會delete這個對象。

所幸,標準庫為我們提供了一個很好的解決方案:auto_ptr。
將一個指標賦給智能指標auto_ptr,那麼智能指標將會具有指標對象的擁有權,它會在生命結束之前,自動delete指標所指的對象。

很好,到現在,我們可以解決問題了,我們定義規則如下:
如果我們要擁有對象的生命週期,那麼我們的介面應該使用auto_ptr.

void testGetObj(auto_ptr<Obj> obj);

如果我們不需要擁有對象的生命週期,也不需要修改指標所指的對象,那麼我們用常量指標。

void testGetObj(const Obj* obj);

如果我們不需要擁有對象的生命週期,但是需要修改指標所指的對象,那麼我們用非常量指標。

void testGetObj(Obj* obj);

對於介面函數的傳回值,我們同樣也能用以上原則。
對於Obj的getName函數,或許會很頻繁的被調用,為了避免無謂的對象複製,我們應該用常量引用(因為不希望使用者隨便修改名字)

const string& getName() const;

由使用者來決定,是否對該對象進行複製。如果使用者需要對該對象進行複製,那麼他可以這樣寫

const string myObjName = obj.getName();

當然,如果他要修改,則必須進行常量轉換const_cast。
更多的使用,使用者不需要對該對象進行複製,那麼他可以這樣寫:

const string& myObjName = obj.getName();

作為介面的設計者,你必須保留使用者的這種權利,而不應該強制使用者對你的返回對象進行複製。

不過,有種情況,很容易讓我們犯錯誤,看下面這個例子:

const string& getName() const{    string name = “Obj”;    return name;}

int main(){ cout << getName << endl; return 0;}

想想這段程式會列印什麼出來?天知道!問題出在哪裡?因為string name是一個臨時變數,它的生命週期僅限於這個函數體內,而函數的傳回值是對臨時變數的引用,一超出函數的範圍,這個對象是無效的,所以那個引用的傳回值已經是個無效的對象。

well,到這裡已經講完我要講的東西,最後來小結一下:

我們設計介面,必須要遵循一定的規則,讓使用介面的人能清楚知道該傳什麼類型的對象進去,而返回的對象又有可能是什麼.

根據指標,引用,和智能指標的特點,我們定義行為規範如下:

  1. 以下規則,通常情況下對基礎類型無效(因為通常情況下,基礎類型不大需要考慮值拷貝的消耗)
  2. 如果沒有特殊需要,我們盡量避免介面的參數,傳回值是物件類型,以省去對象的複製所帶來無謂的消耗.
  3. 當介面的參數一定是存在值的時候,我們應該用引用,如果我們的函數不需要修改傳進來的參數, 則應該用常量引用.
  4. 如果介面的參數不一定存在值,有時可以是Null 物件的時候,而且對傳進來的指標參數,函數不會控制它的生命週期,我們應該用指標,同樣,如果對該指標對象我們不會修改,則該介面的參數,應該用常量指標.
  5. 如果當函數需要控制參數的指標對象的生命週期的時候,我們應該用auto_ptr。
  6. 通常,我們類的一些get函數,我們更應該用引用。由調用該函數的人來決定是否複製該對象。當然,如果返回的是一個臨時對象(比如該對象是函數體內的局部變數),則不能用引用。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.