閱讀578 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Way on c & c++ 小記 [六] – Rule of Three, 複製控製

Rule of Three, 複製控製

作者:Jason Lee @https://blog.csdn.net/jasonblog

日期:2010-04-13

 

[1]複製構造函數copy constructor

Rule of Three是指類如果需要析構函數,則通常也需要複製構造函數和賦值操作符。而其實習慣地顯示編寫這三者本就是一個良好的習慣。因為相較於編譯器自動生成的代碼,顯示編寫自己的代碼能讓程序員對整個程序有著更清晰的認識和把握。

從形式上說,複製構造函數具有單個本類型對象引用的形參(通常用const限定修飾),並且函數名與類名相同,因為複製構造函數也是構造函數。比如:

Demo(const Demo &demo){}

複製構造函數從需要上來看有兩種情況,一是有成員在構造函數中分配資源,比如指針 是最典型的例子;二是需要在複製過程做特定工作。

從作用場合來談,本質上可以看做是至少存在兩個對象的情況下需要複製構造函數,因為需要使用其一來對其它對象進行初始化。比如:

1. 根據同類型對象初始化某一對象;我個人覺得在這種情況下,很容易疏忽的一個細節之處是:一般,對構造函數比較生疏的程序員習慣地定義一個默認構造函數後,並且在裏麵進行指針所需空間的開辟和分配,就會潛意識地以為以後每個該類對象初始化時都會合理地首先對指針成員進行內存分配,然而卻沒有足夠深刻地意識到如果調用了複製構造函數,則不會調用默認函數:

#include <iostream> using namespace std; class Demo { public: Demo(){ p = new int(1); } Demo(const Demo &demo){ *p = *demo.p; }//指針p並未被分配空間 ~Demo(){} void setP(int v){ *p = v; } void show(){ cout << *p << endl; } private: int *p; }; int main(){//程序運行時出錯 Demo d1; d1.setP(3); Demo d2 = d1;//隻調用複製構造函數,並沒有調用默認構造函數,所以指針未擁有空間 return 0; }

2. 將對象作為普通實參;這種情況最容易與值傳遞的情況進行聯想:傳遞的是與變量等值的一個臨時值,而非同一個變量。同樣的,以對象作為普通實參,也需要建立一個臨時對象,就需要調用到複製構造函數。比如以下一段毫無意義的代碼:

3. 從函數中返回一個普通對象;這種情況的道理與上一種情況一樣,兩種情況可以出現在一段代碼中:

Demo Test(Demo demo){ Demo temp; return temp; }

4. 先調用構造函數創建一個臨時對象,再調用複製構造函數將該臨時對象複製到容器中的每個元素;

#include <iostream> #include <vector> using namespace std; class Demo { public: Demo(){ p = new int(1); } Demo(int v){ p = new int; *p = v; } Demo(const Demo &demo){ p = new int; *p = *demo.p; } ~Demo(){} void setP(int v){ *p = v; } void show(){ cout << *p << endl; } private: int *p; }; int main(){ vector<Demo> demos(3);//首先調用Demo()這個默認構造函數,然後再根據建立的臨時對象調用複製構造函數初始化其餘元素 return 0; }

5. 使用花括號對數組進行初始化時。

#include <iostream> using namespace std; class Demo { public: Demo(){ p = new int(1); } Demo(int v){ p = new int; *p = v; } Demo(const Demo &demo){ p = new int; *p = *demo.p; } ~Demo(){} void setP(int v){ *p = v; } void show(){ cout << *p << endl; } private: int *p; }; int main(){ Demo demos[3] = { Demo(3) };//隻根據臨時對象初始化demos[0] demos[0].show(); return 0; }

值得記錄的是當類具有指針成員時,如果沒有顯示編寫複製構造函數,而使用編譯器自動合成的複製構造函數,就很容易出現“指針懸掛”問題。而其實並不隻是該問題,當多對象運作時,由於使用了編譯器自動合成的複製構造函數,導致成員指針具有相同的地址,這就有點類似靜態成員了,實際上多對象操作的都是同一片內存空間。

[2] 賦值操作符assignment operator

賦值操作符是重載運算符的一種。相較於複製構造函數出現於用於初始化對象的場合,賦值操作符則出現於使用對象進行賦值的場合。實際上,二者通常同時需要,甚至可以看做一體。

#include <iostream> using namespace std; class Demo { public: Demo(){ p = new int(1); } ~Demo(){} void setP(int v){ *p = v; } void show(){ cout << *p << endl; } Demo& operator=(Demo &demo){ *p = *demo.p; return *this; } private: int *p; }; int main(){ Demo d1,d2; d1.setP(3); d2 = d1; d2.show(); return 0; }

[3] 析構函數destructor

析構函數按照成員在類中的聲明次序逆序撤銷成員,這與構造函數初始化列表又發生了關係。通常,普通對象在超出作用域時會調用析構函數,比如一個函數中的局部作用域對象;另外一種情況則是刪除指向對象的指針,會調用對象的析構函數。如果指針是指向一個STL 容器或者內置數組,且容器或數組的單元類型是對象的話,也會調用對象的析構函數,並且容器或數組中的元素是按逆序撤銷的。

#include <iostream> using namespace std; class Demo { public: Demo(){ p = new int(1); } Demo(const Demo &demo){ p = new int; *p = *demo.p; } ~Demo(){ delete p; } void setP(int v){ *p = v; } void show(){ cout << *p << endl; } Demo& operator=(Demo &demo){ *p = *demo.p; return *this; } private: int *p; }; int main(){ Demo d1,d2;//調用默認構造函數 d1.setP(3); Demo d3 = d1;//調用複製構造函數 d2 = d1;//使用賦值操作符 //析構... return 0; }

 

最後更新:2017-04-02 05:21:03

  上一篇:go [Qt Topic] – 全局熱鍵、托盤功能和隨機啟動
  下一篇:go IT項目管理的六種錯誤思維