引用计数的特点及作用
1. 引用计数允许多个相同值得对象共享这个值得实现
2. 引用计数可以节省空间、拷贝及析构的开销
3. 引用计数可以简化对象的追踪过程,比如在垃圾体系回收中
引用计数的简单实现:
如果要设计一个类rfstring作为string的引用计数版,则rfstring至少要包含2个成员变量:char *str,int rfcount。因此可以设计为以下形式:
class rfstring
{
public:
rfstring(const char * value);
~rfstring();
rfstring(const rfstring & rhl);
private:
char *str;
int count;
};
rfstring::rfstring(const char * value):count(1)
{
str = new char[strlen(value) + 1];
strcpy(str, value);
}
rfstring::~rfstring()
{
//考虑该在什么条件下删除堆内存?
}
以上设计存在几个问题:
1. 由于需要对char 数据进行引用计数,如果rfstring类直接持有为char 分配的堆内存,则相同的字符串仍然创建多个实例,因此需要考虑将字符串放到另外一个类中,然后让rfstring间接持有char *——假定该类名称为stringvalue
2. 如果rfstring直接持有stringvalue的对象,则仍然会创建多个实例,因此需要持有其指针
3. 由于stringvalue不需要对外部暴露,因此将stringvalue声明为rfstring类的私有类
4. 由于rfstring类含有堆内存,因此需要提供自己的拷贝构造与赋值操作符。
因此我们的rfstring类设计如下:
// rfstring.h
#pragma once
class rfstring
{
public:
rfstring(const char * value);
~rfstring();
rfstring(const rfstring & rhl);
//rfstring & operator=(const char *str);// 可以不实现,如果不实现,通过一个char *对rfstring对象赋值时,编译器会将char *转换为rfstring对象后,调用operator(const rfstring &)
rfstring &operator=(const rfstring &value);
private:
class stringvalue
{
public:
char *value;
int count;
stringvalue(const char *str);
~stringvalue();
};
stringvalue *strvalue;
};
// rfstring.cpp
"rfstring.h"
#include <cstring>
rfstring::rfstring(const char * value)
{
strvalue = new stringvalue(value);
}
rfstring::rfstring(const rfstring & rhl)
{
strvalue = rhl.strvalue;
strvalue->count++; // 由于rhl是const,因此只能通过this指针的strvalue对count进行自增
}
// 由于含有堆内存,因此需要考虑深拷贝和浅拷贝的问题
// 需要防止自赋值
// 如果引用计数为1,则需要将原始的内存释放掉
rfstring & rfstring::operator=(const rfstring &rhl)
{
if (this == &rhl)
{
return *this;
}
// 判断是否需要释放内存
if (--strvalue->count == 0)
{
delete strvalue;
}
strvalue = rhl.strvalue;
strvalue->count++;
return *this;
}
rfstring::~rfstring()
{
if (--strvalue->count == 0)
{
delete strvalue;
}
strvalue = NULL;
}
rfstring::stringvalue::stringvalue(const char *str):count(1)
{
value = new char[strlen(str) + 1];
strcpy(value, str);
}
rfstring::stringvalue::~stringvalue()
{
delete value;
value = NULL;
}
lude "rfstring.h"
void test()
{
rfstring rf1 = "more effective c++"; // 1.调用rfstring(const char * value);
rfstring rf2 = rf1; // 2.调用rfstring(const rfstring & rhl); rfcount++
rfstring rf3("effective c++"); // 3.调用rfstring(const char * value);
rf3 = "more effective c++"; // 4.先调用rfstring(const char * value)构造一个临时的rfstring对象,然后再调用rfstring &operator=(const rfstring &value),然后析构掉临时的对象
}
int main()
{
test();
return 0;
}
上述调用的过程中存在2个问题
- 在第四点中,会构造一个rfstring临时对象,此时引用计数为1,然后调用rfstring &operator=(const char *str)将引用计数自增到2,临时变量析构后又变为1,但是由于临时变量的原因会影响效率,需要提供rfstring &operator=(const char *str)
- 在第四点中,相同的字符串又在堆上重新分配了内存,不是实际意义的引用计数,这个问题可以后续通过2个方式解决
为rfstring添加operator=(const char *)
// 用来解决效率问题
rfstring & operator=(const char *str);
rfstring & rfstring::operator=(const char * str)
{
if (--strvalue->count == 0)
{
delete strvalue;
}
strvalue = new stringvalue(str);
return *this;
}
此rfstring类不含油缺省构造函数,可以进行添加,使strvalue为null,然后调用的过程中,检查strvalue是否为空即可。
上述rfstring类中没有提供std::string类中的operator[]操作,这个操作是必须的,但是它会带来问题,因为你不知道用户调用该操作时,是否会改变该下标对应字符的值,如下:
rfstring rf1 = "more effective c++"; // 1.调用rfstring(const char * value);
rfstring rf2(rf1);
rf1[10] = 'Z';
对于一个非const的rfstring对象,我们应该允许用户修改其某个下标的值,因此第二行代码是合法的,但是此时rf2实际指向的内容不再是”more effective c++”。如何避免呢?当用户试图修改非const的rfstring对象的下标值时重新拷贝一份char *的值即可。那如何知道用户是否要修改其值呢?可以假定如下:
- 假定用户调用所有的非const的rfstring对象operator[]时都会修改其值
- 用户调用的const的rfstring对象的operator[]都返回const char &,符合常理
实现代码如下:
const char &operator[](const int index) const;
char &operator[](const int index);
const char & rfstring::operator[](const int index) const
{
return strvalue->value[index];
}
// 非const版本,需要进行拷贝
char & rfstring::operator[](const int index)
{
if (strvalue->count > 1)
{
strvalue->count--;
strvalue = new stringvalue(strvalue->value);
}
return strvalue->value[index];
}
由于提供了operator[]操作,因此外部可以得到某个下标对应字符的地址,如果这个地址被外部拿到,将造成rfstring对象的值被修改而不知的问题,如下:
String s1 = "Hello";
char *p = &s1[1];
*p = 'x';
该如何解上述问题?stanly lippman提供了三个建议:
1. 忽略它
2. 写入文档
3. 解决它——为rfstring类提供一个是否可以共享的标志,但仍然丑陋,代码如下
struct StringValue {
int refCount;
bool shareable; // add this
char *data;
StringValue(const char *initValue);
~StringValue();
};
然后在相应的函数中添加对shareable的操作即可
完整的代码如下:
// rfstring.h
#pragma once
class rfstring
{
public:
rfstring(const char * value);
~rfstring();
const char &operator[](const int index) const;
char &operator[](const int index);
rfstring(const rfstring & rhl);
rfstring & operator=(const char *str);// 可以不实现,如果不实现,通过一个char *对rfstring对象赋值时,编译器会将char *转换为rfstring对象后,调用operator(const rfstring &)
rfstring &operator=(const rfstring &value);
private:
class stringvalue
{
public:
char *value;
int count;
bool shareable; // add this
stringvalue(const char *str);
~stringvalue();
};
stringvalue *strvalue;
};
// rfstring.cpp
#include "rfstring.h"
#include <cstring>
rfstring::rfstring(const char * value)
{
strvalue = new stringvalue(value);
}
rfstring::rfstring(const rfstring & rhl)
{
if (rhl.strvalue->shareable)
{
strvalue = rhl.strvalue;
strvalue->count++; // 由于rhl是const,因此只能通过this指针的strvalue对count进行自增
}
else
{
strvalue = new stringvalue(rhl.strvalue->value);
}
}
rfstring & rfstring::operator=(const char * str)
{
if (--strvalue->count == 0)
{
delete strvalue;
}
strvalue = new stringvalue(str);
return *this;
}
// 由于含有堆内存,因此需要考虑深拷贝和浅拷贝的问题
// 需要防止自赋值
// 如果引用计数为1,则需要将原始的内存释放掉
rfstring & rfstring::operator=(const rfstring &rhl)
{
if (this == &rhl)
{
return *this;
}
// 判断是否需要释放内存
if (--strvalue->count == 0)
{
delete strvalue;
}
strvalue = rhl.strvalue;
strvalue->count++;
return *this;
}
rfstring::~rfstring()
{
if (--strvalue->count == 0)
{
delete strvalue;
}
strvalue = NULL;
}
const char & rfstring::operator[](const int index) const
{
return strvalue->value[index];
}
// 非const版本,需要进行拷贝
char & rfstring::operator[](const int index)
{
if (strvalue->count > 1)
{
strvalue->count--;
strvalue = new stringvalue(strvalue->value);
}
strvalue->shareable = false;
return strvalue->value[index];
}
rfstring::stringvalue::stringvalue(const char *str):count(1),shareable(true)
{
value = new char[strlen(str) + 1];
strcpy(value, str);
}
rfstring::stringvalue::~stringvalue()
{
delete value;
value = NULL;
}