第五章,技术
29-Reference counting(引用计数)
共享同一个“引用计数与数据”
class String { public: String(const char *initValue = "") : value(new StringValue(initValue)) { ++value->refConunt; }
String(const String& rhs) { value = rhs.value; ++value->refConunt; }
~String() { if (--value->refConunt == 0) delete value; }
String& String::operator=(const String& rhs) { if (value == rhs.value) { return *this; }
if (--value->refConunt == 0) { delete value; }
value = rhs.value; ++value->refConunt;
return *this; }
private: struct StringValue { int refConunt; char *data;
StringValue(const char* initValue) :refConunt(1) { data = new char[strlen(initValue) + 1]; strcpy(data, initValue); } ~StringValue() { delete[] data; } };
StringValue *value; };
|
写时复制(Copy-on-Write)
和其他对象共享一份实值,直到必须对自己所拥有的那一份实值进行写动作时,单独复制出一个副本。
写时复制仍在计算机科学领域有很长历史,特别是操作系统领域: 各进程之间往往允许共享某些内存分页,直到它们打算修改属于自己的那一分页。
const char& String::operator[](int index) const { return value->data[index]; }
char& String::operator[](int index) { if(value->refCount > 1){ --value->refConunt; value = new StringValue(value->data); }
return value->data[index]; }
|
写时复制在工程中可能导致的问题:
String s1 = "Hellow"; char *p = &s1[1]; String s2 = s1; *p = 'x';
|
解决方法1:忽视,假装不存在(我不明白为什么会把这个写在解决方法里面,可能是有一些人就是这样做的情况比较典型,原著要嘲讽一下)
解决方法2:注释说明(鸡肋)
解决方法3:添加共享标识flag,一旦调用了operator[],就让flag置为false,不可共享
视线回到现在(公元2025年),现代的C++以及放弃了COW(写时复制),而是使用SSO(小字符串优化)+立即复制+移动语义;
那么,我知道我很想马上知道现代的C++是什么样的,但是我劝我自己先别急,而是问,这个COW还有什么学习的必要:
1、引用计数、深拷贝浅拷贝、运算符重载的理解
2、设计缺陷分析能力
3、其他一些领域仍在使用:操作系统,数据库,容器技术(Docker),游戏引擎等,这是一个使用广泛且历史长的技术概念
OK,看一下现代C++是怎么实现的:
彻底放弃共享,每个 string 独立拥有数据! 用 SSO + 立即复制替代 COW
SSO : 小字符串直接存在对象内部,不用堆分配
传统 string(无 SSO): ┌─────────┐ │ string │ ┌───────────┐ │ 对象 │────→│ 堆内存 │ │(8字节) │ │ "hello" │ │ 存指针 │ │(6字节) │ └─────────┘ └───────────┘ 总计:8 + 6 = 14字节(两次内存访问)
SSO string(现代 C++): ┌─────────────────────┐ │ string 对象 │ │ ┌─────────────────┐ │ │ │ buffer[16] │ │ ← "hello\0" 直接存在这里! │ │ "hello\0□□□□□□□"│ │ □ = 未使用 │ ├─────────────────┤ │ │ │ 其他成员 │ │ │ └─────────────────┘ │ └─────────────────────┘ 总计:16字节(一次内存访问,无堆分配)
class String { char buffer[16]; char* ptr; String(const char* s) { if (strlen(s) <= 15) { memcpy(buffer, s, strlen(s)+1); ptr = buffer; } else { ptr = new char[strlen(s)+1]; memcpy(ptr, s, strlen(s)+1); } } };
|
立即复制: 拷贝时立刻复制数据,绝不共享
COW(写时复制)- 延迟复制,共享数据: s1 = "hello" ┌─────┐ ┌───────────┐ │ s1 │────────→│ "hello" │ │ │ │ ref: 1 │ └─────┘ └───────────┘
s2 = s1(COW) ┌─────┐ ┌───────────┐ │ s1 │────────→│ "hello" │←────┐ │ │ │ ref: 2 │ │ └─────┘ └───────────┘ │ │ ┌─────┐ │ │ s2 │───────────────────────────┘ │ │ └─────┘ 问题:s1 和 s2 共享同一块内存!修改会互相影响
立即复制(现代 C++)- 拷贝时立刻独立: s1 = "hello" ┌─────┐ ┌───────────┐ │ s1 │────────→│ "hello" │ │ │ │(独立) │ └─────┘ └───────────┘
s2 = s1(立即复制) ┌─────┐ ┌───────────┐ │ s1 │────────→│ "hello" │ ← s1 的数据 │ │ │(独立) │ └─────┘ └───────────┘ ┌───────────┐ ┌─────┐ │ "hello" │ ← s2 的数据(新分配,内容复制) │ s2 │────────→│(独立) │ │ │ └───────────┘ └─────┘ 结果:s1 和 s2 完全独立!修改互不影响
String(const String& rhs) { size_t len = strlen(rhs.ptr); ptr = new char[len + 1]; memcpy(ptr, rhs.ptr, len + 1); }
|
嗯,这么看来,现代的处理手法更加直接,反而没有引用计数和COW那样巧妙,但是大巧不工,也规避了复杂带来的问题。
一个Reference-Counting(引用计数)基类
为了将引用计数可以广泛的被简易使用,可以设计一个基类,继承它就将获得引用计数相关的能力
class RCObject{ public: RCObject(); RCObject(const RCObject& rhs); RCObject& operator=(const RCObject& rhs); void addReference(); void removeReference(); void markUnshareable() const; bool isShareable() const; bool isShared() const;
protected: virtual ~RCObject() = 0;
private: int refCount; bool shareable; };
RCObject::RCObject() : refCount(0), shareable(true){}
RCObject::RCObject(const RCObject&) : refCount(0), shareable(true){}
RCObject& RCObject::operator=(const RCObject& rhs) { return *this; }
RCObject::~RCObject(){}
void RCObject::addReference(){ ++refConunt; }
void RCObject::removeReference() { if(--refCount == 0) delete this;}
bool RCObject::isShareable() const { return shareable; }
bool RCObject::isShared() const { return refConunt > 1; }
|
运用
class String{ private: struct StringValue: public RCObject{ char *data; StringValue(const char *initValue); ~StringValue(); }; ... };
String::StringValue::StringValue(const char *initValue) { data = new char[strlen(initValue) + 1]; strcpy(data,initValue); }
String::StringValue::~StringValue() { delete[] data; }
|
自动操作Reference Count(引用次数)
结合智能指针,使得可以自动调用RCObject中计数相关的成员函数,而不是手动安插
template<class T> class RCPtr{ public: RCPtr(T* realPtr = 0); RCPtr(const RCPtr& rhs); ~RCPtr(); RCPtr& operator=(const RCPtr& rhs); T* operator->() const; T& operator*() const; private: T *pointee; void init(); };
template<class T> RCPtr<T>::RCPtr(T* realPtr):pointee(realPtr) { init(); }
template<class T> RCPtr<T>::RCPtr(const RCPtr& rhs):pointee(rhs.pointee) { init(); }
template<class T> RCPtr<T>::init() { if(pointee == 0){ return; } if(pointee->isShareable() == false){ pointee = new T(*pointee); } pointee->addReference(); }
|
重点提醒: 为所有内含指针的classes撰写copy constructor及assignment operator
发现上述浅拷贝的问题后,马上修补StringValue
class String{ private: struct StringValue: public RCObject{ StringValue(const StringValue& rhs); ... }; ... };
String::StringValue::StringValue(const StringValue& rhs) { data = new char[strlen(rhs.data) + 1]; strcpy(data,rhs.data); }
|
继续回来,实现RCPtr的其他地方
template<class T> RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs) { if(pointee != rhs.pointee){ if(pointee){ pointee->removeReference(); } pointee = rhs.pointee; init(); } return *this; }
template<class T> RCPtr<T>::~RCPtr() { if(pointee) pointee->removeReference(); }
template<class T> T* RCPtr<T>::operator-() const { return pointee; }
template<class T> T& RCPtr<T>::operator*() const { return *pointee; }
|
把所有努力放在一起
template<class T> class RCPtr { public: RCPtr(T* realPtr = 0); RCPtr(const RCPtr& rhs); ~RCPtr(); RCPtr& operator=(const RCPtr& rhs); T* operator->() const; T& operator*() const; private: T *pointee; void init(); };
class RCObject { public: void addReference(); void removeReference(); void markUnshareable(); bool isShareable() const; bool isShared() const; protected: RCObject(); RCObject(const RCObject& rhs); RCObject& operator=(const RCObject& rhs); virtual ~RCObject() = 0; private: int refCount; bool shareable; };
class String { public: String(const char *value = ""); const char& operator[](int index) const; char& operator[](int index); private: struct StringValue: public RCObject { char *data; StringValue(const char *initValue); StringValue(const StringValue& rhs); void init(const char *initValue); ~StringValue(); }; RCPtr<StringValue> value; };
|
RCObject::RCObject() : refCount(0), shareable(true) {}
RCObject::RCObject(const RCObject&) : refCount(0), shareable(true) {}
RCObject& RCObject::operator=(const RCObject&) { return *this; }
RCObject::~RCObject() {}
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; }
template<class T> void RCPtr<T>::init() { if (pointee == 0) return; if (pointee->isShareable() == false) { pointee = new T(*pointee); } pointee->addReference(); }
template<class T> RCPtr<T>::RCPtr(T* realPtr) : pointee(realPtr) { init(); }
template<class T> RCPtr<T>::RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) { init(); }
template<class T> RCPtr<T>::~RCPtr() { if (pointee)pointee->removeReference(); }
template<class T> RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs) { if (pointee != rhs.pointee) { if (pointee) pointee->removeReference(); pointee = rhs.pointee; init(); } return *this; }
template<class T> T* RCPtr<T>::operator->() const { return pointee; }
template<class T> T& RCPtr<T>::operator*() const { return *pointee; }
void String::StringValue::init(const char *initValue) { data = new char[strlen(initValue) + 1]; strcpy(data, initValue); }
String::StringValue::StringValue(const char *initValue) { init(initValue); }
String::StringValue::StringValue(const StringValue& rhs) { init(rhs.data); }
String::StringValue::~StringValue() { delete [] data; }
String::String(const char *initValue) : value(new StringValue(initValue)) {}
const char& String::operator[](int index) const { return value->data[index]; }
char& String::operator[](int index) { if (value->isShared()) { value = new StringValue(value->data); } value->markUnshareable(); return value->data[index]; }
|
将Reference Counting加到既有的Classes身上
再进一步设计,为任何类型加上reference counting能力,包括无法修改源码的程序库中的类!
加上一层间接性,添加一个新的CountHolder class,用以持有引用计数;将CountHolder继承RCObject,然后令CountHolder内含一个需要拥有引用计数能力的类对象。
剩余的和之前代码差不多(存在细节差异),大体是将内含的StringValue变成了CountHolder
template<class T> class RCIPtr { public: RCIPtr(T* realPtr = 0); RCIPtr(const RCIPtr& rhs); ~RCIPtr(); RCIPtr& operator=(const RCIPtr& rhs); const T* operator->() const; T* operator->(); const T& operator*() const; T& operator*(); private: struct CountHolder: public RCObject { ~CountHolder() { delete pointee; } T *pointee; }; CountHolder *counter; void init(); void makeCopy(); };
template<class T> void RCIPtr<T>::init() { if (counter->isShareable() == false) { T *oldValue = counter->pointee; counter = new CountHolder; counter->pointee = new T(*oldValue); } counter->addReference(); }
template<class T> RCIPtr<T>::RCIPtr(T* realPtr) : counter(new CountHolder) { counter->pointee = realPtr; init(); }
template<class T> RCIPtr<T>::RCIPtr(const RCIPtr& rhs) : counter(rhs.counter) { init(); }
template<class T> RCIPtr<T>::~RCIPtr() { counter->removeReference(); }
template<class T> RCIPtr<T>& RCIPtr<T>::operator=(const RCIPtr& rhs) { if (counter != rhs.counter) { counter->removeReference(); counter = rhs.counter; init(); } return *this; }
template<class T> const T* RCIPtr<T>::operator->() const { return counter->pointee; }
template<class T> const T& RCIPtr<T>::operator*() const { return *(counter->pointee); }
|
有了RCIPtr,RCWidget是实现就很容易,因为RCWidget的每一个函数都只是通过底层的RCIPtr转调用对应的Widget函数。
class Widget { public: Widget(int size); Widget(const Widget& rhs); ~Widget(); Widget& operator=(const Widget& rhs); void doThis(); int showThat() const; };
class RCWidget { public: RCWidget(int size) : value(new Widget(size)) {} void doThis() { value->doThis(); } int showThat() const { return value->showThat(); } private: RCIPtr<Widget> value; };
|
OK了,家人们,结束了终于!最后让我总结一下: