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

10. 引用计数

1. 引用计数

1.1 作用

  • 在实际编程中,堆对象可能被传递给另一个对象,一旦这个过程复杂了,我们最后很难确定谁拥有了这个对象。使用引用计数就可以抛开这个问题,不需要再去关心谁拥有了这个对象,我们把管理权交给了对象自己,当对象不再被引用时,它自己负责释放自己。
  • 解决了同一个对象存在多份相同内存的问题。引用计数可以让等值对象共享一份数据实体,可以节省内存,程序速度加快,因为不再需要构造和析构同值对象的多余副本。

1.2 等值对象具有多份复制的情况

#include <iostream>
#include <string.h>using namespace std;class String {
public:String(const char* value = "") {data = new char[strlen(value) + 1];strcpy(data, value);}String& operator=(const String& rhs) {if (this == & rhs) {return *this;}delete[] data;data = new char[strlen(rhs.data) + 1];strcpy(data, rhs.data);return *this;}char* data;
};int main() {String a, b, c, d, e;a = b = c = d = e = "Hello";cout << "a.data=" << (void*)a.data << endl;cout << "b.data=" << (void*)b.data << endl;cout << "c.data=" << (void*)c.data << endl;cout << "d.data=" << (void*)d.data << endl;cout << "e.data=" << (void*)e.data << endl;return 0;
}
$ ./a.out
a.data=0x634ec5a77eb0
b.data=0x634ec5a77ed0
c.data=0x634ec5a77ef0
d.data=0x634ec5a77f10
e.data=0x634ec5a77f30
$

下一个代码:

#include <iostream>
#include <string.h>using namespace std;class String {
private:struct StringValue {int refCount;     // 引用计数char* data;       // 实际字符串存储空间StringValue(const char* initValue);~StringValue();};StringValue* value;   // 指向共享数据的指针public:String(const char* initValue = "");String(const String& rhs);String& operator=(const String& rhs);~String();const char* c_str() const { return value->data; }int use_count() const { return value->refCount; }const void* data_addr() const { return static_cast<const void*>(value->data); }
};// 初始化StringValue
String::StringValue::StringValue(const char* initValue):refCount(1) {data = new char[strlen(initValue) + 1];strcpy(data, initValue);cout << "[构造 StringValue] data地址=" << (void*)data << " 内容=\"" << data << "\" refCount=" << refCount << endl;
}// 析构StringValue
String::StringValue::~StringValue() {cout << "[释放 StringValue] data地址=" << (void*)data << " 内容=\"" << data << "\"" << endl;delete[] data;
}// 构造函数
String::String(const char* initValue):value(new StringValue(initValue)) {cout << "String(const char*) -> 新建对象 value=" << value << endl;
}// 拷贝构造
String::String(const String& rhs):value(rhs.value) {value->refCount++;cout << "String(const String&) -> 拷贝构造,共享 data地址=" << (void*)value->data << " refCount=" << value->refCount << endl;
}// 赋值运算符
String& String::operator=(const String& rhs) {if (this->value == rhs.value) {return *this;}if (--value->refCount == 0) {delete value;}value = rhs.value;++value->refCount;cout << "operator= -> 赋值,共享 data地址=" << (void*)value->data << " refCount=" << value->refCount << endl;return *this;
}// 析构函数
String::~String() {if (--value->refCount == 0) {delete value;} else {cout << "析构但共享存在 -> data地址=" << (void*)value->data << " 当前refCount=" << value->refCount << endl;}
}// main 测试
int main() {cout << "=== 创建 s1 ===" << endl;String s1("Hello");cout << "\n=== 复制构造 s2(s1) ===" << endl;String s2(s1);cout << "\n=== 赋值操作 s3 = s1 ===" << endl;String s3;s3 = s1;cout << "\n=== 打印地址对比 ===" << endl;cout << "s1.data=" << s1.data_addr() << " refCount=" << s1.use_count() << endl;cout << "s2.data=" << s2.data_addr() << " refCount=" << s2.use_count() << endl;cout << "s3.data=" << s3.data_addr() << " refCount=" << s3.use_count() << endl;cout << "\n=== 结束 main,开始析构 ===" << endl;return 0;
}
$ ./a.out
=== 创建 s1 ===
[构造 StringValue] data地址=0x61d315d1a2e0 内容="Hello" refCount=1
String(const char*) -> 新建对象 value=0x61d315d1a2c0=== 复制构造 s2(s1) ===
String(const String&) -> 拷贝构造,共享 data地址=0x61d315d1a2e0 refCount=2=== 赋值操作 s3 = s1 ===
[构造 StringValue] data地址=0x61d315d1a320 内容="" refCount=1
String(const char*) -> 新建对象 value=0x61d315d1a300
[释放 StringValue] data地址=0x61d315d1a320 内容=""
operator= -> 赋值,共享 data地址=0x61d315d1a2e0 refCount=3=== 打印地址对比 ===
s1.data=0x61d315d1a2e0 refCount=3
s2.data=0x61d315d1a2e0 refCount=3
s3.data=0x61d315d1a2e0 refCount=3=== 结束 main,开始析构 ===
析构但共享存在 -> data地址=0x61d315d1a2e0 当前refCount=2
析构但共享存在 -> data地址=0x61d315d1a2e0 当前refCount=1
[释放 StringValue] data地址=0x61d315d1a2e0 内容="Hello"
$

1.3 存在的问题

之前引用计数有个问题:多个 String 对象共享一个底层缓冲区,任何一个对象修改字符串时都会影响到所有对象。

  • 写时复制机制 Copy-on-Write
    只有在“写操作”发生时,才真正复制数据,从而避免无谓的深拷贝。

