最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++中的Primer拷貝控制和資源管理詳解

 更新時間:2025年03月19日 09:11:49   作者:c-c-developer  
這篇文章主要介紹了C++中的Primer拷貝控制和資源管理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

拷貝控制和資源管理

通常,管理類外資源的類必須定義拷貝控制成員,這種類需要通過析構(gòu)函數(shù)來釋放對象所分配的資源。一旦一個類需要析構(gòu)函數(shù),那么它兒乎肯定也需要一個拷貝構(gòu)造函數(shù)和一個拷貝賦值運算符。

為了定義這些成員,我們首先必須確定此類型對象的拷貝語義。一般來說,有兩種選擇,可以定義拷貝操作,使類的行為看起來像一個值或者像一個指針。

類的行為像一個值,意味著它應(yīng)該也有自己的狀態(tài)。當(dāng)我們拷貝一個類值的對象時,副本和原對象是完全獨立的。改變副本不會對原對象有任何影響,反之亦然。

行為像指針的類則共享狀態(tài)。當(dāng)我們拷貝一個這種類的對象時,副本和原對象使用相同的底層數(shù)據(jù)。改變副本也會改變原對象,反之亦然。

在我們使用過的標準庫類中,標準庫容器和string類的行為像一個值。而不出意外的,shared_ptr類提供類似指針的行為,就像我們的StrBlob類一樣,IO類型和unique_ptr不允許拷貝或賦值,因此它們的行為既不像值也不像指針。

為了說明這兩種方式,我們會為練習(xí)中的HasPtr類定義拷貝控制成員。首先,我們將令類的行為像一個值;然后重新實現(xiàn)類,使它的行為像一個指針。

我們的HasPtr類有兩個成員,一個int和一個string指針。通常,類直接拷貝內(nèi)置類型(不包括指針)成員;這些成員本身就是值,因此通常應(yīng)該讓它們的行為像值一樣。我們?nèi)绾慰截愔羔槼蓡T決定了像HasPtr這樣的類是具有類值行為還是類指針行為。

行為像值的類

為了提供類值的行為,對于類管理的資源,每個對象都應(yīng)該擁有一份自己的拷貝。這意味著對于ps指向的string,每個HasPtr對象都必須有自己的拷貝。為了實現(xiàn)類值行為,HasPtr需要

  • 定義一個拷貝構(gòu)造函數(shù),完成string的拷貝,而不是拷貝指針
  • 定義一個析構(gòu)函數(shù)來釋放string
  • 定義一個拷貝賦值運算符來釋放對象當(dāng)前的string,并從右側(cè)運算對象拷貝string

類值版本的HasPtr如下所示

class HasPtr{
public:

HasPtr(const std::string &s=std::string()):
    ps(new std::string(s)1),i(0){}
    //對ps指向的string,每個BasPtr對象都有自己的拷貝
    HasPt(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;
}

我們的類足夠簡單,在類內(nèi)就已定義了除賦值運算符之外的所有成員函數(shù)。第一個構(gòu)造函數(shù)接受一個(可選的)string參數(shù)。這個構(gòu)造函數(shù)動態(tài)分配它自己的string副本,并將指向string的指針保存在ps中。拷貝構(gòu)造函數(shù)也分配它自己的string副本。析構(gòu)函數(shù)對指針成員ps執(zhí)行delete,釋放構(gòu)造函數(shù)中分配的內(nèi)存。

類值拷貝賦值運算符

賦值運算符通常組合了析構(gòu)函數(shù)和構(gòu)造函數(shù)的操作。類似析構(gòu)函數(shù),賦值操作會銷毀左側(cè)運算對象的資源。類似拷貝構(gòu)造函數(shù),賦值操作會從右側(cè)運算對象拷貝數(shù)據(jù)。但是,非常重要的一點是,這些操作是以正確的順序執(zhí)行的,即使將一個對象賦予它自身,也保證正確。而且,如果可能,我們編寫的賦值運算符還應(yīng)該是異常安全的一一當(dāng)異常發(fā)生時能將左側(cè)運算對象置于一個有意義的狀態(tài)。

在本例中,通過先拷貝右側(cè)運算對象,我們可以處理自賦值情況,并能保證在異常發(fā)生時代碼也是安全的。在完成拷貝后,我們釋放左側(cè)運算對象的資源,并更新指針指向新分配的string:

HasPtr&HasPtr::operator=(const HasPtr&rhs)
{
auto newp=new string(*rhs.ps);//指貝底層string
delete ps;//釋放舊內(nèi)存
ps = newp;//從右側(cè)運算對象指貝數(shù)據(jù)到本對象
i = rhs;
return*this;//返回本對象
}

在這個賦值運算符中,非常清楚,我們首先進行了構(gòu)造函數(shù)的工作:newp的初始化器等價于HasPtr的拷貝構(gòu)造函數(shù)中ps的初始化器。接下來與析構(gòu)函數(shù)一樣,我們delete當(dāng)前ps指向的string。然后就只剩下拷貝指向新分配的string的指針,以及從rhs拷貝int值到本對象了。

關(guān)鍵概念:賦值運算征

當(dāng)你編寫賦值運算符時:有兩點需要記住:

