在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;
}
}