1. 引用计数
1.1 作用
- 在实际编程中,堆对象可能被传递给另一个对象,一旦这个过程复杂了,我们最后很难确定谁拥有了这个对象。使用引用计数就可以抛开这个问题,不需要再去关心谁拥有了这个对象,我们把管理权交给了对象自己,当对象不再被引用时,它自己负责释放自己。
- 解决了同一个对象存在多份相同内存的问题。引用计数可以让等值对象共享一份数据实体,可以节省内存,程序速度加快,因为不再需要构造和析构同值对象的多余副本。
1.2 等值对象具有多份复制的情况
cpp
#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;
}
bash
$ ./a.out
a.data=0x634ec5a77eb0
b.data=0x634ec5a77ed0
c.data=0x634ec5a77ef0
d.data=0x634ec5a77f10
e.data=0x634ec5a77f30
$
下一个代码:
cpp
#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;
}
bash
$ ./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
- 自己独享新的副本
- 然后再进行修改
- 如果发现 refCount > 1:
代码改进:
cpp
#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;
}
bash
$ ./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
$