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

More Effective C++ 条款29:引用计数

More Effective C++ 条款29:引用计数


核心思想引用计数是一种通过跟踪对象被引用次数来管理资源生命周期的技术,它在多个客户端需要共享相同资源时,可以避免不必要的拷贝,提高效率并简化资源管理。

🚀 1. 问题本质分析

1.1 资源共享的挑战

  • 不必要的拷贝:多个客户端需要相同数据时,深拷贝成本高昂
  • 资源释放时机:难以确定何时可以安全释放共享资源
  • 内存开销:大量相似对象重复存储相同内容浪费内存
  • 一致性维护:多个副本需要保持同步更新

1.2 引用计数的核心需求

  • 共享而非拷贝:允许多个对象共享同一份资源
  • 自动生命周期管理:当最后一个使用者释放资源时自动清理
  • 写时复制(Copy-on-Write):只在必要时创建副本
  • 内存效率:减少重复数据存储
// 基础示例:简单引用计数实现
class String {
public:String(const char* initValue = "") {init(initValue);}String(const String& rhs) {// 共享同一份数据data = rhs.data;data->addReference();}~String() {if (data->removeReference() == 0) {delete data;}}String& operator=(const String& rhs) {if (this != &rhs) {if (data->removeReference() == 0) {delete data;}data = rhs.data;data->addReference();}return *this;}const char& operator[](int index) const {return data->chars[index];}// 非const版本需要写时复制char& operator[](int index) {if (data->isShared()) {StringValue* newData = new StringValue(data->chars);data->removeReference();data = newData;}data->markUnshareable();return data->chars[index];}private:// 引用计数数据结构struct StringValue {size_t refCount;char* chars;bool shareable;StringValue(const char* initValue) : refCount(1), shareable(true) {chars = new char[strlen(initValue) + 1];strcpy(chars, initValue);}~StringValue() {delete[] chars;}void addReference() {++refCount;}size_t removeReference() {return --refCount;}bool isShared() const {return refCount > 1;}void markUnshareable() {shareable = false;}};StringValue* data;void init(const char* initValue) {data = new StringValue(initValue);}
};

📦 2. 问题深度解析

2.1 写时复制(Copy-on-Write)优化

// 增强的写时复制实现
class COWString {
public:// ... 构造函数、析构函数、拷贝操作同上// 安全的非const访问char& operator[](size_t index) {// 如果被共享,创建副本if (data->isShared()) {// 写时复制:创建新副本StringValue* newData = new StringValue(data->chars);data->removeReference();data = newData;}// 标记为不可共享(因为可能被修改)data->markUnshareable();return data->chars[index];}// 提供const版本避免不必要的复制const char& operator[](size_t index) const {return data->chars[index];}// 批量修改操作也需要检查共享void append(const char* str) {if (data->isShared()) {StringValue* newData = new StringValue(data->chars);data->removeReference();data = newData;}// 执行追加操作...size_t oldLen = strlen(data->chars);size_t appendLen = strlen(str);char* newChars = new char[oldLen + appendLen + 1];strcpy(newChars, data->chars);strcat(newChars, str);delete[] data->chars;data->chars = newChars;}private:struct StringValue {std::atomic<size_t> refCount;  // 原子引用计数char* chars;bool shareable;mutable std::mutex mutex;  // 用于线程安全StringValue(const char* initValue) : refCount(1), shareable(true) {chars = new char[strlen(initValue) + 1];strcpy(chars, initValue);}// 线程安全的引用操作void addReference() {std::lock_guard<std::mutex> lock(mutex);++refCount;}size_t removeReference() {std::lock_guard<std::mutex> lock(mutex);return --refCount;}bool isShared() const {std::lock_guard<std::mutex> lock(mutex);return refCount > 1;}void markUnshareable() {std::lock_guard<std::mutex> lock(mutex);shareable = false;}};StringValue* data;
};

2.2 通用引用计数模板

// 通用引用计数包装器
template<typename T>
class RCPtr {
public:RCPtr(T* realPtr = nullptr) : pointee(realPtr) {init();}RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) {init();}~RCPtr() {if (pointee && pointee->removeReference() == 0) {delete pointee;}}RCPtr& operator=(const RCPtr& rhs) {if (pointee != rhs.pointee) {if (pointee && pointee->removeReference() == 0) {delete pointee;}pointee = rhs.pointee;init();}return *this;}T* operator->() const { return pointee; }T& operator*() const { return *pointee; }// 写时复制支持void makeCopy() {if (pointee->isShared()) {T* oldPtr = pointee;pointee = new T(*oldPtr);oldPtr->removeReference();pointee->addReference();}}private:void init() {if (pointee == nullptr) return;if (pointee->isShareable() == false) {pointee = new T(*pointee);}pointee->addReference();}T* pointee;
};// 可被引用计数管理的基类
class RCObject {
public:RCObject() : refCount(0), shareable(true) {}RCObject(const RCObject&) : refCount(0), shareable(true) {}virtual ~RCObject() = default;void addReference() { ++refCount; }size_t removeReference() { return --refCount; }size_t getRefCount() const { return refCount; }bool isShared() const { return refCount > 1; }bool isShareable() const { return shareable; }void markUnshareable() { shareable = false; }private:size_t refCount;bool shareable;
};// 使用示例:可引用计数的字符串
class RCString : public RCObject {
public:RCString(const char* value = "") {init(value);}RCString(const RCString& rhs) : RCObject(rhs) {init(rhs.data);}~RCString() {delete[] data;}const char* c_str() const { return data; }size_t length() const { return strlen(data); }// 修改操作char& operator[](size_t index) {// 需要在调用前调用makeCopy()return data[index];}// 只读访问const char& operator[](size_t index) const {return data[index];}private:void init(const char* value) {data = new char[strlen(value) + 1];strcpy(data, value);}char* data;
};

