【C++】拷贝控制与资源管理
1. 拷貝控制與資源管理
管理類外資源的類必須定義拷貝控制成員。如P447中所見,這種類需要通過析構(gòu)函數(shù)來釋放對(duì)象所分配的資源。一旦一個(gè)類需要析構(gòu)函數(shù),那么幾乎可確定它也需要一個(gè)拷貝構(gòu)造函數(shù)和一個(gè)拷貝賦值函數(shù)。
明確拷貝語(yǔ)義:可以定義拷貝操作,使類的行為看起來像一個(gè)值或者一個(gè)指針。
類的行為像一個(gè)值,意味著他應(yīng)該有自己的狀態(tài)。深拷貝,副本與本身獨(dú)立。
行為像指針的類則是一個(gè)共享狀態(tài)。淺拷貝,副本與原對(duì)象使用相同的底層數(shù)據(jù)。改變副本也會(huì)改變?cè)瓕?duì)象,反之亦然。
CP5_ex13_11_h 實(shí)例1:我們將在下一節(jié)中展示復(fù)制控制成員的定義。但是,您已經(jīng)知道了實(shí)現(xiàn)這些成員所需要知道的一切。在繼續(xù)閱讀之前,先寫HasPtr復(fù)制構(gòu)造函數(shù)和復(fù)制賦值操作符。
假設(shè)我們希望HasPtr的行為像一個(gè)值。也就是說,每個(gè)對(duì)象都應(yīng)該有自己的對(duì)象所指向的字符串副本。
#include <string>
?
class HasPtr {
public:HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { }HasPtr(const HasPtr &hp) : ps(new std::string(*hp.ps)), i(hp.i) { }HasPtr& operator=(const HasPtr &hp) {auto new_p = new std::string(*hp.ps);delete ps;ps = new_p;i = hp.i;return *this;}~HasPtr() { delete ps;}
private:std::string *ps;int i;
};
2. 行為像值的類:意味著他應(yīng)該有自己的狀態(tài)。深拷貝,副本與本身獨(dú)立。
class HasPtr {
public:HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0) { }//對(duì)ps指向的每一個(gè)string,每個(gè)HasPtr對(duì)象都有自己的拷貝HasPtr(const HasPtr &hp) : ps(new std::string(*hp.ps)), i(hp.i) { }HasPtr& operator=(const HasPtr &hp) {auto new_p = new std::string(*hp.ps);//拷貝底層stringdelete ps; //釋放舊內(nèi)存,當(dāng)發(fā)生異常時(shí),能將左側(cè)對(duì)象置于以惡搞有意義的狀態(tài)ps = new_p; //更新指針指向新的分配的stringi = hp.i; //從右側(cè)數(shù)據(jù)拷貝數(shù)據(jù)到本對(duì)象 return *this; //返回本對(duì)象}~HasPtr() { delete ps;}
private:std::string *ps;int i;
};
賦值運(yùn)算符中做了三件事情:
首先,就行了構(gòu)造函數(shù)的工作:new_p的初始化器等價(jià)于HasPtr的拷貝構(gòu)造函數(shù)中的ps的初始化器。
接著,與析構(gòu)函數(shù)一樣,我們delete當(dāng)前ps指向的string。
然后,拷貝指向新分配的string指針,以及將i拷貝int值到本對(duì)象。
HasPtr& operator=(const HasPtr &hp) {auto new_p = new std::string(*hp.ps);//拷貝底層stringdelete ps; //釋放舊內(nèi)存,當(dāng)發(fā)生異常時(shí),能將左側(cè)對(duì)象置于以惡搞有意義的狀態(tài)ps = new_p; //更新指針指向新的分配的stringi = hp.i; //從右側(cè)數(shù)據(jù)拷貝數(shù)據(jù)到本對(duì)象 return *this; //返回本對(duì)象}
所以本質(zhì)上:賦值運(yùn)算符是組合了析構(gòu)函數(shù)和拷貝構(gòu)造函數(shù)的工作。這里的順序很重要,必須先拷貝,再釋放
一個(gè)好的模式是:先將右側(cè)對(duì)象拷貝到一個(gè)臨時(shí)對(duì)象中,當(dāng)拷貝完成時(shí),銷毀左側(cè)運(yùn)算對(duì)象的現(xiàn)有成員就安全了。
一旦左側(cè)對(duì)象先被釋放或資源被銷毀,就只剩下將數(shù)據(jù)從臨時(shí)對(duì)象拷貝到右側(cè)運(yùn)算對(duì)象的成員中。
3. 行為像指針的類:是一個(gè)共享狀態(tài)。淺拷貝,副本與原對(duì)象使用相同的底層數(shù)據(jù)。改變副本也會(huì)改變?cè)瓕?duì)象。
對(duì)于行為類似指針的類,我們需要為其定義拷貝構(gòu)造函數(shù)和拷貝賦值運(yùn)算符,來拷貝指針成員本身而不是它指向的 string。我們的類仍然需要自己的析構(gòu)函數(shù)來釋放接受string參數(shù)的構(gòu)造函數(shù)分配的內(nèi)存(P447)。但是,在本例中,析構(gòu)函數(shù)不能單萬(wàn)面地釋放關(guān)聯(lián)的string。只有當(dāng)最后一個(gè)指向string的HasPtr銷毀時(shí),才可以釋放string。
令一個(gè)類展現(xiàn)類似指針的行為的最好方法是使用shared ptr來管理類中的資源。但是,有時(shí)我們希望直接管理資源。在這種情況下,使用引用計(jì)數(shù)(reference count)(P402)就很有用了。為了說明引用計(jì)數(shù)如何工作,我們將重新定義HasPtr,令其行為像指針一樣,但我們不使用shared_ptr,而是設(shè)計(jì)自己的引用計(jì)數(shù)。
引用計(jì)數(shù)的工作方式如下:
除了初始化對(duì)象外,每個(gè)構(gòu)造函數(shù)(拷貝構(gòu)造函數(shù)除外)還要?jiǎng)?chuàng)建一個(gè)引用計(jì)數(shù),用來記錄有多少對(duì)象與正在創(chuàng)建的對(duì)象共享狀態(tài)。當(dāng)我們創(chuàng)建一個(gè)對(duì)象時(shí), 只有一個(gè)對(duì)象共享狀態(tài),因此將計(jì)數(shù)器初始化為
拷貝構(gòu)造函數(shù)不分配新的計(jì)數(shù)器,而是拷貝給定對(duì)象的數(shù)據(jù)成員,包括計(jì)數(shù)器。拷貝構(gòu)造函數(shù)遞增共享的計(jì)數(shù)器,指出給定對(duì)象的狀態(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)算對(duì)象的計(jì)數(shù)器,遞堿左側(cè)運(yùn)算對(duì)象的計(jì)數(shù)器。如果左側(cè)運(yùn)算對(duì)象的計(jì)數(shù)器變?yōu)?,意味著它的共享狀態(tài)沒有用戶了,拷貝賦值運(yùn)算符就必須銷毀狀態(tài)。
定義一個(gè)使用引用計(jì)數(shù)的類,類指針的拷貝成員,“篡改”引用計(jì)數(shù)
class HasPtr {
public://構(gòu)造分配新的string和新的計(jì)數(shù)器,將計(jì)數(shù)器置為1HasPtr(const std::string &s = std::string()) : ps(new std::string(s)), i(0),use(new std::size_t(1)) { }//拷貝構(gòu)造函數(shù),拷貝所有的3個(gè)數(shù)據(jù)成員,并遞增計(jì)數(shù)器HasPtr(const HasPtr &p) : ps(p.ps), i(p.i),use(p.use) {++ *use; }//HasPtr& operator=(const HasPtr &hp) {}~HasPtr() {} private:std::string *ps;int i;std::size_t *use;//用來記錄有多少個(gè)對(duì)象共享 *ps 的成員
};
如上,當(dāng)拷貝或賦值一個(gè)HasPtr對(duì)象時(shí),希望副本和原對(duì)象都指向相同的string。即拷貝HasPtr對(duì)象時(shí),我們拷貝ps本身,而不是ps指向的string。當(dāng)我們拷貝時(shí),還會(huì)遞增string關(guān)聯(lián)的計(jì)數(shù)器。
當(dāng)拷貝構(gòu)造函數(shù)給定HasPtr的所有3個(gè)函數(shù)成員時(shí),構(gòu)造函數(shù)還遞增 use成員,指出ps和p.ps指向的string又有一個(gè)新的用戶。
析構(gòu)函數(shù)不能無(wú)條件的delete ps,可能還有其他對(duì)象指向這塊內(nèi)存。析構(gòu)函數(shù)應(yīng)該遞減引用計(jì)數(shù),指出共享string的對(duì)象少了一個(gè)。如果計(jì)數(shù)器變?yōu)?,則析構(gòu)函數(shù)釋放ps以及use指向的指針。
HasPtr::~HasPtr() {if(-- *use == 0){delete ps;delete use;}
}
拷貝賦值運(yùn)算符一樣執(zhí)行拷貝構(gòu)造函數(shù)與析構(gòu)函數(shù)的工作。即,遞增右側(cè)運(yùn)算對(duì)象的引用計(jì)數(shù),遞減左側(cè)運(yùn)算對(duì)象的引用計(jì)數(shù),必要時(shí)釋放內(nèi)存。
HasPtr& HasPtr::operator= (const HasPtr &rhs)++*rhs.use; //遞增右側(cè)對(duì)象的引用計(jì)數(shù)if(*--*use == 0){ //然后,遞減本對(duì)象的引用計(jì)數(shù)delete ps;delete use;}ps = rhs.ps; //將數(shù)據(jù)從rhs中拷貝到對(duì)象i =rhs.i;use=rhs.use;return *this //返回本對(duì)象
}
Exercise 13.27 P475:定義自己的引用計(jì)數(shù)版本的HasPtr。
#include <string>class HasPtr {
public:HasPtr(const std::string &s = std::string()) :ps(new std::string(s)), i(0), use(new size_t(1)) { }HasPtr(const HasPtr &hp) : ps(hp.ps), i(hp.i), use(hp.use) { ++*use; }HasPtr& operator=(const HasPtr &rhs) {++*rhs.use;if (--*use == 0) {delete ps;delete use;}ps = rhs.ps;i = rhs.i;use = rhs.use;return *this;}~HasPtr() {if (--*use == 0) {delete ps;delete use;}}
private:std::string *ps;int i;size_t *use;
};
Exercise 13.28:給定以下類,實(shí)現(xiàn)默認(rèn)構(gòu)造函數(shù)和必要的復(fù)制控制成員。
#include <string>
using std::string;class TreeNode {
public:TreeNode() : value(string()), count(new int(1)), left(nullptr), right(nullptr) { }TreeNode(const TreeNode &rhs) : value(rhs.value), count(rhs.count), left(rhs.left), right(rhs.right) { ++*count; }TreeNode& operator=(const TreeNode &rhs);~TreeNode() {if (--*count == 0) {delete left;delete right;delete count;}}private:std::string value;int *count;TreeNode *left;TreeNode *right;
};class BinStrTree {
public:BinStrTree() : root(new TreeNode()) { }BinStrTree(const BinStrTree &bst) : root(new TreeNode(*bst.root)) { }BinStrTree& operator=(const BinStrTree &bst);~BinStrTree() { delete root; }private:TreeNode *root;
};
總結(jié)
以上是生活随笔為你收集整理的【C++】拷贝控制与资源管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 有没有好看的科幻灾难电影!
- 下一篇: 【OpenCV】OpenCV中积分图函数