More Effective C++之引用计数
Reference counting让我想起了Java,当如果想用C++来实现Java的能力的话,那Reference counting必不可少。Reference counting可以节省程序的运行成本,大量的构造、析构、分配、释放和拷贝的代价被省略。实现
classRCObject { public: RCObject():refCount(0),shareable(true){} RCObject(constRCObject&):refCount(0),shareable(true){} RCObject& operator=(constRCObject& rhs){return *this;} virtual ~RCObject()=0; void AddReference(){++refCount;} void RemoveReference(){if (--refCount == 0) deletethis;} void markUnshareable(){shareable = false;} bool isShareable() const{returnshareable;} bool isShared() const {returnrefCount > 1;} private: int refCount; bool shareable; }; RCObject::~RCObject(){} template <classT> class RCPtr { public: RCPtr(T* realPtr = 0):pointee(realPtr){init();} RCPtr(constRCPtr& rhs):pointee(rhs.pointee){init();} ~RCPtr(){if (pointee) pointee->RemoveReference();} RCPtr& operator = (constRCPtr& rhs) { if (pointee!=rhs.pointee) { if (pointee) pointee->RemoveReference(); pointee = rhs.pointee; init(); } return *this; } T* operator->() const { returnpointee;} T& operator*() const{return *pointee;} private: T* pointee; void init() { if (pointee == 0) return; if (pointee->isShareable() == false) pointee = newT(*pointee); pointee->AddReference(); } }; class String { public: String(const char* value = ""):value(newStringValue(value)){} const char& operator[](intnIndex) const { return value->data[nIndex]; } char& operator[](intnIndex) { if (value->isShared()) value = newStringValue(value->data); value->markUnshareable(); returnvalue->data[nIndex]; } protected: private: struct StringValue:publicRCObject { char* data; String Value(constchar* initValue) { init(initValue); } String Value(constStringValue& rhs) { init(rhs.data); } void init(constchar * initValue) { data = newchar[strlen(initValue) + 1]; strcpy(data,initValue); } ~String Value() { delete [] data; } }; RCPtr<StringValue> value; }; |
这是Meyers给出的String的实现,然而我的观点是如果没有特别的必要的话,对stirng最好不要使用引用计数,因为在多线程程序中同步的代价要大于引用计数本身的好处,得不偿失。
如果StringValue是一个现成的类,无法修改它的实现,那怎么办?没关系可以用委托,下面是一个典型的实现:
classRCObject { public: RCObject():refCount(0),shareable(true){} RCObject(constRCObject&):refCount(0),shareable(true){} RCObject& operator=(constRCObject& rhs){return *this;} virtual ~RCObject()=0; void AddReference(){++refCount;} void RemoveReference(){if (--refCount == 0) deletethis;} void markUnshareable(){shareable = false;} bool isShareable() const{returnshareable;} bool isShared() const {returnrefCount > 1;} private: int refCount; bool shareable; }; RCObject::~RCObject(){} template<classT> class RCIPtr { public: RCIPtr(T* realPtr = 0):counter(new CountHolder) { counter->pointee = realPtr; init(); } RCIPtr(constRCIPtr& rhs):counter(rhs.counter) { init(); } ~RCIPtr() { counter->RemoveReference(); } RCIPtr& operator = (constRCIPtr& rhs) { if (counter != rhs.counter) { counter->RemoveReference(); counter = rhs.counter; init(); } return *this; } constT* operator->()const { returncounter->pointee; } T* operator->() { makeCopy(); returncounter->pointee; } constT& operator*() const { return *(counter->pointee); } T& operator*() { makeCopy(); return *(counter->pointee); } private: struct CountHolder:publicRCObject { ~Count Holder(){deletepointee;} T* pointee; }; Count Holder* counter; void init() { if (counter->isShareable() == false) { T* oldValue = counter->pointee; counter = newCountHolder; counter->pointee = newT(*oldValue); } counter->AddReference(); } void makeCopy() { if (counter->isShared()) { T* oldValue = counter->pointee; counter->RemoveReference(); counter = newCountHolder; counter->pointee = newT(*oldValue); counter->AddReference(); } } }; class Widget { public: Widget(intSize){} Widget(constWidget& rhs){} ~Widget(){} Widget operator=(const Widget& rhs){} void doThis(){printf("doThis() ");return;} int showThat() const{printf("showThat() "); return 0;} protected: private: inti; }; class RCWidget { public: RCWidget(intsize):value(newWidget(size)){} void doThis(){value->doThis();} int showThat()const {returnvalue->showThat();} protected: private: RCIPtr<Widget> value; }; |
评估
实现引用计数是需要有前提的,不是所有的情况下,使用引用计数都是合适的。适合情况如下:
相对多的对象共享相对少量的实值。
对象的实值产生或者销毁的成本很高,或者占用很多内存。
但是要记住,即使是Java也会有内存泄漏,不要指望小小的引用计数(上面简单的实现)不会产生同样的问题。
引用计数是一项很深奥的技术,想想Java,所以需要很谨慎的对待,但愿它能带来程序设计上的优化。