三五法則
阻止拷貝
定義行為像值的類
定義行為像指標的類
交換操作
物件移動
當定義乙個類時,我們顯式地或隱式地在此型別的物件拷貝、移動、複製和銷毀時做什麼。乙個類通過定義五種特殊的成員函式來控制這些操作,包括:
class
foo;
拷貝建構函式的第乙個引數必須是乙個引用型別,雖然我們可以定義乙個接受非const
引用的拷貝建構函式,但此引數幾乎總是乙個const的引用。拷貝建構函式在幾種情況下都會被隱式地使用。因此,拷貝建構函式通常不應該是explicit的
class
foo
賦值運算子通常應該返回乙個指向左側運算物件的引用
當我們決定乙個類是否要定義它自己版本的拷貝控制成員時,乙個基本原則是首先確定這個類是否需要乙個析構函式。通常,對析構函式的需求要比拷貝建構函式或賦值運算子的需求更加明顯。如果這個類需要乙個析構函式,我們幾乎肯定它也需要乙個拷貝建構函式和乙個拷貝複製運算子
(乙個類有析構函式肯定是有分配在堆上的記憶體需要釋放,那麼如果不定義拷貝建構函式和拷貝複製運算子編譯器就會呼叫合成版本導致淺拷貝,即多個物件指向同乙個記憶體)
我們可以通過將拷貝控制成員定義為=default來顯示地要求編譯器生成合成的版本
class
sales_ data
;sales_ data& sales_ data:
:operator=(
const sales_ data&)=
default
;
我們只能對具有合成版本的成員函式使用=default(即,預設建構函式或拷貝控制成員)
雖然大多數類應該定義拷貝建構函式和拷貝賦值運算子,但對某些類來說,這些操作沒有合理的意義。在此情況下,定義類時必須採用某種機制阻止拷貝或賦值。例如,iostream類阻止了拷貝,以避免多個物件寫入或讀取相同的io緩衝。為了阻止拷貝,看起來可能應該不定義拷貝控制成員。但是,這種策略是無效的;如果我們的類未定義這些操作,編譯器為它生成合成的版本。
在新標準下,我們可以通過將拷貝建構函式和拷貝賦值運算子定義為刪除的函式來組織拷貝。刪除的函式是這樣一種函式:我們雖然宣告了它們,但不能以任何方式使用它們。在函式的引數列表後面加上=delete來指出我們希望將它定義為刪除的。
struct nocopy
行為像值的類即每個物件之間沒有關聯,就跟值一樣,如int類,string類等
class
hasptr
//對ps指向的string,每個hasptr物件都有自已的拷貝
hasptr (
const hasptr &p)
: ps (
new std:
:string
(*p.ps)),
i(p.i)
hasptr&
operator=(
const hasptr &);
~hasptr()
private
: std:
:string *ps;
int i;};
hasptr& hasptr:
:operator=(
const hasptr &rhs)
行為像指標的類是多個物件共享底層資料,只有最後乙個物件析構後底層資料才會釋放。
對於行為類似指標的類,我們需要為定義拷貝建構函式和拷貝賦值運算子,來拷貝指標成員本身而不是它指向的資料。
令乙個類展現類似指標的行為的最好方法是使用shared_ptr來管理類中的資源。拷貝(或賦值)乙個shared_ptr會拷貝(賦值)shared_ptr所指向的指標。shared_ptr類自己記錄有多少使用者共享它所指向的物件。當沒有使用者使用物件時,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的函式。
class
hasptr
;inline
void swap (hasptr &lhs, hasptr &rhs)
新標準的乙個最主要的特性是可以移動而非拷貝物件的能力。很多情況下都會發生物件拷貝。在其中某些情況下,物件拷貝後就立即被銷毀了。在這些情況下,移動而非拷貝物件會大幅度提公升效能。使用移動而不是拷貝的另乙個原因源於io類或unique_ptr這樣的類。這些類都包含不能被共享的資源。因此,這些型別的物件不能拷貝但可以移動。
為了支援移動操作,新標準引入了一種新的引用型別——右值引用, 所謂右值引用就是必須繫結到右值的引用。我們通過&&而不是&來獲得右值引用。如我們將看到的,**右值引用有乙個重要的性質——只能繫結到乙個將要銷毀的物件。**因此,我們可以自由地將乙個右值引用的資源「移動」到另乙個物件中。
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繫結到乘法結果上
由於右值引用只能幫繫結到臨時物件,我們得知:
這兩個特性意味著:使用右值引用的**可以自由地接管所引用的物件的資源
我們可以通過呼叫乙個名為move的新標準庫函式來獲得繫結到左值上的右值引用
int
&& rr3 = std::
move
(rr1)
;
move呼叫高速編譯器:我們有乙個左值,但我們希望像乙個右值一樣處理它。我們必須認識到,呼叫move就意味著承諾:除了對rr1賦值或銷毀它外,我們將不再使用它。在呼叫move之後,我們不能對移後源物件的值做任何假設。
為了讓我們自己的型別支援移動操作,需要為其定義移動建構函式和移動複製運算子,這兩個成員類似對應的拷貝操作,但它們從給定物件「竊取」資源而不是拷貝資源。
strvec:
:strvec (strvec &&s)
noexcept
//移動操作不應丟擲任何異常
//成員初始化器接管s中的資源
: elements (s.elements), first_ free
(s.first_ free), cap
(s. cap)
strvec &strvec:
:operator
=(strvec &&rhs)
noexcept
return
*this
;}
由於移動操作「竊取"資源,它通常不分配任何資源。因此,移動操作通常不會丟擲任何異常。當編寫乙個不丟擲異常的移動操作時,我們應該將此事通知標準庫。我們將看到,除非標準庫知道我們的移動建構函式不會丟擲異常,否則它會認為移動我們的類物件時可能會丟擲異常,並且為了處理這種可能性而做一些額外的工作。
只有當乙個類沒有定義任何自己版本的拷貝控制成員,且它的所有資料成員都能移動構造或移動賦值時,編譯器才會為它合成移動建構函式或移動複製運算子。
C 隨筆之 拷貝控制
拷貝建構函式 第乙個引數必須是引用型別。原因 如果引數不是引用型別 在呼叫拷貝建構函式時,一定會拷貝它的實參,如果要拷貝實參就必定會呼叫它的拷貝構造。那就會這樣無限迴圈下去。拷貝構造函式呼叫的時期 1.用 定義變數 2.將乙個物件作為實參傳遞給乙個非引用型別的形參 3.從乙個返回值型別為非引用型別的...
C 拷貝控制
本文主要討論c 類定義中的拷貝控制 copy control 拷貝建構函式 賦值操作符和析構函式。如果文中有錯誤或遺漏之處,敬請指出,謝謝!c 類中有四個不可或缺的部分,那就是建構函式 拷貝建構函式 賦值操作符和析構函式。如果類中沒有定義這些函式,那麼編譯器將為類自動生成這些函式。當然,你也可以通過...
C 拷貝控制
當定義乙個類時,顯示或隱式地指定了此型別的物件在拷貝 賦值和銷毀時所執行的操作,通過三個特殊的成員函式來控制這些操作,分別是拷貝建構函式,賦值運算子和析構函式。拷貝建構函式定義了當使用同型別的另乙個物件初始化新物件時的操作,賦值運算子定義了將乙個物件賦值給同型別的另乙個物件時的操作,析構函式定義了此...