  • 如果將一個對象賦予它自身,賊值運算待必須能正確工作。
  • 天多數(shù)賦值運算符組各了析構(gòu)函數(shù)和拷貝構(gòu)造函數(shù)的工作。

當(dāng)你編寫一個賦值運算符時,一個好的模式是先將右側(cè)運算對象賦值到一個局部臨時對象中。當(dāng)拷貝完成后,銷毀左側(cè)運算對象的現(xiàn)有成員就是安全的了。一旦左側(cè)運算對象的資源被銷毀,就只剩下將數(shù)據(jù)從臨時對象拷貝到左側(cè)運算對象的成員中了。

為了說明防范自賦值操作的重要性,考慮如果賦值運算符如下編寫將會發(fā)生什么

//這樣編寫賦值運算符是錯誤的!
HasPtr&
HasPtr::operator=(const HasPtr&zhs)
{
    delete ps;//釋放對象指向的string
    //如果rhs和*this是同一個對象,我們就將從已釋放的內(nèi)存中拷貝數(shù)據(jù)!
    ps=new string(*(rhs.ps));
    i=rhs.i;
    return*this;
}

如果zhs和本對象是同一個對象,delete ps會釋放this和rhs指向的string。接下來,當(dāng)我們在new表達式中試圖拷貝(rhs.ps)時,就會訪問一個指向無效內(nèi)存的指針,其行為和結(jié)果是未定義的。

這樣我們的StrBlobPtr類就仍能使用指向vector的weak_ptr了。你修改后的類將需要一個拷貝構(gòu)造函數(shù)和一個拷貝賦值運算符,但不需要析構(gòu)函數(shù)。解釋拷貝構(gòu)造出數(shù)和拷貝賦值運算符必須要做什么。解釋為什么不需要析構(gòu)函數(shù)。

定義行為像指針的類

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

令一個類展現(xiàn)類似指針的行為的最好方法是使用shared_ptr來管理類中的資源。拷貝(或賦值)一個shared_ptr會拷貝(賦值)shared_ptr所指向的指針。
shared_ptr類自己記錄有多少用戶共享它所指向的對象。當(dāng)沒有用戶使用對象時,shared_ptr類負責(zé)釋放資源。

但是,有時我們希望直接管理資源。在這種情況下,使用引用計數(shù)(reference count)就很有用了。為了說明引用計數(shù)如何工作,我們將重新定義HasPtr,令其行為像指針一樣,但我們不使用shared_ptr,而是設(shè)計自己的引用計數(shù)。

引用計數(shù)

引用計數(shù)的工作方式如下:

  • 除了初始化對象外,每個構(gòu)造函數(shù)(拷貝構(gòu)造函數(shù)除外)還要創(chuàng)建一個引用計數(shù),用來記錄有多少對象與正在創(chuàng)建的對象共享狀態(tài)。當(dāng)我們創(chuàng)建一個對象時,只有一個對象共享狀態(tài),因此將計數(shù)器初始化為1。
  • 拷貝構(gòu)造函數(shù)不分配新的計數(shù)器,而是拷貝給定對象的數(shù)據(jù)成員,包括計數(shù)器。拷貝構(gòu)造函數(shù)遞增共享的計數(shù)器,指出給定對象的狀態(tài)又被一個新用戶所共享。“析構(gòu)函數(shù)遞減計數(shù)器,指出共享狀態(tài)的用戶少了一個。如果計數(shù)器變?yōu)?,則析構(gòu)函數(shù)釋放狀態(tài)。
  • 拷貝賦值運算符遞增右側(cè)運算對象的計數(shù)器,遞減左側(cè)運算對象的計數(shù)器。如果左側(cè)運算對象的計數(shù)器變?yōu)?,意味著它的共享狀態(tài)沒有用戶了,拷貝賦值運算符就必須銷毀狀態(tài)。

