日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

拷贝控制

發(fā)布時(shí)間:2025/4/16 编程问答 75 豆豆
生活随笔 收集整理的這篇文章主要介紹了 拷贝控制 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

當(dāng)定義一個(gè)類時(shí),我們顯式地或隱式地指定在此類型的對象拷貝、移動、賦值和銷毀時(shí)做什么。一個(gè)類通過定義五種特殊的成員函數(shù)來控制這些操作,包括:拷貝構(gòu)造函數(shù)(copy constructor)、拷貝賦值運(yùn)算符(copy-assignment operator)、移動構(gòu)造函數(shù)(move constructor)、移動賦值運(yùn)算符(move-assignment operator)和析構(gòu)函數(shù)(destructor)。拷貝和移動構(gòu)造函數(shù)定義了當(dāng)用同類型的另一個(gè)對象初始化本對象時(shí)做什么。拷貝和移動賦值運(yùn)算符定義了將一個(gè)對象賦予同類型的另一個(gè)對象時(shí)做什么。析構(gòu)函數(shù)定義了當(dāng)此類型對象銷毀時(shí)做什么。我們稱這些操作為拷貝控制操作(copy control)。

1,拷貝構(gòu)造函數(shù)——如果沒有為一個(gè)類定義拷貝構(gòu)造函數(shù),編譯器會為我們自動定義一個(gè),稱之為合成拷貝構(gòu)造函數(shù)

