13-2 c++拷贝控制和资源管理
为了定义这些成员,我们首先必须确定此类型对象的拷贝语义。一般来说,有两种选择:可以定义拷贝操作,使类的行为看起来像一个值或者像一个指针。
- 类的行为像一个值,意味着它应该也有自己的状态。当我们拷贝一个像值的对象时,副本和原对象是完全独立的。改变副本不会对原对象有任何影响,反之亦然。
- 行为像指针的类则共享状态。当我们拷贝一个这种类的对象时,副本和原对象使用相同的底层数据。改变副本也会改变原对象,反之亦然。
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;
};
类拷贝赋值运算符的编写
赋值运算符组合了析构函数和构造函数的操作
这些操作需要以正确的顺序执行
- 先拷贝右侧对象
- 处理自赋值情况
- 异常安全
- 释放左侧对象的资源
- 更新左侧对象
HasPtr& HasPtr::operator=(const HasPtr &rhs){
string *newp = new string(*rhs.ps);//拷贝底层string
delete ps; //释放旧内存
ps = newp; //更新左对象
i = rhs.i;
return *this; //返回本对象
}
在定义赋值运算符时要注意两点:
- 能处理自赋值。
- 大多数赋值运算符组合了析构函数和拷贝构造函数的工作。
如果操作顺序错误,先销毁左侧对象再拷贝,就无法处理自赋值
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; //返回本对象
}
13-2 c++拷贝控制和资源管理的更多相关文章
- 【C++ Primer 第13章】2. 拷贝控制和资源管理
拷贝控制和资源管理 • 类的行为像一个值.意味着它应该有自己的状态,当我们拷贝一个像值得对象时,副本和原对象是完全独立的,改变副本不会对原对象有任何影响. • 行为像指针的类则共享状态.当我们拷贝一个 ...
- C++ 拷贝控制和资源管理,智能指针的简单实现
C++ 关于拷贝控制和资源管理部分的笔记,并且介绍了部分C++ 智能指针的概念,然后实现了一个基于引用计数的智能指针.关于C++智能指针部分,后面会有专门的研究. 通常,管理类外资源的类必须定义拷贝控 ...
- [C++]类的设计(2)——拷贝控制(拷贝控制和资源管理)
1.类的行为分类:看起来像一个值:看起来想一个指针. 1)类的行为像一个值,意味着他应该有自己的状态.当我们拷贝一个像值的对象时,副本和原对象是完全独立的.改变副本不会对原有对象有任何影响 ...
- C++ Primer : 第十三章 : 拷贝控制之拷贝控制和资源管理
定义行为像值的类 行为像值的类,例如标准库容器和std::string这样的类一样,类似这样的类我们可以简单的实现一个这样的类HasPtr. 在实现之前,我们需要: 定义一个拷贝构造函数,完成stri ...
- [C++ Primer] : 第13章: 拷贝控制
拷贝, 赋值与销毁 当定义一个类时, 我们显示地或隐式地指定在此类型的对象拷贝, 移动, 赋值和销毁时做什么. 一个类通过定义5种特殊的成员函数来控制这些操作, 包括: 拷贝构造函数, 拷贝赋值运算符 ...
- 【c++ Prime 学习笔记】第13章 拷贝控制
定义一个类时,可显式或隐式的指定在此类型对象上拷贝.移动.赋值.销毁时做什么.通过5种成员函数实现拷贝控制操作: 拷贝构造函数:用同类型的另一个对象初始化本对象时做什么(拷贝初始化) 拷贝赋值算符:将 ...
- Chapter13:拷贝控制
拷贝控制操作:拷贝构造函数.拷贝赋值运算符.移动构造函数.移动赋值运算符.析构函数. 实现拷贝控制操作的最困难的地方是首先认识到什么时候需要定义这些操作. 拷贝构造函数: 如果一个构造函数的第一个参数 ...
- 【C++】C++的拷贝控制
目录结构: contents structure [-] 拷贝.赋值与销毁 拷贝构造函数 拷贝初始化 参数和返回值 拷贝赋值运算符 析构函数 三五法则 拷贝控制和资源管理 交换操作 对象移动 右值引用 ...
- 【C++ Primer 第13章】1. 拷贝控制、赋值和销毁
拷贝控制.赋值和销毁 如果一个构造函数的第一个参数是自身类的引用,且额外的参数都有默认值,则此构造函数是拷贝控制函数(拷贝构造函数不应该是explicit的). 如果我们没有为一个类定义拷贝构造函数, ...
- 《C++ Primer》笔记 第13章 拷贝控制
拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么.拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么.析构函数定义了当此类型对象销毁时做什么.我们称这些操作为拷贝控制 ...
随机推荐
- 23 暑假友谊赛 No.4(UKIEPC 2017)
23 暑假友谊赛 No.4(UKIEPC 2017) Problem A Alien Sunset hh,开始一眼差分,但是写寄了qwq,后来换枚举过了(Orz,但是看学长差分是能做的,我就说嘛,差分 ...
- 在程序里面执行system(“cd /某个目录“),为什么路径切换不成功?
粉丝提问: 彭老师,问下,在程序里面执行system("cd /某个目录"),这样会切换不成功,为啥呢 实例代码: 粉丝的疑惑是明明第10行执行了cd /media操作, 为什么1 ...
- 如何使用4G模块通过MQTT协议传输温湿度数据到onenet
本次实验是采用SIM7600CE 4G cat4 模块进行操作的,本模块支持GNSS定位功能.也可以采用别的4G模块,只要支持TCP传输就行.本模块支持的AT命令相当强大,拥有TCP&UDP命 ...
- 从网友探秘 《黑神话:悟空》 的脚本说说C#
<黑神话:悟空>千呼万唤始出来.在正式发售后不到24小时,Steam在线玩家峰值突破222万,在Steam所有游戏在线玩家历史峰值中排名第二.第一拨玩家纷纷晒出好评,称这款现象级产品正式开 ...
- Windows 查看当前登录用户名
使用 PowerShell 输入 whoami,PowerShell 将以 hostname\username 的方式输出主机名和用户名: $ whoami hostname\username 使用 ...
- Linux 更新 TeX Live
更新 TeX Live 假设你的旧版 TeX Live 版本号为 2023,新版 TeX Live 版本号为 2024.你需要在下面的命令中相应地更改实际版本号.TeX Live 版本可以通过 tlm ...
- MiniMax:如何基于 JuiceFS 构建高性能、低成本的大模型 AI 平台
MiniMax 成立于 2021 年 12 月,是领先的通用人工智能科技公司,致力于与用户共创智能.MiniMax 自主研发了不同模态的通用大模型,其中包括万亿参数的 MoE 文本大模型.语音大模型以 ...
- freebsd 上硬盘操作
列出硬盘 # egrep 'ad[0-9]|cd[0-9]' /var/run/dmesg.boot or # geom disk list or # camcontrol devlist 查看cac ...
- 总结篇3:redis 典型缓存架构设计问题及性能优化
聊聊对于缓存预热.缓存穿透.缓存雪崩.缓存击穿.缓存更新.缓存降级的定义理解 缓存穿透 定义 当查询Redis中没有的数据时,该查询会下沉到数据库层,同时数据库层也没有该数据,当这种情况大量出现或被恶 ...
- 前端解决Long类型精度丢失的问题
问题 数据库数据: 前端得到的数据: 出现了Long类型的数据出现精度丢失问题! 原因 JS中Long最大值:9007199254740992 JAVA中Long最大值:922337203685477 ...