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

引用计数的特点及作用
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个问题

  1. 在第四点中,会构造一个rfstring临时对象,此时引用计数为1,然后调用rfstring &operator=(const char *str)将引用计数自增到2,临时变量析构后又变为1,但是由于临时变量的原因会影响效率,需要提供rfstring &operator=(const char *str)
  2. 在第四点中,相同的字符串又在堆上重新分配了内存,不是实际意义的引用计数,这个问题可以后续通过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 *的值即可。那如何知道用户是否要修改其值呢?可以假定如下:

  1. 假定用户调用所有的非const的rfstring对象operator[]时都会修改其值
  2. 用户调用的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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值