您的位置:首页 > 编程语言 > C语言/C++

more effective c++——Item M29 引用计数(一)简略的rfstring类设计和写时拷贝

2017-05-03 00:50 721 查看
引用计数的特点及作用

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 str
4000
value;
}

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息