C++拷貝控制與資源管理

C++拷貝控制與資源管理

參考《C++ Primer》P452 拷貝控制與資源管理

當一個類需要自定義析構函數,那麼它幾乎肯定也需要定義拷貝構造函數和拷貝複製運算符。 如下例子:

class HasPtr {
public:
  HasPtr(const std::string &s = std::string()):
    ps(new std::string(s)),i(0) {}
  ~HasPtr() {delete ps;}
  //此時需要定義一個拷貝構造函數和一個拷貝賦值運算符
};

如果我們沒有定義拷貝構造函數和拷貝賦值運算符,那麼會調用類的合成拷貝構造函數和合成拷貝賦值運算符,這些函數簡單拷貝指針成員,意味着多個HasPtr對象可能指向相同的內存。

HasPtr f(HasPtr hp)
{
  HasPtr ret = hp;    //拷貝給定的HasPtr
  //處理ret
  return ret;         //ret和hp被銷燬
}

當f返回時,hp和ret都被銷燬,在這兩個對象上都會調用HasPtr的析構函數,析構函數會調用delete ret和hp的指針成員,但這兩個指針指向相同的內存塊,則導致了指針被delete兩次的錯誤。

定義行爲像值的類

爲了提供類值的行爲,需要:
- 定義一個拷貝構造函數,完成string的拷貝,而不是指針拷貝;
- 定義一個析構函數來釋放string;
- 定義一個拷貝複製運算符來釋放對象當前的string,並從右側運算對象拷貝string;

clas 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) {}  //拷貝string的值,而不是ps指針
    HasPtr& operator=(const HasPtr&);
    ~HasPtr() {delete ps;}
private:
    std::string *ps;
    int i;
};
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
    auto newp = string(*rhs.ps); //首先將右側運算對象拷貝到局部臨時變量中。
    delete ps;                   //再刪除左側原始成員
    ps = newp;                   //賦予新的成員值
    i = rhs.i;
    return *this;                //返回對象本身
}

需要注意的是:
如果將一個對象賦予它自身,賦值運算符必須保證正確。
大多數賦值運算符組合了析構函數和拷貝構造函數的工作。

定義行爲像指針的類

相比值行爲類,指針行爲類需要拷貝的是指針,會出現多個對象指向同一內存塊的行爲,則我們需要加入引用計數,計數器需要放置在動態內存中,當創建一個新的對象時,我們分配一個新的計數器,當拷貝或賦值對象時,我們拷貝指向計數器的指針。

class HasPtr{
public:
  HasPtr(const string &s=string()):
    ps(new string(s)),i(0),use(new size_t(1)){}
  HasPtr(const HasPtr &p):        //拷貝指針,計數器加1
    ps(p.ps),i(p.i),use(p.use){++ *use}
  HasPtr& operator=(const HasPtr &);
  ~HasPtr();
private:
  string *ps;
  int i;
  size_t *use;
};

HasPtr::~Hasptr() {
  if(-- *use==0) {   //計數器減1,如果引用計數變爲0,則釋放string和計數器內存
    delete ps;
    delete use;
  }
}
HasPtr& HasPtr::operator=(const HasPtr &rhs) {
  ++ *rhs.user;      //右值對象計數器加1
  if(--*use==0) {    //左值對象計數器減1,如果計數器爲0,則釋放內存
    delete ps;
    delete use;
  }
  ps = rhs.ps;
  i = rhs.i;
  use = rhs.use;
  return *this;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章