ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

13-2 c++拷贝控制和资源管理

2022-03-01 15:33:51  阅读:164  来源: 互联网

标签:HasPtr ps 13 string 对象 c++ 计数器 拷贝 资源管理


目录

为了定义这些成员,我们首先必须确定此类型对象的拷贝语义。一般来说,有两种选择:可以定义拷贝操作,使类的行为看起来像一个值或者像一个指针。

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

13.2.1 行为像值的类

定义一个HasPtr类,该类需要

  • 定义一个拷贝构造函数,完成string的拷贝,而不是拷贝指针
  • 定义一个析构函数来释放string
  • 定义一个拷贝赋值运算符来释放对象当前的 string,并从右侧运算对象拷贝string

HasPtr的类值版本如下:

class HasPtr{
public:
    HasPtr(const &string &s = string()) : 
    	ps(new string(s)), i(0){}
    //对于ps指向的string,每个HasPtr都有自己的拷贝
    HasPtr(const HasPtr &p):
    	ps(new string(*p.ps)), i(p.i){}
    HasPtr& operator=(const HasPtr &p);
    ~HasPtr() {delete ps;} //记得释放内存
private:
    string *ps;
    int i;
};

类拷贝赋值运算符的编写

赋值运算符组合了析构函数和构造函数的操作

这些操作需要以正确的顺序执行

  1. 先拷贝右侧对象
    • 处理自赋值情况
    • 异常安全
  2. 释放左侧对象的资源
  3. 更新左侧对象
HasPtr& HasPtr::operator=(const HasPtr &rhs){
    string *newp = new string(*rhs.ps);//拷贝底层string
    delete ps; //释放旧内存
    ps = newp; //更新左对象
    i = rhs.i;
    return *this; //返回本对象
}

在定义赋值运算符时要注意两点:

  • 能处理自赋值。
  • 大多数赋值运算符组合了析构函数和拷贝构造函数的工作。

如果操作顺序错误,先销毁左侧对象再拷贝,就无法处理自赋值

image-20220301143709522

13.2.2 定义行为像指针的类

需要定义拷贝构造函数和拷贝运算符,拷贝指针而不是string

析构函数要释放string,在本例中,只有当最后一个指向string的ps被销毁后才能释放底层string的内存,需要用到shared_ptr

使用shared_ptr的关键作用之一是引用计数,记录指向对象的指针有多少个,在此我们不直接使用shared_ptr,而是自主实现引用计数,加强理解

引用计数

工作方式

  • 除了初始化对象外,每个构造函数(拷贝构造函数除外)还要创建一个引用计数,用来记录有多少对象与正在创建的对象共享状态。当我们创建一个对象时,只有一个对象共享状态,因此将计数器初始化为1。
  • 拷贝构造函数不分配新的计数器,而是拷贝给定对象的数据成员,包括计数器。拷贝构造函数递增共享的计数器,指出给定对象的状态又被一个新用户所共享。
  • 析构函数递减计数器,指出共享状态的用户少了一个。如果计数器变为0,则析构函数释放状态。
  • 拷贝赋值运算符递增右侧运算对象的计数器,递减左侧运算对象的计数器。如果左侧运算对象的计数器变为0,意味着它的共享状态没有用户了,拷贝赋值运算符就必须销毁状态。

在哪里存放计数器?

不能作为类的成员

HasPtr p1("Hi");
HasPtr p2(p1);
HasPtr p3(p1);

创建p3时,我们可以递增p1的计数器并把它拷贝到p3中,但是,p2的计数器没有变化

所以:要让所有对象共享计数器的底层数据,需要把计数器定义在动态内存

定义一个使用引用计数的类

指针版本的HasPtr

class HasPtr{
public : 
	//构造函数分配新的构造器,初始化为1
    HasPtr(const string &s = string()) :
    	ps(new string(s)), i(0), use(new int(1)){}
    //拷贝构造函数拷贝所有成员,计数器+1
    HasPtr(const HasPtr &p) :
    	ps(p.ps), i(p.i), use(p.use){++*use;}
    HasPtr& operator=(const HasPtr&);
    ~HasPtr();
private :
    string *ps;
    int i;
    int *use; //记录有多少个共享*ps的对象
};
//析构函数
HasPtr::~HasPtr(){
    //递减计数器,计数==0时释放内存
    if(--*use == 0){
        delete ps;  //释放string内存
        delete 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; //返回本对象
}

标签:HasPtr,ps,13,string,对象,c++,计数器,拷贝,资源管理
来源: https://www.cnblogs.com/timothy020/p/15950383.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有