唯一的難題是確定在哪里存放引用計數(shù)。計數(shù)器不能直接作為HasPtr對象的成員。

下面的例子說明了原因:

HasPtr p1("HiYa!");

HasPtr p2(p1);//p1和p2指向相同的string
HasPtr p3(p1);//p1、p2和p3都指向相同的string

如果引用計數(shù)保存在每個對象中,當(dāng)創(chuàng)建p3時我們應(yīng)該如何正確更新它呢?可以遞增p1中的計數(shù)器并將其拷貝到p3中,但如何更新p2中的計數(shù)器呢?

解決此問題的一種方法是將計數(shù)器保存在動態(tài)內(nèi)存中。當(dāng)創(chuàng)建一個對象時,我們也分配一個新的計數(shù)器。當(dāng)拷貝或賦值對象時,我們拷貝指向計數(shù)器的指針。使用這種方法,副本和原對象都會指向相同的計數(shù)器。

定義一個使用引用計數(shù)的類

通過使用引用計數(shù),我們就可以編寫類指針的HasPtr版本了:

class HasPtr{
public:
//構(gòu)造函數(shù)分配新的string和新的計數(shù)器,將計數(shù)器置為1
HasPtr(const std::string&s=std::string()):
ps(newstd::string(s)),i(0),use(new std::size_t(1)){}
//指貝構(gòu)造函數(shù)拷貝所有三個數(shù)據(jù)成員,并遞增計數(shù)器
HasPtr(const HasPtr &p):
ps(p.ps),i(p.i),use(p.use){++*use;}
HasPtr &operator=(const HasPtr&);
~HasPtr();
private:
std::string* ps;
std::size_t* use;//用來記錄有多少個對象共享*ps的成員
};

在此,我們添加了一個名為use的數(shù)據(jù)成員,它記錄有多少對象共享相同的string。接受string參數(shù)的構(gòu)造函數(shù)分配新的計數(shù)器,并將其初始化為1,指出當(dāng)前有一個用戶使用本對象的string成員。

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

當(dāng)拷貝或賦值一個HasPtr對象時,我們希望副本和原對象都指向相同的string。即,當(dāng)拷貝一個HasPtr時,我們將拷貝ps本身,而不是ps指向的string。當(dāng)我們進行拷貝時,會遞增該string關(guān)聯(lián)的計數(shù)器。

(我們在類內(nèi)定義的)拷貝構(gòu)造函數(shù)拷貝給定HasPtr的所有三個數(shù)據(jù)成員。這個構(gòu)造函數(shù)還遞增use成員,指出ps和p.ps指向的string又有了一個新的用戶。

析構(gòu)函數(shù)不能無條件地delete ps一一可能還有其他對象指向這塊內(nèi)存。析構(gòu)函數(shù)應(yīng)該遞減引用計數(shù),指出共享string的對象少了一個。如果計數(shù)器變?yōu)?,則析構(gòu)函數(shù)釋放ps和use指向的內(nèi)存:

HasPtr::~HasPtr()
{
if(--*use==0){//如果引用計數(shù)變?yōu)?
delete ps;//釋放string內(nèi)存
delete use;//釋放計數(shù)器內(nèi)存
}
}

拷貝賦值運算符與往常一樣執(zhí)行類似拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù)的工作。即,它必須遞增右側(cè)運算對象的引用計數(shù)(即,拷貝構(gòu)造函數(shù)的工作),并遞減左側(cè)運算對象的引用計數(shù),在必要時釋放使用的內(nèi)存(即,析構(gòu)函數(shù)的工作)。

而且與往常一樣,賦值運算符必須處理自賦值。我們通過先遞增zhs中的計數(shù)然后再遞減左側(cè)運算對象中的計數(shù)來實現(xiàn)這一點。通過這種方法,當(dāng)兩個對象相同時,在我們檢查ps(及use)是否應(yīng)該釋放之前,計數(shù)器就已經(jīng)被遞增過了:

