複製構造函數、賦值操做符和析構函數總稱爲複製控制。app
複製構造函數:特殊的構造函數,具備單個形參,該形參時對該類類型的const引用。定義新對象並用同類型對象初始化,顯式調用了複製構造函數;將該類型對象傳遞給函數或從函數返回該類型的對象時,隱式調用了複製構造函數。函數
析構函數:當對象超出做用域或動態分配的對象被刪除時,自動調用析構函數,用於釋放在造函數或在對象生命期內獲取的資源。fetch
對類類型對象來講,this
1)直接初始化直接調用與實參匹配的構造函數指針
2)複製初始化老是調用複製構造函數(複製初始化首先使用指定構造函數建立一個臨時對象,而後用複製構造函數將臨時對象複製到正在建立的對象)code
若是咱們沒有定義複製構造,編譯器會爲咱們合成。即便定義了其餘構造函數,也會合成。對象
合成複製構造函數的行爲是,執行逐個成員初始化,將新對象初始化爲原對象的副本。blog
只包含類類型成員和內置類型(不是指針)成員的類,無需顯式定義複製構造函數;ip
複製構造函數的定義也可使用初始化列表,能夠在函數體中作任何其餘必要工做。內存
爲防止複製,類必須顯式聲明其複製構造函數爲private;若是想要連友元和成員中的複製也禁止,就能夠聲明爲private而不定義。
合成賦值操做符執行逐個成員賦值,返回*this,是對左操做數對象的引用。
可使用合成複製構造函數的類一般也可使用合成賦值操做符
撤銷類對象(超出做用域)會自動調用析構函數。
當對象的引用或指針超出做用域時,不會運行析構函數。只有刪除指向動態分配對象的指針或實際對象(而不是引用)超出做用域時,纔會運行析構函數。
三法則:若是須要析構函數,則須要全部三個複製控制成員。
與複製構造和賦值操做符不一樣,編譯器老是(即使本身編寫了析構)會爲咱們合成一個析構函數。合成析構函數按成員在類中聲明次序的逆序撤銷成員。對於類類型成員,合成析構函數調用該成員的析構函數來撤銷該對象。
執行順序:先執行類定義的析構,再運行合成析構函數
1)指針成員採起常規指針型行爲。具備指針的全部缺陷但無需特殊的複製控制
使用默認合成複製構造函數
沒法避免懸垂指針(指針指向的內存被釋放,指針指向一個不復存在的對象)
2)使用智能指針。指針所指向的對象是共享的,但類可以防止懸垂指針
3)類採起值型行爲。指針所指向的對象是惟一的。由每一個類對象獨立管理。
使用計數類
// private class for use by HasPtr only class U_Ptr { friend class HasPtr; int *ip; size_t use; U_Ptr(int *p): ip(p), use(1) { } ~U_Ptr() { delete ip; } };
使用計數類的使用
/* smart pointer class: takes ownership of the dynamically allocated * object to which it is bound * User code must dynamically allocate an object to initialize a HasPtr * and must not delete that object; the HasPtr class will delete it */ class HasPtr { public: // HasPtr owns the pointer; pmust have been dynamically allocated HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i) { } // copy members and increment the use count HasPtr(const HasPtr &orig): ptr(orig.ptr), val(orig.val) { ++ptr->use; } HasPtr& operator=(const HasPtr&); // if use count goes to zero, delete the U_Ptr object ~HasPtr() { if (--ptr->use == 0) delete ptr; } private: U_Ptr *ptr; // points to use-counted U_Ptr class int val; };
賦值與使用計數
HasPtr& HasPtr::operator=(const HasPtr &rhs) { ++rhs.ptr->use; // increment use count on rhs first if (--ptr->use == 0) delete ptr; // if use count goes to 0 on this object, delete it ptr = rhs.ptr; // copy the U_Ptr object val = rhs.val; // copy the int member return *this; }
給指針成員提供值語義,複製值型對象時,會獲得一個不一樣的新副本;要使指針成員表現得像一個值,賦值對象時必須複製指針所指向的對象。
/* * Valuelike behavior even though HasPtr has a pointer member: * Each time we copy a HasPtr object, we make a new copy of the * underlying int object to which ptr points. */ class HasPtr { public: // no point to passing a pointer if we're going to copy it anyway // store pointer to a copy of the object we're given HasPtr(const int &p, int i): ptr(new int(p)), val(i) {} // copy members and increment the use count HasPtr(const HasPtr &orig): ptr(new int (*orig.ptr)), val(orig.val) { } HasPtr& operator=(const HasPtr&); ~HasPtr() { delete ptr; } // accessors must change to fetch value from Ptr object int get_ptr_val() const { return *ptr; } int get_int() const { return val; } // change the appropriate data member void set_ptr(int *p) { ptr = p; } void set_int(int i) { val = i; } // return or change the value pointed to, so ok for const objects int *get_ptr() const { return ptr; } void set_ptr_val(int p) const { *ptr = p; } private: int *ptr; // points to an int int val; };
賦值操做符不須要分配新對象,它只是必須記得給其指針所指向的對象賦新值,而不是給指針自己賦值。(即便要將一個對象賦值給它自己,賦值操做符也必須老是保證正確。)
HasPtr& HasPtr::operator=(const HasPtr &rhs) { // Note: Every HasPtr is guaranteed to point at an actual int; // We know that ptr cannot be a zero pointer *ptr = *rhs.ptr; // copy the value pointed to val = rhs.val; // copy the int return *this; }