欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

拷貝控制和資源管理

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

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

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

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

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

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

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

行為像值的類

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

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

類值版本的HasPtr如下所示

class HasPtr{
public:

HasPtr(const std::string &s=std::string()):
    ps(new std::string(s)1),i(0){}
    //對ps指向的string,每個(gè)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)就已定義了除賦值運(yùn)算符之外的所有成員函數(shù)。第一個(gè)構(gòu)造函數(shù)接受一個(gè)(可選的)string參數(shù)。這個(gè)構(gòu)造函數(shù)動(dòng)態(tài)分配它自己的string副本,并將指向string的指針保存在ps中??截悩?gòu)造函數(shù)也分配它自己的string副本。析構(gòu)函數(shù)對指針成員ps執(zhí)行delete,釋放構(gòu)造函數(shù)中分配的內(nèi)存。

類值拷貝賦值運(yùn)算符

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

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

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

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

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

當(dāng)你編寫賦值運(yùn)算符時(shí):有兩點(diǎn)需要記住:

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

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

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

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

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

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

定義行為像指針的類

對于行為類似指針的類,我們需要為其定義拷貝構(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è)類展現(xiàn)類似指針的行為的最好方法是使用shared_ptr來管理類中的資源??截?或賦值)一個(gè)shared_ptr會拷貝(賦值)shared_ptr所指向的指針。
shared_ptr類自己記錄有多少用戶共享它所指向的對象。當(dāng)沒有用戶使用對象時(shí),shared_ptr類負(fù)責(zé)釋放資源。

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

引用計(jì)數(shù)

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

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

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

下面的例子說明了原因:

HasPtr p1("HiYa!");

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

總結(jié)

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

相關(guān)文章

  • Qt利用QNetwork實(shí)現(xiàn)上傳數(shù)據(jù)的示例代碼

    Qt利用QNetwork實(shí)現(xiàn)上傳數(shù)據(jù)的示例代碼

    這篇文章主要為大家詳細(xì)介紹了Qt如何利用QNetwork實(shí)現(xiàn)上傳數(shù)據(jù)的 功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-02-02
  • C++三色球問題描述與算法分析

    C++三色球問題描述與算法分析

    這篇文章主要介紹了C++三色球問題描述與算法分析,結(jié)合注釋形式詳細(xì)講述了三色球問題的描述與相應(yīng)的算法設(shè)計(jì)思路,并給出了相關(guān)的實(shí)現(xiàn)方法,需要的朋友可以參考下
    2016-05-05
  • Qt中QPushButton組件的使用詳解

    Qt中QPushButton組件的使用詳解

    QPushButton是Qt庫中的一個(gè)重要組件,本文主要介紹了Qt中QPushButton組件的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-07-07
  • C++學(xué)習(xí)筆記std::vector底層原理及擴(kuò)容

    C++學(xué)習(xí)筆記std::vector底層原理及擴(kuò)容

    這篇文章主要為大家介紹了C++學(xué)習(xí)之std::vector底層原理及擴(kuò)容詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • 歸并排序的遞歸實(shí)現(xiàn)與非遞歸實(shí)現(xiàn)代碼

    歸并排序的遞歸實(shí)現(xiàn)與非遞歸實(shí)現(xiàn)代碼

    以下是對歸并排序的遞歸實(shí)現(xiàn)與非遞歸實(shí)現(xiàn)代碼進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下
    2013-08-08
  • C語言字符串替換空格實(shí)例詳解

    C語言字符串替換空格實(shí)例詳解

    這篇文章主要為大家詳細(xì)介紹了C語言字符串替換空格實(shí)例,使用數(shù)據(jù)庫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • C語言static修飾函數(shù)詳細(xì)解析

    C語言static修飾函數(shù)詳細(xì)解析

    以下是對C語言中的static修飾函數(shù)進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過來參考下
    2013-08-08
  • C/C++ Qt 自定義Dialog對話框組件應(yīng)用案例詳解

    C/C++ Qt 自定義Dialog對話框組件應(yīng)用案例詳解

    有時(shí)候我們需要一次性修改多個(gè)數(shù)據(jù),使用默認(rèn)的模態(tài)對話框似乎不太夠用,此時(shí)我們需要自己創(chuàng)建一個(gè)自定義對話框。這篇文章主要介紹了Qt自定義Dialog對話框組件的應(yīng)用,感興趣的同學(xué)可以學(xué)習(xí)一下
    2021-11-11
  • C語言實(shí)現(xiàn)貪吃蛇小黑窗

    C語言實(shí)現(xiàn)貪吃蛇小黑窗

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)貪吃蛇小黑窗,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • 一篇文章帶你用C語言玩轉(zhuǎn)結(jié)構(gòu)體

    一篇文章帶你用C語言玩轉(zhuǎn)結(jié)構(gòu)體

    本文主要介紹C語言 結(jié)構(gòu)體的知識,學(xué)習(xí)C語言肯定需要學(xué)習(xí)結(jié)構(gòu)體,這里詳細(xì)說明了結(jié)構(gòu)體并附示例代碼,供大家參考學(xué)習(xí),有需要的小伙伴可以參考下
    2021-09-09

最新評論