HasPtr& HasPtr::operator=(const HasPtr&rhs)
{
    ++*rhs.use;//通增右側(cè)運算對象的引用計數(shù)
    if(--*use==0){//然后遞減本對象的引用計數(shù)
        delete ps;//如果沒有其他用戶
        delete use;//釋放本對象分配的成員
    }
    ps=rhs.ps;//將數(shù)據(jù)從rhs拷貝到本對象
    i = rhs.i;
    use=rhs.use;
    return*this;//返回本對象
}

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • C語言實現(xiàn)小學(xué)生考試系統(tǒng)

    C語言實現(xiàn)小學(xué)生考試系統(tǒng)

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)小學(xué)生考試系統(tǒng),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-03-03
  • 非常漂亮的新年祝福!C語言實現(xiàn)漂亮的煙花效果

    非常漂亮的新年祝福!C語言實現(xiàn)漂亮的煙花效果

    非常漂亮的新年祝福!這篇文章主要介紹了C語言實現(xiàn)漂亮的煙花效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-02-02
  • 關(guān)閉顯示器軟件代碼分享

    關(guān)閉顯示器軟件代碼分享

    這篇文章主要介紹了使用程序如何關(guān)閉顯示器的代碼,需要的朋友可以參考下
    2014-01-01
  • C++使用redis的實現(xiàn)步驟

    C++使用redis的實現(xiàn)步驟

    本文主要介紹了C++中的redis-plus-plus庫來操作Redis服務(wù)器,包括連接到Redis服務(wù)器、執(zhí)行各種命令(如get、set、list、set等)、以及處理返回值和錯誤,具有一定的參考價值,感興趣的可以了解一下
    2025-05-05
  • C語言菜鳥基礎(chǔ)教程之加法

    C語言菜鳥基礎(chǔ)教程之加法

    C語言中運算符和表達式數(shù)量之多, 在高級語言中是少見的。正是豐富的運算符和表達式使C語言功能十分完善。 這也是C語言的主要特點之一。今天我們來看看加法運算
    2017-10-10
  • C++11  Lambda 表達式、std::function 與 std::bind 解析

    C++11  Lambda 表達式、std::function 與 s

    這段文章詳細解析了C++1Lambda表達式、std::function和std::bind的核心概念、語法結(jié)構(gòu)與應(yīng)用場景,特別強調(diào)了Lambda表達式在代碼緊湊性和可讀性上的優(yōu)勢,以及std::function和std::bind在解耦和類型擦除中的重要性
    2026-06-06
  • C++?CPU的局部性原理兩種類型解析

    C++?CPU的局部性原理兩種類型解析

    這篇文章給大家介紹了CPU的局部性原理,包括時間局部性、空間局部性以及如何通過緩存利用這些局部性來提高程序性能,局部性原理是現(xiàn)代計算機體系結(jié)構(gòu)和高性能編程的核心思想,感興趣的朋友跟隨小編一起看看吧
    2026-02-02
  • C語言實現(xiàn)猜拳游戲

    C語言實現(xiàn)猜拳游戲

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)猜拳游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • 數(shù)據(jù)結(jié)構(gòu)串的操作實例詳解

    數(shù)據(jù)結(jié)構(gòu)串的操作實例詳解

    這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)串的操作實例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-07-07
  • C++中模板(Template)詳解及其作用介紹

    C++中模板(Template)詳解及其作用介紹

    這篇文章主要介紹了C++中模板(Template)的詳解及其作用介紹,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09

最新評論

北海市| 尚义县| 河北省| 怀远县| 镇雄县| 蛟河市| 海晏县| 岢岚县| 衡东县| 大丰市| 沅江市| 平塘县| 贵南县| 甘谷县| 托克逊县| 都匀市| 平陆县| 广宁县| 浦北县| 濮阳县| 通渭县| 茂名市| 钟祥市| 广南县| 惠来县| 夏邑县| 咸丰县| 克什克腾旗| 民勤县| 澎湖县| 贵德县| 德安县| 阳东县| 广昌县| 马关县| 怀仁县| 蒲城县| 汉源县| 遂昌县| 桐庐县| 蓬莱市|