c++拷贝控制和资源管理

什么时候需要定义拷贝控制成员

通常,管理类外资源必须定义拷贝控制成员。这种类需要通过析构函数来释放对象所分配的资源。
一个类需要析构韩式,那么它几乎肯定也需要一个拷贝构造函数和一个拷贝赋值运算符。

拷贝语义

  1. 类的行为像一个值,当我们拷贝一个像值的对象时,副本和原对象时完全独立,改变副本不会影响原对象。
    标准库容器string类的行为像一个值。
  2. 行为像指针则共享状态,当我们拷贝一个这种类的对象时,副本和原对象使用相同的底层数据。改变副本也会改变原对象。
    shared_ptr类提供类似指针的行为,IO类型和unique_ptr不允许拷贝或赋值,因此它们的行为既不像值也不像指针。

行为像值的类

class 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){}
  //拷贝赋值运算符
  HasPtr& opeartor=(const HasPtr& rhs)
  {
    auto newp = new std::string(*rhs.ps); //拷贝底层string
    delete ps; //释放旧内存
    ps = newp; 
    i = rhs.i;
    return *this; //返回本对象
  }
  ~HasPtr() {delete ps;}
private:
  std::string *ps;
  int i;
};

关键概念:赋值运算符
1.如果将一个对象赋值予它自身,赋值运算符必须能正确工作。
2.大多数赋值运算符组合了析构函数和拷贝构造函数的工作。

//这样编写赋值运算符时错误的!
HasPtr& 
HasPtr::operator=(const HasPtr& rhs)
{
  delete ps; //释放对象指向的string
  //如果rhs和*this是同一对象,我们就将从已释放的内存中拷贝数据!
  ps = new std::string(*rhs.ps);
  i = rhs,i;
  return *this;
}

如果rhs和本对象是同一个对象,delete ps会释放this和rhs指向的string。接下聊,当
我们new表达式中试图拷贝
rhs.ps时,就会访问一个指向无效内存的指针,其行为和结果是未定义的。

定义行为像指针的类

class HasPtr
{
public:
  HasPtr(const std::string &s = std::string())
    :ps(new std::string(s)),i(0),use(new std::size_t(1)) {}
  HasPtr(const HasPtr &p)
    :ps(p.ps),i(p.i),use(p.use) { ++*use;}
  HasPtr& operator=(const HasPtr& rhs)
  {
    ++*rhs.use;  //递增右侧运算符对象的引用计数
    if(--*use == 0) //然后递减本对象的引用计数
    {
      delete ps;  //如果没有其他用户
      delete use; //释放本对象分配的成员
    }
    ps = rhs.ps; //将数据从rhs拷贝到本对象
    i = rhs.i;
    use = rhs.use;
    return *this; //返回本对象
  }
  ~HasPtr()
  {
    if(--*use == 0) //如果引用计数变为0
    {
      delete ps; //释放string内存
      delete use; //释放计数器内存
    }
  }
private:
  std::string *ps;
  int i;
  std::size_t *use;
};

赋值运算符与往常一样执行类似拷贝构造函数和析构函数的工作。即,它必须递增右侧运算对象的引用计数,并递减左侧
运算对象的引用计数,在必要时释放使用的内存。而且,赋值运算符必须处理自赋值。我们通过先递增rhs中的计数然后再递
减左侧运算对象中的计数来实现这一点。

练习题如下:

class TreeNode
{
public:
  TreeNode()
    :value(std::string()),count(new int(0)),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)
  {
    ++*rhs.count;
    if(--*count == 0)
    {
      if(left) delete left;
      if(right) delete right;
      delete count;
    }
    value = rhs.value;
    left = rhs.left;
    right = rhs.right;
    count = rhs.count;
  }
  ~TreeNode()
  {
    if(--*count == 0)
    {
      if(left)
      {
        delete left;
        left = nullptr;
      }
      if(right)
      {
        delete right;
        right = nullptr;
      }
      delete count;
      count = nullptr;
    }
  }
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& rhs)
  {
    TreeNode *new_root = new TreeNode(*rhs.root);
    delete root;
    root = new_root;
    return *this;
  }
  ~BinStrTree(){delete root;}
private:
  TreeNode *root;
};
posted @ 2021-06-17 10:09  一瞬光阴  阅读(60)  评论(0)    收藏  举报