⚖️ 3. 解决方案与最佳实践

3.1 智能指针与引用计数结合

// 带引用计数的智能指针
template<typename T>
class RefCountedPtr {
public:explicit RefCountedPtr(T* ptr = nullptr) : pointer(ptr), refCount(new size_t(1)) {}RefCountedPtr(const RefCountedPtr& other) : pointer(other.pointer), refCount(other.refCount) {++(*refCount);}RefCountedPtr(RefCountedPtr&& other) noexcept : pointer(other.pointer), refCount(other.refCount) {other.pointer = nullptr;other.refCount = nullptr;}~RefCountedPtr() {decrementRefCount();}RefCountedPtr& operator=(const RefCountedPtr& other) {if (this != &other) {decrementRefCount();pointer = other.pointer;refCount = other.refCount;++(*refCount);}return *this;}RefCountedPtr& operator=(RefCountedPtr&& other) noexcept {if (this != &other) {decrementRefCount();pointer = other.pointer;refCount = other.refCount;other.pointer = nullptr;other.refCount = nullptr;}return *this;}T& operator*() const { return *pointer; }T* operator->() const { return pointer; }T* get() const { return pointer; }size_t use_count() const { return refCount ? *refCount : 0; }// 写时复制支持void make_unique() {if (use_count() > 1) {// 创建新副本T* newPointer = new T(*pointer);decrementRefCount();pointer = newPointer;refCount = new size_t(1);}}private:void decrementRefCount() {if (refCount) {--(*refCount);if (*refCount == 0) {delete pointer;delete refCount;}}}T* pointer;size_t* refCount;
};// 使用示例
class LargeData {
public:LargeData() { /* 昂贵的初始化 */ }LargeData(const LargeData&) { /* 昂贵的拷贝 */ }// ... 其他操作
};void usageExample() {RefCountedPtr<LargeData> data1(new LargeData());RefCountedPtr<LargeData> data2 = data1;  // 共享,不拷贝// 当需要修改时再创建副本data2.make_unique();  // 现在创建实际副本
}

3.2 线程安全的引用计数

