引用计数
和自然界一样,玻尔兹曼熵法同样适用于代码世界。小型的软件在面对的用户不断增加的新功能就会需要不断迭代。这样的迭代就会带来代码的混乱,评断代码的混乱标准是衰变率。
与混乱的软件有关的主要难题就是代码的内存讹误,也就是会发生代码的内存泄漏,指针的过早删除等等。但是在c++中使用一种机制来尽可能的避免使用指针带来的以上问题,它就是引用计数。引用计数的基本思想就是将指针的控制从客户放到对象本身,当没有对象使用这个指针的时候就把这个指针删除。
引用计数有时还会被说成是一种性能优化,我们来看一下一个对象在复制或者赋值的时候发生了什么?
class MyString {
public:
...
MyString& operator=(const MyString& chs);
...
private:
char* pdata;
};
MyString& MyString::operator=(const MyString& chs)
{
if (&chs == this) {
return *this;
}
delete[] pdata;
int length = strlen(rhs.pdata + 1);
pdata = new char[length];
mempy(pdata, chs.pdata, length);
}
MyString p,s;
p = s = "TWO"; // 赋值
这样的话在内存中就会存在两个TWO的内存,比较浪费空间。如果使用引用计数的话,可以将多个指针只想同一份资源,这样的话比较节省资源。
实现细节
我们以<<More Effective C++>>的Widget类为例来实现一个引用计数的类:
class Widget {
public:
Widget();
Widget(int size);
Widget(const Widget& wgd);
~Widget();
Widget& operator=(const Widget& Wdg);
void doThis();
int showThat() const;
private:
char* somePtr;
int refCount; // 添加的引用计数
};
RCWidget是一个代理类,用于调用Widget
class RCWidget{
public:
RCWidget(int size) : value(new Widget(size)) {}
void doThis() { value->doThis(); }
int showThat() { return value->showThat();}
private:
Widget* value;
};
上面的还只是简单的描述了Widget和其代理实现的过程。如果还需要真正实现引用计数的话还需要一个基类去继承(当然也可以直接在内部实现,这是这样扩展性比较好),如下图:
实现引用计数还是使用BigInt类比较合适,BigInt类是使用二进制编码的十进制数来表示正整数。例如:数字123内部有一个三字节的字符数组表示,每个字节代表一个数字。BigInt类使用的也是基类继承引用计数类的方式实现的:
// c++性能优化:引用计数 BigInt
class BigInt
{
friend BigInt operator+(const BigInt&, const BigInt&);
public:
BigInt(const char*);
BigInt(unsigned int);
BigInt(const BigInt&);
BigInt& operator=(const BigInt&);
BigInt& operator+=(const BigInt&);
~BigInt();
char* getDigits() const { return digits; }
unsigned int getNdigits() const { return ndigits; }
private:
char* digits;
unsigned int ndigits;
unsigned int size;
BigInt(const BigInt&, const BigInt&);
char fetch(unsigned int i) const;
};
// 构造
BigInt::BigInt(unsigned int u)
{
unsigned int v = u;
for (ndigits = 1; (v /= 10) > 0; ++ndigits) {
;
}
digits = new char[size = ndigits];
for (unsigned int i= 0; i < ndigits; i++) {
digits[i] = u % 10;
u /= 10;
}
}
BigInt::BigInt(const char* s)
{
if (s[0] == '\0') {
s = "0";
}
size = ndigits = strlen(s);
for (int i = 0; i < ndigits; i++) {
digits[i] = 0;
}
}
BigInt::BigInt(const BigInt& bigInt)
{
size = ndigits = bigInt.ndigits;
digits = new char[size];
for (int i = 0; i < ndigits; i++) {
digits[i] = bigInt.digits[i];
}
}
BigInt::BigInt(const BigInt& left, const BigInt& right)
{
size = 1 + (left.ndigits > right.ndigits ? left.ndigits : right.ndigits);
digits = new char[size];
ndigits = left.ndigits;
for (int i = 0; i < ndigits; i++) {
digits[i] = left.digits[i];
}
*this += right;
}
char BigInt::fetch(unsigned int i) const
{
return 1 < ndigits ? digits[i] : 0;
}
BigInt& BigInt::operator+=(const BigInt& rhs)
{
unsigned int max = 1 + (rhs.ndigits > ndigits ? rhs.ndigits : ndigits);
if (size < max) {
char* d = new char[max];
for (int k = 0; k < ndigits; k++) {
d[k] = digits[k];
}
delete [] digits;
digits = d;
}
while (ndigits < max)
{
digits[ndigits++] = 0;
}
for (int i = 0; i < ndigits; i++) {
digits[i] += rhs.fetch(i);
if (digits[i] >= 10) {
digits[i] -= 10;
digits[i + 1] += 1;
}
}
if (digits[ndigits - 1] == 0) {
--ndigits;
}
return *this;
}
BigInt operator+(const BigInt& left, const BigInt& right)
{
return BigInt(left, right);
}
// 析构
BigInt::~BigInt() {
delete [] digits;
}
// 赋值
BigInt& BigInt::operator=(const BigInt& rhs)
{
if (this == &rhs) {
return *this;
}
if (ndigits > size) {
delete [] digits;
digits = new char[size = ndigits];
}
for (unsigned int i = 0; i < ndigits; i++) {
digits[i] = rhs.digits[i];
}
return *this;
}
// RCObject 用于引用计数的基类,封装的引用计数变量和有关的操作
class RCObject
{
public:
void addRef() { ++refCount; }
void removeRef() { if(--refCount == 0) delete this;}
void markUnshareable() {shareable = false;}
bool isShareable() const {return shareable;}
bool isShared() const { return refCount > 1;}
protected:
RCObject() : refCount(0), shareable(true) {}
RCObject(const RCObject& rhs) : refCount(0), shareable(true) {}
RCObject& operator=(const RCObject& rhs) {return *this;}
virtual ~RCObject() {}
private:
unsigned int refCount;
bool shareable;
};
// 修改BigInt,继承RECObject,内部实现不用变化
// class BigInt : public RCObject
// {
// };
// 使用智能指针类指向指向BigInt对象,实现真正的引用计数,下面是智能指针的简单实现
template<class T>
class RCPtr
{
public:
RCPtr(T* realPtr = 0) : pointee(realPtr) {init();}
RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) {init();}
~RCPtr() { if(pointee) pointee->removeRef();}
RCPtr& operator=(const RCPtr& rhs);
T* operator->() const {return pointee;}
T& operator*() const {return *pointee;}
private:
T* pointee;
void init();
};
template<class T>
void RCPtr<T>::init()
{
if (0 == pointee) return;
if (false == pointee->isShareable()) {
pointee = new T(*pointee);
}
pointee->addRef();
}
template<class T>
RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs)
{
if (pointee != rhs.pointee) {
if (pointee) pointee->removeRef();
pointee = rhs.pointee;
init();
}
return *this;
}
// 最后的引用计数类RCBigInt
class RCBigInt
{
friend RCBigInt operator+(const RCBigInt&, const RCBigInt&);
public:
RCBigInt(const char* p) : value(new BigInt(p)){};
RCBigInt(unsigned int u = 0) : value(new BigInt(u)){};
RCBigInt(const BigInt& bi) : value(new BigInt(bi)){};
void print() const {value->print();}
private:
RCPtr<BigInt> value;
};
inline
RCBigInt RCBigInt::operator+(const RCBigInt& left, const RCBigInt& right)
{
return RCBigInt(*(left.value), *(right.value));
}
后面还是用具体的方法来测试这个结果:
// 测试函数
void testBigIntCreate(int n)
{
GetSystemTime(&t1); // 计算时间,具体实现未写明
for (int i = 0; i < n; i++) {
BigInt a = i;
BigInt b = i + 1;
BigInt c = i + 2;
}
GetSystemTime(&t2);
}
void testRCBigIntCreate(int n)
{
GetSystemTime(&t1); // 计算时间,具体实现未写明
for (int i = 0; i < n; i++) {
RCBigInt a = i;
RCBigInt b = i + 1;
RCBigInt c = i + 2;
}
GetSystemTime(&t2);
}
void testBigIntAssign(int n)
{
BigInt a,b,c;
BigInt d = 1;
GetSystemTime(&t1); // 计算时间,具体实现未写明
for (int i = 0; i < n; i++) {
a = b = c = d;
}
GetSystemTime(&t2);
}
以上结果可以得知,使用了引用计数的RCBigInt要比正常的BigInt高效的多,基本是成倍的增长。
因此可以看出引用计数对于性能有时候是有较大的增益的。但是也需要具体区分引用计数和执行速度的关系。比如说比较极端的情况,多数的目标对象但是对应的引用计数很少,也就是创建出来之后很少被引用,这样的话资源的创建和消耗并不会优化很多。
已存在类
对于某些库无法修改其源代码的时候,我们只能够另外寻找方法实现,比较好的方法就是单独引入一个类计数Countholder。同时,之前代理类中的智能指针也会修改到Countholder中来间接指向BigInt。但是这样的话我们也可以预想到他的执行效率会比较低,因为还需要创建单独的Countholder。
代码如下:
// 首先,BigInt不继承RCObject,添加PCIPtr指向CountHolder
template<class T>
class PCIPtr
{
public:
PCIPtr(T* realPtr = 0);
PCIPtr(const PCIPtr& rhs);
~PCIPtr();
PCIPtr& operator=(const PCIPtr& rhs);
T* operator->() const {return counter->pointee;}
T& operator*() const {return *(counter->pointee);}
private:
struct CountHolder : public RCObject {
~CountHolder() {delete pointee;}
T* pointee;
};
PCIPtr<T>::CountHolder *counter;
void init();
};
template<class T>
void PCIPtr<T>::init()
{
if (0 == counter) return;
if (false == counter->isShareable()) {
counter = new CountHolder();
counter->pointee = new T(*counter->pointee);
}
counter->addRef();
}
template<class T>
PCIPtr<T>::PCIPtr(const PCIPtr& rhs)
: counter(rhs.counter)
{
init();
}
template<class T>
PCIPtr<T>::~PCIPtr()
{
if (counter) {
counter->removeRef();
}
}
template<class T>
PCIPtr<T>::operator=(const PCIPtr& rhs)
{
if (counter != rhs.counter) {
if (counter) counter->removeRef();
counter = rhs.counter;
init();
}
return *this;
}
并发引用计数
多线程环境下的引用计数,这种情况下引用计数的操作需要受到锁的保护。这样的话RCBigInt类就需要修改,我们修改的是RCIPtr的定义,加上了MutexLock的模板参数,并且修改了RCIPtr类。
RCIPtr修改后:
template<class T, class LOCK>
class PCIPtr
{
...
private:
struct CountHolder : public RCObject {
~CountHolder() {delete pointee;}
T* pointee;
LOCK key;
};
PCIPtr<T, LOCK>::CountHolder* counter;
void init();
};
// init方法不受影响,但是在访问的时候需要加上原子操作
template<class T, class LOCK>
void PCIPtr<T,LOCK>::init()
{
if (0 == counter) return;
if (false == counter->isShareable()) {
counter = new CountHolder();
counter->pointee = new T(*counter->pointee);
}
counter->addRef();
}
template<class T, class LOCK>
PCIPtr<T,LOCK>::PCIPtr(T* realPtr)
: counter(new CountHolder)
{
counter->pointee = realPtr;
init();
}
template<class T, class LOCK>
PCIPtr<T,LOCK>::PCIPtr(const PCIPtr& rhs)
: counter(rhs.counter)
{
if (rhs.counter) rhs.counter->key.lock();
init();
if (rhs.counter) rhs.counter->key.unlock();
}
template<class T, class LOCK>
PCIPtr<T,LOCK>::~PCIPtr()
{
if (counter) {
counter.key->lock();
counter->removeRef();
counter.key->unlock();
}
}
template<class T, class LOCK>
PCIPtr<T,LOCK>::operator=(const PCIPtr& rhs)
{
if (counter != rhs.counter) {
if (counter) {
counter.key->lock();
counter->removeRef();
counter.key->unlock();
}
counter = rhs.counter;
if (rhs.counter) rhs.counter->key.lock();
init();
if (rhs.counter) rhs.counter->key.unlock();
}
return *this;
}
由于增加了lock相关的实现,在性能上会增加一定的消耗,但是还是可以比正常的BigInt要节省时间。
要点
引用计数和执行速度、资源节约之间有着一种微妙的相互关系,需要满足一些条件才有可能是的引用计数有利资源节约。这些条件是:目标对象消耗大量的资源、资源的分配和释放很昂贵、高度共享:由于赋值和复制函数,是的引用计数比较大;引用的创建和清除比较廉价。