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

拷贝控制

前文我们介绍了HasPtr类的拷贝控制,实现了行为像值的类,所谓行为像值的类就是我们所说的深拷贝,将一个类对象拷贝给另一个类对象时,其所有的成员都作为副本在新的类对象创建一遍,如果是指针类型的成员,则将指针指向的空间的数据复制一份给新对象,这样两个对象所有的成员都不关联,实现了深拷贝,不会受彼此影响。比如之前的HasPtr的拷贝构造

HasPtr::HasPtr(const HasPtr &hp)
{
    cout << "this is copy construtor" << endl;
    if (&hp != this)
    {
        this->m_str = new string(string(*hp.m_str));
        int seconds = time((time_t *)NULL);
        _curnum++;
        this->m_index = _curnum;
    }

    return;
}

下面我们介绍另一只拷贝控制,行为类似于指针的类,所谓行为类似于指针的类就是在类对象进行拷贝构造时,如果有指针成员,则浅拷贝,将指针的值赋值给新的对象,这两个对象共享同一个指针成员的指针。
我们同样实现另一个类SharePtr类,其内部有一个指针成员ps,现在我们实现这样的需求,多个SharePtr之间拷贝构造或者赋值,将ps做浅拷贝,也就是直接赋值给新对象,当所有SharePtr对象销毁后,才销毁成员ps。简单来讲,SharePtr的声明如下

class SharePtr
{
public:
    //构造函数根据字符串值构造一个字符串指针,并且初始化引用计数为1
    SharePtr(const std::string str = "") : pstr(new string(str)),
                                           usecount(new size_t(1)) {}
    SharePtr(const SharePtr &sptr);
    SharePtr &operator=(const SharePtr &sptr);

private:
    //共享的字符串指针
    string *pstr;
    //引用计数
    size_t *usecount;
};

usecount用来管理引用计数,表示多少个SharePtr类对象共享字符串指针。
pstr字符串指针,在SharePtr对象拷贝构造和赋值等操作时,将pstr赋值给新对象,实现浅拷贝。
接下来我们实现拷贝构造和拷贝赋值

SharePtr &SharePtr::operator=(const SharePtr &sptr)
{
    //如果是自赋值则跳过
    if (&sptr == this)
    {
        return *this;
    }

    //减少自己原来的引用计数
    (*this->usecount)--;

    //判断引用计数是否为0
    if (*(this->usecount) == 0)
    {
        //回收原来指向的内存
        delete (this->pstr);
        //回收引用计数内存
        delete (this->usecount);
    }

    //其他类对象拷贝给自己
    this->pstr = sptr.pstr;

    //对方引用计数增加
    (*sptr.usecount)++;
    //拷贝对方的引用计数给自己
    this->usecount = sptr.usecount;
    return *this;
}

SharePtr::SharePtr(const SharePtr &sptr)
{
    //如果是自赋值则跳过
    if (&sptr == this)
    {
        return;
    }
    //其他类对象拷贝给自己
    this->pstr = sptr.pstr;
    //引用计数增加
    (*sptr.usecount)++;
    //拷贝对方的引用计数给自己
    this->usecount = sptr.usecount;
}

实现了拷贝构造和拷贝赋值,首先都要判断是否是自拷贝或者自赋值,如果不是自拷贝,那就将对方的引用计数增加1并且赋值给自己。对于赋值运算,先将自己原来指向的引用计数-1,如果引用计数为0则回收原来开辟的内存。然后将被拷贝的对象引用计数+1,然后赋值给自己。
对于析构函数,先减少引用计数,如果引用计数为0则回收内存

SharePtr::~SharePtr()
{
    //引用计数-1
    (*this->usecount)--;
    //引用计数为0,销毁内存
    if (*(this->usecount) == 0)
    {
        cout << "use count is 0 dealloc"
             << endl;
        delete usecount;
        delete pstr;
        return;
    }
}

接下来我们实现一个打印引用计数的函数,一个测试函数

//获取引用计数
size_t SharePtr::getUseCount()
{
    return *(this->usecount);
}

void test_share()
{
    SharePtr sptr1("hello zack");
    SharePtr sptr2(sptr1);
    cout << "sptr1 use count is "
         << sptr1.getUseCount() << endl;
    cout << "sptr2 use count is "
         << sptr2.getUseCount() << endl;
    SharePtr sptr3("hello world");
    cout << "sptr3 use count is "
         << sptr3.getUseCount() << endl;
    sptr2 = sptr3;
    cout << "sptr1 use count is "
         << sptr1.getUseCount() << endl;
    cout << "sptr2 use count is "
         << sptr2.getUseCount() << endl;
    cout << "sptr3 use count is "
         << sptr3.getUseCount() << endl;
}

在主函数中调用test_share结果输出如下

sptr1 use count is 2
sptr2 use count is 2
sptr3 use count is 1
sptr1 use count is 1
sptr2 use count is 2
sptr3 use count is 2
use count is 0 dealloc
use count is 0 dealloc

因为我们只开辟了两份对象,sptr1和sptr3,所以最后会回收两个对象。sptr2是拷贝构造生成的,共享了sptr1的数据。
可以看到拷贝赋值减少了=左边的引用计数,增加了=右边的引用计数。拷贝构造增加了引用计数,两个对象都共享一套数据。

总结

这个例子实现了类指针行为的类SharePtr,其行为很像智能指针,只不过智能指针通过模板实现了泛型而已。所以通过这个例子我们可以理解智能指针的工作原理和内部实现了。
源码链接
https://gitee.com/secondtonone1/cpplearn
想系统学习更多C++知识,可点击下方链接。
C++基础

posted @ 2022-01-27 17:24  恋恋风辰  阅读(35)  评论(0编辑  收藏  举报