// 线程安全的引用计数实现
template<typename T>
class ThreadSafeRefCounted {
public:ThreadSafeRefCounted() : refCount(1) {}virtual ~ThreadSafeRefCounted() = default;void addReference() {refCount.fetch_add(1, std::memory_order_relaxed);}size_t removeReference() {size_t oldCount = refCount.fetch_sub(1, std::memory_order_acq_rel);if (oldCount == 1) {delete this;}return oldCount - 1;}size_t getRefCount() const {return refCount.load(std::memory_order_relaxed);}protected:mutable std::atomic<size_t> refCount;
};// 线程安全的智能指针
template<typename T>
class TSRefCountedPtr {
public:explicit TSRefCountedPtr(T* ptr = nullptr) : pointer(ptr) {if (pointer) {pointer->addReference();}}TSRefCountedPtr(const TSRefCountedPtr& other) : pointer(other.pointer) {if (pointer) {pointer->addReference();}}TSRefCountedPtr(TSRefCountedPtr&& other) noexcept : pointer(other.pointer) {other.pointer = nullptr;}~TSRefCountedPtr() {if (pointer) {pointer->removeReference();}}TSRefCountedPtr& operator=(const TSRefCountedPtr& other) {if (this != &other) {// 先增加新对象的引用if (other.pointer) {other.pointer->addReference();}// 然后减少当前对象的引用if (pointer) {pointer->removeReference();}pointer = other.pointer;}return *this;}// 其他操作类似...private:T* pointer;
};

3.3 引用计数在复杂对象中的应用

// 复杂对象的引用计数管理
class Document : public ThreadSafeRefCounted {
public:static Document* create(const std::string& content) {return new Document(content);}// 只读访问const std::string& getContent() const {std::lock_guard<std::mutex> lock(mutex);return content;}// 修改操作需要线程安全void append(const std::string& text) {std::lock_guard<std::mutex> lock(mutex);content += text;++version;}size_t getVersion() const {std::lock_guard<std::mutex> lock(mutex);return version;}// 创建快照(写时复制)Document* createSnapshot() const {std::lock_guard<std::mutex> lock(mutex);return new Document(content, version);}private:Document(const std::string& content) : content(content), version(0) {}Document(const std::string& content, size_t ver): content(content), version(ver) {}std::string content;size_t version;mutable std::mutex mutex;
};// 使用智能指针管理文档
class DocumentHandle {
public:explicit DocumentHandle(Document* doc = nullptr) : document(doc) {}// 读取内容std::string read() const {if (document) {return document->getContent();}return "";}// 修改内容(自动处理引用计数)void write(const std::string& text) {if (document && document->getRefCount() > 1) {// 如果有其他引用,创建副本Document* newDoc = document->createSnapshot();document->removeReference();document = newDoc;}if (document) {document->append(text);}}// 创建新版本DocumentHandle createBranch() const {if (document) {Document* newDoc = document->createSnapshot();return DocumentHandle(newDoc);}return DocumentHandle();}private:Document* document;
};

💡 关键实践原则

  1. 识别共享场景
    当多个对象需要相同数据且读取远多于写入时,引用计数特别有效
  2. 写时复制优化
    延迟拷贝直到真正需要修改时,避免不必要的复制开销
  3. 线程安全考虑
    在多线程环境中使用原子操作确保引用计数安全
  4. 避免循环引用
    引用计数无法处理循环引用,需要配合弱引用或其他机制
  5. 性能权衡
    引用计数增加开销,适用于拷贝成本高而共享收益大的场景

引用计数 vs 垃圾收集

// 引用计数的优点:
// 1. 确定性销毁:资源立即释放
// 2. 预测性性能:没有GC暂停
// 3. 更少的内存开销:不需要GC数据结构// 引用计数的缺点:
// 1. 循环引用问题
// 2. 原子操作开销
// 3. 代码复杂性

现代C++中的引用计数

// std::shared_ptr 已经内置引用计数
void modernExample() {std::shared_ptr<LargeData> data1 = std::make_shared<LargeData>();std::shared_ptr<LargeData> data2 = data1;  // 共享引用// 如果需要修改且避免影响其他使用者if (!data2.unique()) {data2 = std::make_shared<LargeData>(*data2);  // 深拷贝}// 现在可以安全修改data2
}

总结
引用计数是一种强大的资源管理技术,通过在多个使用者间共享资源而非复制,显著提高内存效率和性能。

关键实现技术包括写时复制、原子操作确保线程安全、以及适当的抽象来隐藏复杂性。现代C++中std::shared_ptr提供了内置的引用计数,但在特定场景下自定义实现可能更有优势。

正确使用引用计数可以大幅减少不必要的资源拷贝,同时保持代码的清晰性和资源管理的可靠性。但在使用时需要注意循环引用问题和性能权衡。

http://www.xdnf.cn/news/20329.html

相关文章:

  • 【完整源码+数据集+部署教程】骰子点数识别图像实例分割系统源码和数据集:改进yolo11-DCNV2
  • 【知识点讲解】模型扩展法则(Scaling Law)与计算最优模型全面解析:从入门到前沿
  • 深入了解synchronized
  • 2025世界职校技能大赛总决赛争夺赛汽车制造与维修赛道比赛资讯
  • 告别Qt Slider!用纯C++打造更轻量的TpSlider组件
  • 一文了解太阳光模拟器的汽车材料老化测试及标准解析
  • 企业级 AI Agent 开发指南:基于函数计算 FC Sandbox 方案实现类 Chat Coding AI Agent
  • 集成学习 | MATLAB基于CNN-LSTM-Adaboost多输入单输出回归预测
  • 调试技巧:Chrome DevTools 与 Node.js Inspector
  • 从零开始学大模型之大模型训练流程实践
  • Multisim14.0(五)仿真设计
  • OpenResty 和 Nginx 到底有啥区别?你真的了解吗!
  • 分布式3PC理论
  • Qt---字节数据处理QByteArray
  • 【FastDDS】Layer Transport ( 02-Transport API )
  • k8s基础练习环境搭建
  • 服务器硬盘“Unconfigured Bad“状态解决方案
  • WebSocket:实现实时通信的革命性技术
  • Iwip驱动8211FS项目——MPSOC实战1
  • 当服务器出现网卡故障时如何检测网卡硬件故障并解决?
  • Grizzly_高性能 Java 网络应用框架深度解析
  • 基于智能合约实现非托管支付
  • Qt添加图标资源
  • conda配置pytorch虚拟环境
  • git cherry-pick 用法
  • dpdk example
  • 自动化流水线
  • Ubuntu 22 redis集群搭建
  • 电脑越用越卡?C盘红到预警?这款清理神器帮你一键 “减负”!
  • 跨域请求问题浅解