C++当心资源管理类中的拷贝行为

简介: C++当心资源管理类中的拷贝行为

1.管理堆之外的资源


昨天的文章C++中基于对象来管理资源中介绍了如何使用auto_ptr和shared_ptr来管理基于堆(heap)的资源。但对于堆之外的资源(例如Mutex锁),智能指针就不那么好用了,因此我们需要写自己的资源管理类。假设我们现在正在操作一个Mutex锁,如下所示:


1void lock(Mutex* pm);  // 锁住pm所指向的锁
2void unlock(Mutex* pm);  // 解开pm所指向的锁


同时,还有一个符合RAII规范(即获取资源在对象构造过程中,释放资源在对象析构过程中)的类来管理这些锁,如下所示:


1#include <iostream>
 2#include <memory>
 3#include <mutex>
 4
 5void lock(Mutex* pm);
 6void unlock(Mutex* pm);
 7
 8class Lock{
 9public:
10    explicit Lock(Mutex* pm): mutexPtr(pm){
11        lock(mutexPtr);   //  在构造时获取资源,上锁
12    }
13    ~Lock(){
14        unlock(mutexPtr);   //  在析构时释放资源,解锁
15    }
16private:
17    Mutex* mutexPtr;
18};


例如,访问临界区(critical section), 临界区即线程必须互斥地访问某些资源,这些资源必须只能最多由一个线程访问。因此,我们就需要以RAII的方式来进行操作。


1Mutex m;
2
3// ...
4{   // 创建一个代码块来定义临界区
5    Lock m1(&m);  // 构造锁ml,锁住m
6
7    // ... 执行临界区操作
8}  // 临界区结束,调用ml的析构函数,解锁


2.资源管理类中的拷贝行为


以上代码中的用法都是没有问题的,可如果锁被拷贝了呢?如下所示:


1Mutex m;
2
3Lock m1(&m);  // m在m1构造过程中被锁住
4Lock m2(m1);  // 把m1拷贝进m2,注意会发生什么情况?


在创建自己的RAII资源管理类时,我们必须要思考需要如何规定这个类的拷贝行为。对于这个问题,我们有如下选择:


(1).禁止拷贝


有些对象的拷贝是没有意义的,就如上例中的这个锁,没有人会给同一个资源上两个锁。对于这样的类,我们就干脆禁止拷贝。正如之前的文章如果不想使用编译器默认生成的函数,请明确拒绝它提到的需要把拷贝函数声明为私有private来禁止拷贝,如下所示:


1class Lock: private Uncopyable{  // 设为private,禁止拷贝
 2public:
 3    explicit Lock(Mutex* pm): mutexPtr(pm){
 4        lock(mutexPtr);   //  在构造时获取资源,上锁
 5    }
 6    ~Lock(){
 7        unlock(mutexPtr);   //  在析构时释放资源,解锁
 8    }
 9private:
10    Mutex* mutexPtr;
11};


(2).给资源引用计数


有时候我们需要一直持有一个资源直到最后一个对象使用完毕。要实现这样的功能,我们必须有一个计数器来统计当前有多少对象在使用这个资源。当生成一个拷贝时加一,当删除一个拷贝时减一,和shared_ptr智能指针原理是一样的。我们可以替代裸指针把shared_ptr作为RAII对象的数据成员来实现这个功能(将mutexPtr类型从Mutex*变成shared_ptr<Mutex>)。我们知道默认情况下,shared_ptr在引用计数为零时会删除掉它所包含的指针。但对于Mutex锁,我们想要的是解锁而不是删除掉,否则我们是没有办法解开一个被删除的锁。不要着急,这只是默认的情况。shared_ptr还提供了一个特殊的可定义函数,删除器(deleter),即在引用计数为零时调用的函数,是shared_ptr构造函数的一个附加参数。这个函数在auto_ptr中是不存在的,因此它不能有自定义的删除行为,只能删除掉它包括的指针。如下所示:


1class Lock{
2public:
3    explicit Lock(Mutex* pm): mutexPtr(pm, unlock){  // 将unlock函数绑定到删除器
4        lock(mutexPtr.get());   
5    }
6    //  这里不需要定义析构函数     
7private:
8    std::shared_ptr<Mutex> mutexPtr;  // 使用shared_ptr,不使用裸指针
9};


上面的程序中,我们并没有定义析构函数,因为前面的文章C++类中默认生成的函数中提到过,类的析构函数会调用它的非静态数据成员的析构函数。Lock类的析构函数会调用它的成员mutexPtr的析构函数,而当mutexPtr的引用计数为零时,它的析构函数则会调用删除器,即我们绑定的unlock函数。


(3).深拷贝封装的资源


