本文將學習類如何通過一組函式控制物件拷貝、賦值、移動和銷毀,這組函式分別是拷貝建構函式、移動建構函式、拷貝賦值運算子、移動賦值運算子以及析構函式。若類沒有顯示定義這些拷貝控制成員,則編譯器會自動定義。
如果乙個建構函式的第乙個引數是自身類型別的引用(幾乎總為const
引用),且任何額外引數都有預設值,則為拷貝建構函式,拷貝建構函式會被隱式使用,故不應為explicit
合成拷貝建構函式
若沒有為類定義拷貝建構函式,則編譯器會定義,即使定義了其他建構函式,編譯器也會合成拷貝建構函式
拷貝初始化
拷貝建構函式自身引數必須是引用型別,因為為了呼叫拷貝建構函式,必須拷貝實參,就又會呼叫拷貝建構函式,死迴圈
class sales_data // 拷貝建構函式,第乙個引數為引用,且通常為const
private:
std::string bookno;
int units_sold = 0;
}string dots(10, '.'); // 直接初始化
string s(dosts); // 直接初始化,因為是呼叫最匹配的建構函式,包括拷貝建構函式
string s2 = dots; // 拷貝初始化
參考
c++的一大誤區——深入解釋直接初始化與複製初始化的區別
與拷貝建構函式類似,若類未定義自己的拷貝賦值運算子,編譯器會生成乙個合成拷貝賦值運算子
析構函式釋放物件使用的資源,並銷毀物件的非static
資料成員
當指向乙個物件的引用或指標離開作用域時,析構函式不會執行
析構函式體自身並不直接銷毀成員,成員是在析構函式體之後隱含的析構階段被銷毀的
定義類時可以採取定義刪除的函式來阻止拷貝或賦值,因為對於某些類,這些操作可能無意義
拷貝操作可使型別的行為分為兩種
class hasptr
hasptr(const hasptr &p) : ps(new
std::string(*p.ps)), i(p.i) {}
hasptr& operator=(const hasptr &);
~hasptr() // 對 ps 執行 delete, 釋放分配的記憶體
private:
std::string *ps;
int i;
};hasptr& hasptr::operator=(const hasptr &rhs)
int main(void)
令乙個類展現類似指標的行為的最好方法是使用shared_ptr
管理類中的資源。若希望直接管理資源,可以使用引用計數(reference count), 接下來重新定義hasptr
,使用引用計數而不是shared_ptr
引用計數的工作方式
class
hasptr
// 拷貝建構函式拷貝所有三個資料成員,並遞增計數器
hasptr(const hasptr &p) : ps(p.ps), i(p.i), use(p.use)
hasptr& operator=(const hasptr&);
~hasptr();
private:
std::string *ps;
int i;
std::size_t *use; // 用來記錄有多少個物件共享 *ps 的成員
};hasptr::~hasptr()
}hasptr& hasptr::operator=(const hasptr &rhs)
ps = rhs.ps; // 將資料從 rhs 拷貝到本物件
i = rhs.i;
use = rhs.use;
return *this; // 返回本物件
}
管理資源的淚還會定義乙個名為swap
的函式,對於那些重拍元素順序的演算法,在交換元素時會呼叫swap
class hasptr;
inline
void swap(hasptr &lhs, hasptr &rhs)
// 引數是按值傳遞,故呼叫拷貝建構函式建立 rhs
hasptr& hasptr::operator=(hasptr rhs)
新標準定義了移動物件的特性,比拷貝物件大幅度提公升效能
右值引用就是必須繫結到右值的引用
int i = 42;
int &r = i; // 正確:r 引用 i
int &&rr = i; // 錯誤:不能將乙個右值引用繫結到乙個左值上
int &r2 = i * 42; // 錯誤:i * 42 是右值
const
int &r3 = i *42; // 正確:可以將 const 引用繫結到右值上
int &&rr2 = i * 42; // 正確:將 rr2 繫結到乘法結果上
為了讓自定義型別支援移動操作,需要為其定義移動建構函式和移動賦值運算子
與拷貝操作不同,移動操作永遠不會隱式i定義為刪除的函式
若我們顯示要求編譯器生成=default
的移動操作,且編譯器不能移動所有成員,則編譯器會將移動操作定義為刪除的函式
定義了乙個移動建構函式或移動賦值運算子的類必須也定義自己的拷貝操作,否則,該類的合成拷貝建構函式和拷貝賦值運算子被定義為刪除的
若乙個類有乙個可用的拷貝建構函式而沒有移動建構函式,則其物件是通過拷貝建構函式來移動的。拷貝賦值運算子和移動賦值運算子的情況類似
class hasptr
hasptr& operator=(hasptr rhs)
// ... 同上
}int main()
區分移動和拷貝的過載函式通常有乙個版本接受乙個const t&
, 另乙個版本接受乙個t &&
通常不需要為函式定義接受乙個const x&&
或乙個普通的x &
引數的版本。因為移動建構函式需要竊取資料,通常傳遞右值引用,故實參不能為const
。而拷貝建構函式的操作不應該改變該物件,故不需要普通的x &
引數的版本
// 定義了 push_back 的標準庫容器提供了兩個版本
void push_back(const x&); // 拷貝:繫結到任意型別的 x
void push_back(x&&); // 移動:只能繫結到型別 x 的可修改的左值
string s = "hao"
vector
vs;
vs.push_back(s); // 呼叫 push_back(const string&);
引用限定符
與定義const
成員函式形同,通過在引數列表後指定引用限定符,指定this
的左右/右值屬性,只能用於(非static
)成員函式,且必須同時出現在函式的宣告和語義中
// 舊標準中會出現向右值賦值的情況
string s1 = "a value", s2 = "another";
s1 + s2 = "wow!";
// 新標準可通過引用限定符解決上述問題
class foo;
foo &foo::operator=(const foo &rhs) &
每個類都會通過拷貝建構函式、移動建構函式、拷貝賦值運算子、移動賦值運算子和析構函式控制該型別物件拷貝、移動、賦值以及銷毀操作。移動建構函式和移動賦值運算子接受乙個(通常是非const
)的右值引用,而拷貝版本則接受乙個(通常是const
)的普通左值引用
若類未宣告這些操作,編譯器會自動生成。若這些操作未定義成刪除的,則會逐成員初始化、移動、賦值或銷毀物件:合成的操作依次處理每個非static
資料成員,根據成員來興確定如何移動、拷貝、賦值和銷毀它
分配了記憶體或其他資源的類幾乎總是需要定義拷貝控制成員來管理分配的資源,如果乙個類需要析構函式,則它幾乎也肯定需要定義移動和拷貝建構函式及移動和拷貝賦值運算子
C Primer學習筆記 13 拷貝控制
題記 本系列學習筆記 c primer學習筆記 主要目的是討論一些容易被大家忽略或者容易形成錯誤認識的內容。只適合於有了一定的c 基礎的讀者 至少學完一本c 教程 如果文中有錯誤或遺漏之處,敬請指出,謝謝!c 類中有四個不可或缺的部分,那就是建構函式 拷貝建構函式 賦值操作符和析構函式。如果類中沒有...
C 拷貝控制 學習筆記
sales data sales data const sales data orig bookno orig.bookno units sold orig.units sold revenue orig.revenue string dots 10 直接初始化 string s dots 直接初始...
13 拷貝控制
拷貝控制 一些特殊的情況使用拷貝初始化 但是使用emplace建立的元素都進行直接初始化 在函式呼叫的過程中,具有非引用型別的引數要執行拷貝初始化,當函式具有乙個非引用的返回值型別時,返回值用來初始化呼叫方的結果 這樣來總結一下直接初始化和拷貝初始化 當建構函式只有乙個形參時,就有一套預設的隱式轉換...