class Foo { public:Foo(); //默認(rèn)構(gòu)造函數(shù)Foo(const Foo&); //拷貝構(gòu)造函數(shù) } string dots(10,'.'); //直接初始化 string s(dots); //直接初始化 string s2=dots; //拷貝初始化 string null_book="9-999-99999-9"; //拷貝初始化 string nines=string(100,'9'); //拷貝初始化

當(dāng)使用直接初始化時(shí),編譯器使用普通函數(shù)匹配來選擇與我們提供的參數(shù)最匹配的構(gòu)造函數(shù),當(dāng)我們使用拷貝初始化時(shí),編譯器將右側(cè)運(yùn)算對象拷貝到正在創(chuàng)建的對象中,如果需要的話還要進(jìn)行類型轉(zhuǎn)換。

2,拷貝賦值運(yùn)算符——合成拷貝

class Foo{ public: Foo& operator=(const Foo&); //賦值運(yùn)算符 }

3,析構(gòu)函數(shù)——合成析構(gòu)函數(shù)不會delete一個(gè)指針數(shù)據(jù)成員

class Foo{ public:~Foo(); //析構(gòu)函數(shù) }

三/五法則

1) 需要析構(gòu)函數(shù)的類也需要拷貝和賦值操作
2) 需要拷貝操作的類也需要賦值操作,反之亦然。

使用default:當(dāng)我們在類內(nèi)用=default修飾成員的聲明時(shí),合成的函數(shù)將隱式地聲明為內(nèi)聯(lián)的,而對成員的類外定義使用=default,就像對拷貝賦值運(yùn)算符所做的那樣。

使用delete阻止拷貝和賦值,不能刪除析構(gòu)函數(shù)。

private拷貝控制,將其拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符聲明為private來阻止拷貝。將拷貝控制成員聲明為private,但不定義它們。

拷貝控制和資源管理:通常,管理類外資源的類必須定義拷貝控制成員。拷貝語義:1,類的行為像一個(gè)值;2,類的行為像一個(gè)指針,共享狀態(tài)。

1,類的行為像一個(gè)值
Hasptr需要:
1,定義一個(gè)拷貝構(gòu)造函數(shù),完成string的拷貝,而不是拷貝指針
2,定義一個(gè)析構(gòu)函數(shù)來釋放string
3,定義一個(gè)拷貝賦值運(yùn)算符來釋放對象當(dāng)前的string,并從右側(cè)運(yùn)算對象拷貝string

class Hasptr{ public:Hasptr(const std::string &s = std::string()):ps(new std::string(s)), i(0){}Hasptr(const Hasptr &p) :ps(new std::string(*p.ps)), i(p.i){}Hasptr& operator=(const Hasptr &);~Hasptr(){ delete ps; } private:std::string *ps;int i; };Hasptr& Hasptr::operator=(const Hasptr &rhs) {auto newp = new string(*rhs.ps); //拷貝底層string,自賦值操作delete ps;ps = newp;i = rhs.i;return *this; }

2,定義行為像指針的類
對于行為類似指針的類,我們需要為其定義拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符,來拷貝指針成員本身而不是它指向的string。我們的類仍然需要自己的析構(gòu)函數(shù)來釋放接受string參數(shù)的構(gòu)造函數(shù)的內(nèi)存。但是,析構(gòu)函數(shù)不能單方面地釋放關(guān)聯(lián)的string。只有當(dāng)最后一個(gè)指向string的Hasptr銷毀時(shí),它才可以釋放string。

定義一個(gè)使用引用計(jì)數(shù)的類

class Hasptr{ public:Hasptr(const std::string &s = std::string()):ps(new std::string(s)), i(0),use(new std::size_t(1)){}Hasptr(const Hasptr &p) :ps(new std::string(*p.ps)), i(p.i), use(p.use){ ++*use; }Hasptr& operator=(const Hasptr &);~Hasptr(){ } private:std::string *ps;int i;std::size_t *use; //用來記錄有多少個(gè)對象共享*ps的成員 };

類指針的拷貝成員“篡改”引用計(jì)數(shù)

Hasptr::~Hasptr() {if (--*use == 0){delete ps; //釋放string內(nèi)存delete use; //釋放計(jì)數(shù)器內(nèi)存} }Hasptr& Hasptr::operator=(const Hasptr &rhs) {++*rhs.use; //遞增右側(cè)運(yùn)算對象的引用計(jì)數(shù)if (--*use == 0){delete ps; //如果沒有其他用戶,釋放本對象分配的成員delete use;}ps = rhs.ps;i = rhs.i;use = rhs.use;return *this; }

交換操作——swap,交換兩個(gè)元素,swap函數(shù)應(yīng)該調(diào)用swap,而不是std::swap

class Hasptr{friend void swap(HasPtr&, HasPtr&); }_inline void swap(Hasptr &lhs, Hasptr &rhs) {using std::swap;swap(lhs.ps, rhs.ps); //交換指針,而不是string數(shù)據(jù)swap(lhs.i, rhs.i); //交換int成員 }

每個(gè)swap調(diào)用應(yīng)該都是未加限定的。即,每個(gè)調(diào)用都應(yīng)該是swap,而不是std::swap。如果存在類型特定的swap版本,其匹配程度會優(yōu)于std中定義的版本。同樣,在賦值運(yùn)算符中使用swap

//注意rhs是按值傳遞的,意味著HasPtr的拷貝構(gòu)造函數(shù)
//將右側(cè)運(yùn)算對象中的string拷貝到rhs

Hasptr& Hasptr::operator=(const Hasptr &rhs) {swap(*this,rhs); //rhs現(xiàn)在指向本對象曾經(jīng)使用的內(nèi)存return *this; //rhs被銷毀,從而delete了rhs中的指針 }

對象移動:1,在重新分配內(nèi)存的過程中,從舊內(nèi)存將元素拷貝到新內(nèi)存是不必要的,更好的方式是移動元素。2,使用移動而不是拷貝的另一個(gè)原因源于IO類或unique_ptr這樣的類,這些類都包含不能被共享的資源(如指針或IO緩沖)。因此,這些類型的對象不能拷貝但可以移動。

一,右值引用(rvalue reference)。通過&&而不是&來獲得右值引用,右值只能綁定到一個(gè)將要銷毀的對象。左值有持久的狀態(tài),而右值要么是字面常量,要么是在表達(dá)式求值過程中創(chuàng)建的臨時(shí)對象。右值所引用的對象將要被銷毀,該對象沒有其他用戶。使用右值引用的代碼可以自由地接管所引用的對象的資源。

int i=42; int &r=i; //正確:r引用i int &&rr=i; //錯(cuò)誤:不能將一個(gè)右值引用綁定到一個(gè)左值上 int &r2=i*42; //錯(cuò)誤:i*42是一個(gè)右值 const int &r3=i*42; //正確:我們可以將一個(gè)const的引用綁定到一個(gè)右值上 int &&rr2=i*42; //正確;將rr2綁定到乘法結(jié)果上

標(biāo)準(zhǔn)庫move函數(shù),定義在頭文件utility。

int &&rr3=std::move(rr1); //ok

move告訴編譯器:我們有一個(gè)左值,但我們希望像一個(gè)右值一樣處理它。我們必須認(rèn)識到,調(diào)用move就意味著承諾:除了對rr1賦值或銷毀它之外,我們將不再使用它。在調(diào)用move之后,我們不能對移后源對象的值做任何假設(shè)。

移動構(gòu)造函數(shù)和移動賦值運(yùn)算符,我們必須在類頭文件的聲明中和定義中(如果定義在類外的話)都指定noexcept。

class StrVec{ public:StrVec(StrVec&&) noexcept; //移動構(gòu)造函數(shù) }StrVec::StrVec(SteVec &&s) noexcept : elements(s.elements),first_free(s.first_free),cap(s.cap) {//令s進(jìn)入這樣的狀態(tài)—對其運(yùn)行析構(gòu)函數(shù)是安全的s.elements=s.first_free=s.cap=nullptr; }StrVec &StrVec::operator=(StrVec &&rhs) noexcept {//直接檢測自賦值if(this !=&rhs){free(); //釋放已有元素elements=rhs.elements; //從rhs接管資源first_free=rhs.first_free;cap=rhs.cap;//將rhs置于可析構(gòu)狀態(tài)rhs.elements=rhs.first_free=rhs.cap=nullptr;}return *this; }

只有當(dāng)一個(gè)類沒有定義任何版本的拷貝控制成員,且類的每個(gè)非static數(shù)據(jù)成員都可以移動時(shí),編譯器才會為它合成移動構(gòu)造函數(shù)或移動賦值運(yùn)算符。編譯器可以移動內(nèi)置類型的成員。如果一個(gè)成員是類類型,且該類有對應(yīng)的移動操作,編譯器才能移動這個(gè)成員:

//編譯器會為X和hasX合成移動操作 struct X{int i; // 內(nèi)置類型可以移動std::string s; //string定義了自己的移動操作 }struct hasX{X mem; //X有合成的移動操作 }X x,x2=std::move(x); //使用合成的移動構(gòu)造函數(shù) hasX hx,hx2=std::move(hx); //使用合成的移動構(gòu)造函數(shù)

另外,

hasY(hasY&&)=default; //hasY將有一個(gè)刪除的移動構(gòu)造函數(shù)

如果一個(gè)類既有移動構(gòu)造函數(shù),也有拷貝構(gòu)造函數(shù),編譯器使用普通的函數(shù)匹配規(guī)則來確定使用哪個(gè)構(gòu)造函數(shù),賦值操作的情況類似。
1,移動右值,拷貝左值
2,如果沒有移動構(gòu)造函數(shù),右值也被拷貝

總結(jié)

以上是生活随笔為你收集整理的拷贝控制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。