有时候我们可以拥有某个资源的多份拷贝,那么我们的资源管理类就要确保每一份拷贝都要在使用周期结束后释放资源,并且每一份拷贝互不干涉。因此,拷贝这样的对象就要拷贝它包含的所有资源,进行深拷贝(deep copy)。例如当对象包含一个指针,我们必须先生成一个指针的拷贝,分配一个新的内存空间再把数据拷贝过来,这就是深拷贝。如果是浅拷贝,拷贝则直接使用了本体的指针成员,没有生成指针的拷贝,那么两个对象的指针成员就会指向同一个地址,删除拷贝就会导致本体被删除。


(4).转移所有权


有时候我们想要只有一个对象来持有这个资源,因此进行拷贝时,资源的所有权就要从原始对象转移到拷贝对象身上,原始对象不再持有资源,这就是auto_ptr的原理。文章C++类中默认生成的函数中指出编译器会为你生成默认的拷贝函数(即拷贝赋值运算符函数和拷贝构造函数),除非它们能实现你想要的功能,否则我们需要自己写拷贝函数来实现以上的其中一种功能。


3.总结


(1).拷贝RAII对象必须一并拷贝它所管理的资源,所以资源的拷贝行为决定RAII对象的拷贝行为。

(2).常用的RAII类拷贝行为有禁止拷贝、使用引用计数、拷贝资源、转移所有权,但也可以用其他做法来符合特殊需要。


4.参考资料


[1] https://zhuanlan.zhihu.com/p/71805363

相关文章
|
13天前
|
存储 编译器 C++
C ++初阶:类和对象(中)
C ++初阶:类和对象(中)
|
13天前
|
C++
C++(十六)类之间转化
在C++中,类之间的转换可以通过转换构造函数和操作符函数实现。转换构造函数是一种单参数构造函数,用于将其他类型转换为本类类型。为了防止不必要的隐式转换,可以使用`explicit`关键字来禁止这种自动转换。此外,还可以通过定义`operator`函数来进行类型转换,该函数无参数且无返回值。下面展示了如何使用这两种方式实现自定义类型的相互转换,并通过示例代码说明了`explicit`关键字的作用。
|
13天前
|
存储 设计模式 编译器
C++(十三) 类的扩展
本文详细介绍了C++中类的各种扩展特性,包括类成员存储、`sizeof`操作符的应用、类成员函数的存储方式及其背后的`this`指针机制。此外,还探讨了`const`修饰符在成员变量和函数中的作用,以及如何通过`static`关键字实现类中的资源共享。文章还介绍了单例模式的设计思路,并讨论了指向类成员(数据成员和函数成员)的指针的使用方法。最后,还讲解了指向静态成员的指针的相关概念和应用示例。通过这些内容,帮助读者更好地理解和掌握C++面向对象编程的核心概念和技术细节。
|
13天前
|
存储 C++
C++(五)String 字符串类
本文档详细介绍了C++中的`string`类,包括定义、初始化、字符串比较及数值与字符串之间的转换方法。`string`类简化了字符串处理,提供了丰富的功能如字符串查找、比较、拼接和替换等。文档通过示例代码展示了如何使用这些功能,并介绍了如何将数值转换为字符串以及反之亦然的方法。此外,还展示了如何使用`string`数组存储和遍历多个字符串。
|
22天前
|
存储 C++
C++ dll 传 string 类 问题
C++ dll 传 string 类 问题
16 0
|
1月前
|
机器学习/深度学习 数据采集 算法框架/工具
使用Python实现深度学习模型:智能人力资源管理与招聘
【8月更文挑战第12天】 使用Python实现深度学习模型:智能人力资源管理与招聘
43 2
|
2月前
|
数据采集 监控 数据安全/隐私保护
ERP系统中的人力资源管理与员工绩效评估解析
【7月更文挑战第25天】 ERP系统中的人力资源管理与员工绩效评估解析
80 1
|
2月前
|
机器学习/深度学习 Oracle 安全
人力资源管理革新:6款系统一站式解决HR事务
**Zoho People、BambooHR、Workday、ADP Workforce Now和Oracle HCM Cloud是知名的人力资源管理系统。Zoho People提供模块化设计、移动应用和自动化工作流;BambooHR以用户友好界面和员工档案管理见长;Workday侧重全球化云解决方案和智能决策工具;ADP Workforce Now集成HR与薪资管理,强调合规性;Oracle HCM Cloud则以高度定制和分析工具闻名。这些系统各有特点,适用于不同规模和需求的企业。**
66 11
|
2月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的人力资源管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的人力资源管理系统附带文章源码部署视频讲解等
27 2
|
3月前
|
机器学习/深度学习 人工智能 搜索推荐
人力资源管理(HRM)系统的未来:技术引领的变革浪潮
【6月更文挑战第24天】随着AI、大数据、云计算和移动技术的融合,HRM正转型为提升效率和员工体验的关键工具。AI助力智能决策,大数据驱动精准管理,云计算与移动技术实现无缝访问和远程操作。员工体验被置于设计核心,系统更人性化、个性化。未来趋势强调全面整合与协同工作,赋能企业高效运营。

热门文章

最新文章