关键思想:

  • 多个对象共享同一份字符串(引用计数大于 1 时)。
  • 当某个对象想要修改内容时,
    • 如果发现 refCount > 1:
      • 先把当前数据拷贝一份
      • 把旧的引用计数减去 1
      • 自己独享新的副本
    • 然后再进行修改

代码改进:

#include <iostream>
#include <string.h>using namespace std;class String {
private:struct StringValue {int refCount;     // 引用计数char* data;       // 实际字符串存储空间StringValue(const char* initValue);~StringValue();};StringValue* value;   // 指向共享数据的指针public:String(const char* initValue = "");String(const String& rhs);String& operator=(const String& rhs);~String();const char* c_str() const { return value->data; }int use_count() const { return value->refCount; }const void* data_addr() const { return static_cast<const void*>(value->data); }// 重载下标操作符const char& operator[](size_t index) const; // 只读版本char& operator[](size_t index); // 可写版本
};// 初始化StringValue
String::StringValue::StringValue(const char* initValue):refCount(1) {data = new char[strlen(initValue) + 1];strcpy(data, initValue);
}// 析构StringValue
String::StringValue::~StringValue() {delete[] data;
}// 构造函数
String::String(const char* initValue):value(new StringValue(initValue)) {
}// 拷贝构造
String::String(const String& rhs):value(rhs.value) {value->refCount++;
}// 赋值运算符
String& String::operator=(const String& rhs) {if (this->value == rhs.value) {return *this;}if (--value->refCount == 0) {delete value;}value = rhs.value;++value->refCount;return *this;
}// 析构函数
String::~String() {if (--value->refCount == 0) {delete value;}
}const char& String::operator[](size_t index) const {return value->data[index];
}char& String::operator[](size_t index) {if (value->refCount > 1) {value->refCount--;value = new StringValue(value->data);}return value->data[index];
}int main() {String a("Hello");String b(a);String c = b;cout << "=== 初始状态 ===" << endl;cout << "a: " << a.c_str() << " addr=" << a.data_addr() << " ref=" << a.use_count() << endl;cout << "b: " << b.c_str() << " addr=" << b.data_addr() << " ref=" << b.use_count() << endl;cout << "c: " << c.c_str() << " addr=" << c.data_addr() << " ref=" << c.use_count() << endl;cout << "\n=== 修改 b[0] = 'h' ===" << endl;b[0] = 'h';  // 触发写时复制cout << "\n=== 修改后状态 ===" << endl;cout << "a: " << a.c_str() << " addr=" << a.data_addr() << " ref=" << a.use_count() << endl;cout << "b: " << b.c_str() << " addr=" << b.data_addr() << " ref=" << b.use_count() << endl;cout << "c: " << c.c_str() << " addr=" << c.data_addr() << " ref=" << c.use_count() << endl;return 0;
}
$ ./a.out
=== 初始状态 ===
a: Hello addr=0x60f6a530fed0 ref=3
b: Hello addr=0x60f6a530fed0 ref=3
c: Hello addr=0x60f6a530fed0 ref=3=== 修改 b[0] = 'h' ====== 修改后状态 ===
a: Hello addr=0x60f6a530fed0 ref=2
b: hello addr=0x60f6a5310320 ref=1
c: Hello addr=0x60f6a530fed0 ref=2
$
http://www.dtcms.com/a/552798.html

相关文章:

  • 利用DeepSeek辅助改写luadbi-duckdb支持日期和时间戳数据类型
  • 用 Redis 的 List 存储库存队列,并通过 LPOP 原子性出队来保证并发安全案例
  • 定制开发开源AI智能名片S2B2C商城系统:新零售革命下云零售模式的创新实践
  • WebForms Validation
  • AI智能办公系统/企业OA办公/DeepSeek办公应用★pc/公众号/H5/App/小程序
  • 破局冷轧困境:RFID 赋能钢厂高效安全升级
  • 线程同步机制及三大不安全案例
  • Leetcode438. 找到字符串中所有字母异位词
  • 站内推广和站外推广的区别wordpress采集生成用户插件
  • 高清的网站制作iis7.0搭建网站
  • 使用 Docker Compose 部署 Spring Boot 应用:SmartAdmin 实战指南
  • 徐州建设企业网站网站建设与网站制作
  • 拆解ASP.NET MVC 视图模型:为 View 量身定制的 “数据小票“
  • 使用Docker搭建DOClever接口管理平台
  • 2025年10月个人工作生活总结
  • 金仓售后服务体系:构建高可用数据库运维新范式
  • 融入现代消费生活 浦发故宫文化主题卡的传承与创新
  • Android Studio Narwhal 4:创建空应用报错 —— AAPT2 process unexpectedly exit 的排查与解决
  • 九寨:在山水间触摸生活的诗意
  • C++(23):延长for循环临时变量生命期
  • Android Studio gradle下载失败报错
  • 贵州 做企业网站的流程58同城最新消息招聘
  • Kubernetes 双层 Nginx 容器环境下的 CORS 问题及解决方案(极端情况)
  • Kimi发布新一代注意力架构!线性注意力实现75% KV缓存减少、6倍解码速度提升
  • 做电子商务系统网站建设wordpress图片加水印
  • 电机参数标幺化与定点计算在整数MCU上的实现
  • Rust开发之错误处理与日志记录结合(log crate使用)
  • 2025年11月1日(星期六)骑行笔架山
  • opencv 学习: 03 初识 cv:Mat
  • 数据结构(c++版):邻接矩阵的实现