C++ 简易string类实现(三)-抽离引用计数

C++ 简易string类实现(二)-引用计数里我们在String类实现里加入了引用计数,从而有效地实现了字符串的共享,降低内存消耗,提升了代码的执行效率.引用计数(RC)不仅可以用于字符串,任何class如果其不同的对象可能拥有相同的值,都适用此技术.然而重写class以便运用引用计数(RC),可能是一个大工程,我们中的大部分人该做的事情还有很多.如果我们能够在一个与外界无任何关联的环境下撰写引用计数(RC)代码(并测试以及说明),然后在必要的时机把它移植到classes身上,这样可以有效地利用已有代码去实现新功能,降低时间消耗和BUG数量.

如何去撰写这样的引用计数(RC)代码?面向对象的封装和继承特性可以帮助我们有效地完成这个需求.

第一个步骤是,在C++ 简易string类实现(二)-引用计数里我们发现,StringValue内的ptr变量是和String相耦合的,但refCount和shareable和String没有必要的关联(换成其它class,对refCount和shareable的操作基本不变),因此我们将这两个变量剥离出来,以此产生一个base class RCObject,作为”reference-counted对象”之用.任何class如果希望自动拥有reference counting能力,都必须继承这个class.RCObject将”引用计数器”本身以及增减计数值的函数封装进来.此外,还包括一个函数,用来将不再被使用(也就是引用次数为0)的对象销毁掉.注:以上所需的成员函数,其实就是将C++ 简易string类实现(二)-引用计数中和refCount及shareable变量相关的操作进行的封装.

RCObject声明如下:

//class String;
class RCObject
{
    //friend class String;
public:
    RCObject();

    /*RCObject(const RCObject& rhs_);*/

    /*RCObject& operator=(const RCObject& rhs_);*/

    //使RCObject成为抽象基类,但该纯虚函数需要提供
    //定义,不然会使被继承的类无法在栈上创建(原因可
    //查阅如何仅在堆上或栈上分配内存)
    virtual ~RCObject() = 0;       

public:
    void addReference();

    void removeReference();

    void  markUnshareable();

    bool isShareable() const;

    bool isShared() const;

private:
    RCObject& operator=(const RCObject&) = delete;

private:
    int refCount;
    bool shareable;
};

RCObject定义如下:

RCObject::RCObject()
    :refCount(1), shareable(true)
{
    //std::cout << "RCObject" << std::endl;
}

/*RCObject::RCObject(const RCObject&)
    :refCount(1), shareable(true)
{
}*/

/*RCObject& RCObject::operator=(const RCObject&)
{
    return *this;
}*/

RCObject::~RCObject()
{
    //std::cout << "~RCObject" << std::endl;
}

void RCObject::addReference()
{
    ++refCount;
}

void RCObject::removeReference()
{
    if (--refCount == 0)
    {
        delete this;
    }
}

void RCObject::markUnshareable()
{
    shareable = false;
}

bool RCObject::isShareable() const
{
    return shareable;
}

bool RCObject::isShared() const
{
    return refCount > 1;
}

需要注意的地方:
(1)more effective C++中提供了RCObject(const RCObject& rhs_)拷贝构造函数,但我觉得这里暂时没有必要实现提供该函数,因为,RCObject本身是虚基类,拷贝构造只有可能由派生类的构造函数主动调用RCObject的拷贝构造函数,否则派生类的构造函数只会调用RCObject的默认(无参)构造函数;
(2)默认(无参)构造函数里,refCount应该初始化为1,more effective C++将其初始化为0,按照我的理解,是为了迎合后面实现自动操作引用次数(Reference Count)而做的铺垫,在这里,我们不引入这个技术,所以将refCount初始化为1(这里初始化为0,会造成未定义的错误);
(3)more effective C++里对RCObject的赋值运算符声明和定义如下:

RCObject& operator=(const RCObject& rhs_);

RCObject& RCObject::operator=(const RCObject&)
{
    return *this;
}

其对此的解释是:”RCObeject涉及赋值动作,指向左右两方RCObject外围对象(例如本例的String对象)的个数都不会收到影响”,但如下图,
这里写图片描述RCObject的赋值操作,必然是因为StringValue的赋值操作而引起的(RCObject是虚基类,没有实例对象,无法直接调用其赋值运算符函数),此时sv1的ptr也指向sv2的ptr所指向的字符串S2,我们知道,RCOject记录的不仅是当前有多少个String对象指向该ptr,更应该是一共有多少个String对象指向ptr所指向的字符串,如果仅仅是前者,effective C++里的写法固然没有问题,但如果是后者的话(也必须是后者),那么此时指向字符串S2的String对象就有5个,如果依旧是3个,那么当s1,s2,s3都不拥有sv1时,sv1就会将其指向的资源析构,也就是说,尽管还有sv2的ptr指向字符串S2,但字符串S2还是被释放了,这就会导致未定义行为!!!
虽然,上述的情况一般不会发生,因为在String里,StringValue对象都是heap对象,String对象的赋值操作不会导致其StringValue指针变量发生赋值操作(除非手动进行指针赋值操作),所以完全不需要担心上述问题,但该问题始终是潜在会发生的,解决的办法:A.禁止StringValue的赋值操作(将StringValue的赋值运算符设置为delete或者private);B.发生StringValue的赋值操作时,delete其ptr所指向的资源,并修改RCObject的实现,使得在发生赋值时,会正确修改refCount的值,代码相对复杂,尝试写了下很多问题,所以仅仅在这里提供一个上述的思路,若日后可以解决,再加上.这里我们采用策略A,简单而且有效.

