当前位置: 首页 > news >正文

老生常谈之引用计数:《More Effective C++》条款29

《More Effective C++》条款29深入探讨了**引用计数(Reference Counting)这一内存管理技术,通过共享对象实现优化性能,并结合写时拷贝(Copy-On-Write)**机制解决资源复用与修改安全的矛盾。以下是条款29的核心内容与实践要点:

一、引用计数的核心原理

引用计数通过跟踪对象被引用的次数,实现资源的自动释放。当最后一个引用离开作用域时,对象被销毁。条款29以String类为例,将字符串值封装在StringValue类中,并维护一个refCount计数器:

  • 共享机制:多个String对象可共享同一StringValue实例,减少内存分配与拷贝开销。
  • 自动释放:当refCount降为0时,StringValue自动销毁,避免内存泄漏。

二、写时拷贝(Copy-On-Write)的实现

为了在共享与修改之间平衡,条款29引入写时拷贝技术:

  1. 读操作优化const operator()直接返回共享值的引用,无需拷贝。
  2. 写操作分离non-const operator()在修改前检查引用计数:
    char& String::operator()(int index) {if (value->refCount > 1) { // 存在共享时--value->refCount; // 减少原引用计数value = new StringValue(value->data); // 创建独立拷贝}return value->data(index); // 返回新拷贝的引用
    }
    
    此逻辑确保只有在实际修改时才创建独立副本,显著提升性能。

三、引用计数基类(RCObject)的设计

条款29提出通过抽象基类RCObject封装引用计数的通用逻辑,使StringValue等类可继承复用:

class RCObject {
public:void addReference() { ++refCount; }void removeReference() { if (--refCount == 0) delete this; }bool isShared() const { return refCount > 1; }bool isShareable() const { return shareable; }void markUnshareable() { shareable = false; }
private:int refCount;bool shareable; // 标记是否允许共享
};
  • 共享控制shareable标志防止不可变对象被共享,避免多线程或外部指针导致的数据竞争。
  • 安全性removeReference在引用计数为0时delete this,要求RCObject子类必须在堆上分配。

四、智能指针RCPtr的封装

为了简化引用计数的手动管理,条款29设计了智能指针模板RCPtr,自动处理计数增减:

template<typename T>
class RCPtr {
public:RCPtr(T* realPtr = 0) : pointee(realPtr) { init(); }RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) { init(); }~RCPtr() { if (pointee) pointee->removeReference(); }private:T* pointee;void init() {if (pointee && !pointee->isShareable()) {pointee = new T(*pointee); // 不可共享时深拷贝}if (pointee) pointee->addReference(); // 增加引用计数}
};
  • 自动拷贝策略:若StringValue不可共享,RCPtr在构造时自动深拷贝,确保线程安全。
  • 异常安全:通过RAII机制,确保在异常抛出时引用计数正确释放。

五、关键挑战与解决方案

  1. 外部指针/引用的干扰

    • 问题:若用户保存operator()返回的char&,后续修改可能影响所有共享该值的String对象。
    • 解决
      • 文档声明:明确禁止保存临时引用。
      • 共享标志:通过markUnshareable()标记不可共享的值对象,强制后续修改创建独立拷贝。
      • Proxy类(条款M30):区分读写操作,精确控制共享行为。
  2. 线程安全

    • 单线程场景:条款29默认假设单线程环境,引用计数操作无需同步。
    • 多线程扩展:实际应用中需结合互斥锁(如std::mutex)保护refCount,但条款未深入讨论。
  3. 循环引用

    • 条款29的处理:通过shareable标志限制共享范围,避免对象间永久互相引用。
    • 现代方案:C++11的weak_ptr可更优雅地解决循环引用,但条款29发布时(1996年)尚未引入。

六、引用计数的优缺点

优点缺点
自动内存管理,避免泄漏循环引用需手动处理(条款29通过共享标志缓解)
写时拷贝显著减少不必要的拷贝操作多线程环境需额外同步机制
可精确控制对象生命周期引用计数操作带来轻微性能开销

七、总结

条款29通过String类的设计,系统展示了引用计数与写时拷贝的实现细节,强调了以下最佳实践:

  1. 分层设计:通过RCObject基类与RCPtr智能指针分离引用计数逻辑与业务逻辑。
  2. 延迟拷贝:仅在实际修改时创建副本,平衡效率与安全性。
  3. 明确接口约束:通过shareable标志与文档声明,限制潜在风险操作。

引用计数至今仍是C++中高效管理共享资源的重要手段,条款29的思想为理解std::shared_ptr等现代智能指针提供了底层实现视角。

http://www.dtcms.com/a/330852.html

相关文章:

  • 炎热的夏天
  • SQL181 第二快/慢用时之差大于试卷时长一半的试卷
  • 掌握MATLAB三维可视化:从基础到实战技巧
  • Redis 从入门到生产:数据结构、持久化、集群、工程实践与避坑(含 Node.js/Python 示例)
  • jenkins在windows配置sshpass
  • 构建Node.js单可执行应用(SEA)的方法
  • 【前端工具】使用 Node.js 脚本实现项目打包后自动压缩
  • Go语言defer机制详解与应用
  • 机器学习介绍
  • 预训练模型在机器翻译中的应用:迁移学习的优势详解
  • 华为实验WLAN 基础配置随练
  • dkms安装nvidia驱动和多内核支持
  • 【motion】GIF 转mp4及ubuntu的VLC播放
  • 数据结构初阶(14)排序算法—交换排序(冒泡)(动图演示)
  • 基于SpringBoot+Vue的房屋匹配系统(WebSocket实时通讯、协同过滤算法、地图API、Echarts图形化分析)
  • iOS App TestFlight 上架全流程案例,从 0 到 1 完成内测分发
  • C#通过TCP_IP与PLC通信
  • vue部署正式环境上传nginx后遇到的问题
  • 分享10个ai生成ppt网站(附ai生成ppt入口)
  • ZigBee入门与提高(3)—— ZigBee协议初识
  • Wireshark中常见协议
  • 重学JS-002 --- JavaScript算法与数据结构(二)JavaScript 基础知识
  • MFT 在零售行业的实践案例与场景:加速文件集成与业务协作的高效方案
  • day30 TCP通信
  • 财务自动化软件敏感数据泄露风险评估与防护措施
  • B站 韩顺平 笔记 (Day 18)
  • C++ 仿RabbitMQ实现消息队列项目
  • 使用uniapp自定义组件双重支付密码
  • RabbitMQ面试精讲 Day 22:消息模式与最佳实践
  • 8.14网络编程——TCP通信基础