C++ Primer第十三章③

C++ Primer

拷贝控制

拷贝控制和资源管理

这部分我们主要介绍两个内容,通过定义不同的拷贝操作,使自定义的类的行为看起来像一个值或指针。

  • 类的行为像一个值,意味着它应该也有自己的状态。当我们拷贝一个像值的对象时,副本和原对象是完全独立的,改变副本不会对原对象有任何影响,反之亦然。
  • 行为像指针的类共享状态,当我们拷贝一个这种类的对象时,副本和原对象使用相同的底层数据,改变副本也会改变原对象,反之亦然。还记得我们的StrBlob类吗,为了实现它我们才使用了智能指针。

在我们使用过的标准库类中,标准库容器和string类的行为像一个值;shared_ptr类提供类似指针的行为;还有一种奇葩,他们的行为两个都不像,例如IO类和unique_ptr不允许拷贝或赋值。

接下来我们要实现一个类HasPtr,让它的行为像一个值;然后重新实现它,使其像一个指针:
我们的HasPtr有两个成员,一个int和一个string指针,对于内置类型int,我们就直接拷贝值好了,改变它也不符合常理;我们把重心放到string指针,它的行为决定了该类是像值还是像指针。

行为像值的类

为了像值,每个string指针指向的那个string对象,都得有自己的一份拷贝,为了实现这个目的,我们需要做以下三个微小的工作:

  1. 定义一个拷贝构造函数,完成string的拷贝,而不是拷贝指针
  2. 配套的,定义析构函数来释放string
  3. 定义一个拷贝赋值运算符来释放对象当前的string(这话待会会解释滴),并从右侧运算对象拷贝string

我们来抛代码:

class HasPtr
{
public:
    HasPtr(const string &s = string()) : ps(new string(s)), i(0){} //默认实参,列表初始化

    HasPtr(const HasPtr &p) : ps(new string(*p.ps)), i(p.i){} //拷贝构造函数

    HasPtr& operator=(const HasPtr &); //赋值运算符声明

    ~HasPtr(){delete ps;} //析构函数

private:
    string *ps;
    int i;
};

主要在于拷贝构造函数,它是有副本的,会拷贝string对象,所以析构函数要delete来释放内存。这个类可以好好看看,写得很优雅。

类值拷贝赋值运算符

情况是这样的:

a = b;

HasPtr对象出现这样的赋值时,干两件事:

  1. 销毁左侧运算对象的资源(毕竟它没用了嘛),类似析构函数
  2. 从右侧运算对象拷贝数据,类似拷贝构造函数 为了保证一个对象能为它本身赋值,我们先拷贝右侧对象,再释放左侧资源,并更新指针指向新分配的string:
    HasPtr& HasPtr::operator=(const HasPtr &rhs)
    {
     auto newp = new string(*rhs.ps); //拷贝底层string
     delete ps; //释放旧内存
     ps = newp; //从右侧对象拷贝数据到本对象
     i = rhs.i;
     return *this; //返回本对象
    }
    

定义行为像指针的类

我们可能觉得拷贝指针就行了嘛,没那么简单,你还是要释放内存啊,而且这个释放内存的时机很重要:只有当最后一个指向string的HasPtr销毁时,才能释放内存,所以啊,我们可以用shared_ptr,我们这里呢,不用智能指针,弄得麻烦些,让大家看看底层怎么实现引用计数:

class HasPtr
{
public:
    HasPtr(const string &s = string()) : ps(new string(s)), i(0),
    use(size_t(1)){} //默认实参,列表初始化

    HasPtr(const HasPtr &p) : ps(p.ps), i(p.i), use(p.use){++(*use)}
    //拷贝构造函数,要递增计数器

    HasPtr& operator=(const HasPtr &); //赋值运算符声明

    ~HasPtr() //析构函数
    {
        if(--(*use) == 0) //没人引用了才释放
        {
            delete ps;
            delete use;
        }
    }

private:
    string *ps;
    int i;
    size_t *use; //引用计数
};

赋值运算符:

HasPtr& 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; //返回本对象
}
#C++工程师#
全部评论

相关推荐

06-04 17:59
已编辑
长江大学 Java
点赞 评论 收藏
分享
评论
6
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务