StringValue的声明,定义如下:

struct StringValue  : public RCObject
    {
        char* ptr;

        StringValue(const char* str_);

        //不可赋值操作
        StringValue& operator=(const StringValue&) = delete;

        ~StringValue();
    };

String::StringValue::StringValue(const char* str_)
{
    ptr = new char[strlen(str_) + 1];
    strcpy(ptr, str_);
}

String::StringValue::~StringValue()
{
    if (ptr)
    {
        delete[] ptr;
    }
}

全部代码如下;

声明:

class RCObject
{
public:
    RCObject();

    RCObject(const RCObject& rhs_);

    //使RCObject成为抽象基类,但该纯虚函数需要提供
    //定义,不然会使被继承的类无法在栈上创建(原因可
    //查阅如何仅在堆上或栈上分配内存)
    virtual ~RCObject() = 0;       

public:
    void addReference();

    void removeReference();

    void  markUnshareable();

    bool isShareable() const;

    bool isShared() const;

private:
    RCObject& operator=(const RCObject&) = delete;

private:
    int refCount;
    bool shareable;
};

class String
{
public:
    String(const char* str_ = "");

    String(const String& str_);

    String& operator=(const String& str_);

    ~String();

public:
    const char& operator[](size_t index_) const;

    char& operator[](size_t index_);

private:
    struct StringValue  : public RCObject
    {
        char* ptr;

        StringValue(const char* str_);

        //不可赋值操作
        StringValue& operator=(const StringValue&) = delete;

        ~StringValue();
    };

    StringValue* _value;
};

实现:

RCObject::RCObject()
    :refCount(1), shareable(true)
{
    //std::cout << "RCObject" << std::endl;
}

RCObject::RCObject(const RCObject&)
//调用无参构造函数,注意:该调用仅能在
//初始化成员列表里,如果在函数实现内调用,
//那么仅仅是在栈上生成新的对象,而不是完成
//该对象的成员初始化
    :RCObject()
{
    std::cout << "RCObject" << std::endl;
}

RCObject::~RCObject()
{
    //std::cout << "~RCObject" << std::endl;
}

void RCObject::addReference()
{
    ++refCount;
}

void RCObject::removeReference()
{
    if (--refCount == 0)
    {
        delete this;
    }
}

void RCObject::markUnshareable()
{
    shareable = false;
}

bool RCObject::isShareable() const
{
    return shareable;
}

bool RCObject::isShared() const
{
    return refCount > 1;
}

String::String(const char* str_ /* = "" */)
    : _value(new StringValue(str_))
{
}

String::String(const String& str_)
{
    if (str_._value->isShareable())
    {
        _value = str_._value;
        _value->addReference();
    }
    else
    {
        _value = new StringValue(str_._value->ptr);
    }
}

String& String::operator=(const String& str_)
{
    /*
    //这样写存在问题,例如str2 = str3; str2 = str3;即重复一次,
    //如果是这种写法,那么会执行后续代码一次,虽然结果是对的
    //但造成了不必要的运行消耗
    if (this != &str_)
    {
        return *this;
    }*/
    if (_value == str_._value)
    {
        return *this;
    }

    _value->removeReference();

    if (str_._value->isShareable())
    {
        _value = str_._value;
        _value->addReference();
    }
    else
    {
        _value = new StringValue(str_._value->ptr);
    }

    return *this;
}

String::~String()
{
    _value->removeReference();
}

const char& String::operator[](size_t index_) const
{
    //为了简化代码,不引入_size变量记录字符串长度
    if (index_ >= strlen(_value->ptr))
    {
        throw std::out_of_range("String out of range!");
    }
    return _value->ptr[index_];
}

char& String::operator[](size_t index_)
{
    if (index_ >= strlen(_value->ptr))
    {
        throw std::out_of_range("String out of range!");
    }

    //本对象和其他String对象共享同一个实值
    if (_value->isShared())
    {
        _value->removeReference();

        _value = new StringValue(_value->ptr);
    }

    _value->markUnshareable();
    return _value->ptr[index_];
}

String::StringValue::StringValue(const char* str_)
{
    ptr = new char[strlen(str_) + 1];
    strcpy(ptr, str_);
}

String::StringValue::~StringValue()
{
    if (ptr)
    {
        delete[] ptr;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值