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

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ù)。改變副本也會改變原對象,反之亦然。

在我們使用過的標(biāo)準(zhǔn)庫類中,標(biāo)準(zhǔn)庫容器和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類負(fù)責(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)文章

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

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

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

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

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

    Qt中QPushButton組件的使用詳解

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

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

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

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

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

    C語言字符串替換空格實例詳解

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

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

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

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

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

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

    這篇文章主要為大家詳細(xì)介紹了C語言實現(xiàn)貪吃蛇小黑窗,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    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

最新評論