Reference counting,允许多个等值对象共享同一实值,目的是:第一,简化heap objects的回收工作,建构出垃圾回收机制(grabage collection)的一个简单形式,第二,相同的值不需要存储多次,节省内存和加快程序的速度;
为每一个String存储引用次数很重要,但是那个空间不可以设计在String对象内,因为是要为每一个字符串值准备一个引用次数,而不是为每一个字符串对象准备;
class MyString{
public:
MyString(const char* initValue);
MyString(const MyString& rhs);
MyString& operator=(const MyString& rhs);
~MyString();
private:
struct StringValue // private域 内部类
{
int refCount;
char *data;
StringValue(const char *initValue);
~StringValue();
};
StringValue* value;
};
MyString::MyString(const char* initValue):value(new StringValue(initValue)){
}
MyString::MyString(const MyString& rhs):val ue(rhs.value){
++(value->refCount); // 发生拷贝时,单纯增加引用计数(浅拷贝)
}
MyString& MyString::operator=(const MyString& rhs){
if (this->value == rhs.value) // 等号运算符判同
return *this;
if (--value->refCount == 0) // 先对原本引用的对象进行"释放"处理
delete value;
value = rhs.value; // 浅拷贝
++(value->refCount); // 增加引用计数
return *this;
}
MyString::~MyString(){
if ( --(value->refCount) == 0)
delete value;
}
MyString::StringValue::StringValue(const char *initValue) :refCount(1){ // 引用计数初始化为1
data = new char[strlen(initValue) + 1]; // 深拷贝方式构造data指针的内容(因为传的内容不一定是位于堆内存)
strcpy(data, initValue);
}
MyString::StringValue::~StringValue(){
delete[] data;
}
StringValue类存储引用次数也存储所追踪的对象值(对象值和引用次数之间的耦合关系),将其嵌套放在String的private段落内,所有的String member functions 都能访问StringValue(外部不能访问该类),可以方便的让该类的所有成员有权处理该类,又能禁止任何其他人访问这个类;
operaotr[]操作符可返回 cosnt 和 non-const的成员,而non-const会有被修改的风险;
为了实现安全的non-const operator[],必须确保没有其他任何“共享同一个StringValue”的MyString对象因写动作而改变(编译器无法告知operator[]被用于读还是写,条款30的代理类可以区分),简单说,任何时候返回一个reference,指向MyString的StringValue对象内的一个字符时,必须确保该StringValue对象的引用次数为1;
char& MyString::operator[](size_t index){
if (value->refCount > 1){ // 当引用计数大于1时(存在其他无关对象同时被修改的风险时)
--(value->refCount); // 放弃当前引用
value = new StringValue(value->data); // 为自己做一份当前副本
}
return value->data[index]; // 返回的对象确保了引用次数为1
}
MyString s1 = "hello";
char *p = &s1[1]; // p获得s1的“访问许可”
String s2 = s1;
*p = 'x'; // s1 和 s2 同时被修改
共有三种方法处理此问题:
第一种:忽略,普遍为“引用计数字符串”类库采用;
第二种:在文档中说明此种情况;
第三种:为每一个StringValue对象加上一个标志(flag)变量,用以指示是否可被共享,一旦non const operator[]作用于对象值身上就将标志设置为false;
MyString::MyString(const MyString& rhs){
if (rhs.value->shareable){ // 改变了拷贝时的操作,进行共享判断(实际上增加了StringValue的信息量,shareable描述了是否暴露修改的接口)
value = rhs.value;
++(value->refCount);
}
else
value = new StringValue(rhs.value->data);
}
char& MyString::operator[](size_t index){
if (value->refCount > 1){
--(value->refCount);
value = new StringValue(value->data);
}
value->shareable = false; // 发生operator[]时,停止共享
return value->data[index];
}
class RCObject{
public:
RCObject();
RCObject(const RCObject& rhs);
RCObject& operator=(const RCObject& rhs);
virtual ~RCObject() = 0;
void addReference();
void removeReference();
void markUnshareable();
bool isShareable() const;
bool isShared() const;
private:
int refCount;
bool shareable;
};
RCObject::RCObject():refCount(0),shareable(true){
}
RCObject::RCObject(const RCObject& rhs):refCount(0), shareable(true){
}
RCObject& RCObject::operator=(const RCObject& rhs){
return *this;
}
void RCObject::markUnshareable(){
shareable = false;
}
void RCObject::removeReference(){
if ((--refCount) == 0)
delete this;
}
void RCObject::addReference(){
++refCount;
}
bool RCObject::isShared() const{
return (refCount > 1);
}
bool RCObject::isShareable()const{
return shareable;
}
任何类希望拥有引用计数能力,都必须继承自这个类,RCObject将“引用计数器”本身和用以增减计数器的函数封装进来RCOject只开放了计数器的接口;
计数器的改变依赖于在类中手动安插好,但是可以通过在智能指针对计数器的计数进行控制,具体实现和之前的智能指针相差不大;
template
class RCPtr{
public:
RCPtr(T* realPtr = 0);
RCPtr(const RCPtr& rhs);
RCPtr& operator=(const RCPtr& rhs);
T& operator*() const;
T* operator->() const;
~RCPtr(){delete T};
private:
T *pointee;
void init();
void makeCopy();
};
template
RCPtr::RCPtr(T* realPointer):pointee(realPointer){
init();
}
template
RCPtr::RCPtr(const RCPtr& rhs):pointee(rhs.pointee){
init();
}
template
T& RCPtr::operator*() const{
makeCopy();
return *pointee;
}
template
T* RCPtr::operator->() const{
makeCopy();
return pointee;
}
template
void RCPtr::init(){
if (pointee == 0)
return;
if (pointee->isShareable() == false)
pointee = new(*pointee); //因为pointee有可能指向T派生类对象,所以这需要在派生类对象提供相应的拷贝构造函数(虚构造)
pointee->addReference();
}
对库的组件加入这类功能可以加入一层间接性,使得RCObject为间接类的组成部分,并使用智能指针管理实际资源,而库原本使用实际资源的部分,就用间接类代替;
template
class Array2D
{
public:
Array2D(int dim1, int dim2);
~Array2D();
T& operator[][](int index1, int index2); // 错误
const T& operator[][](int index1, int index2); // 错误
...
};
Array2D data(10, 20); // 允许
Array2D *data = new Array2D(10, 20); //允许
int *data = new int[4][4]; // 错误
Array2D data[10][10]; // 错误
因为没有operator[][],所以编译器无法实现这样的函数;
template
class Array2D
{
public:
class Array1D // 内部类
{
public:
T& operator[](int index);
const T& operator[](int index);
};
Array1D operator[](int index);
const Array1D operator[](int index);
};
把operator[][]划分为两个操作符动作,在内部类实现第二维度动作(抽象来说,每个二维数组都是一个数组内每一个元素都是一维数组的一维数组);
class String{
public:
String(const String& rhs);
String(char* str);
const String& operator=(const String& rhs) const; // 针对读取
const char& operator[](int index) const; // 针对写入
char& operator[](int index);
private:
char* str;
};
String s1, s2;
...
cout << s1[5]; // 读取
s2[5] = 'a'; // 写入
编译器在const和non-const 成员函数之间的选择,只是以“调用该函数的对象是否是const”为基准,并不考虑它们在什么情况下被调用,所以不能用返回类型是否const来区分读写;
使用lazy-evluation来使operator[]的读写决议进行延迟,基于以下一个事实:默认返回的operator[]是以默认可以写的状态返回的(返回-----可读可写),或许我们不知道operator[]是在左值还是右值情况下被调用,但是通过要处理的动作(使用动作)延缓,直到operator[]的返回结果将如何被使用为止(真正决议的时候),再去做相应的动作(返回------使用决议------读或写),使用代理类可以在真正决议的时候,通过操作函数不同来区分当前动作是读还是写;
class String{
public:
class CharProxy{
public:
CharProxy(String& str, int index);
CharProxy& operator=(const CharProxy& rhs); // 左值引用
CharProxy& operator=(char x); // 左值引用
char& operator&(); // 用于 char* p = &str[1]情况,要消除中间的间接性
const char& operator&() const;
operator char() const; // 右值引用
private:
String& theString;
int charIndex;
};
String(const String& rhs);
String(char* str);
const String& operator=(const String& rhs) const;
const CharProxy& operator[](int index) const;
CharProxy& operator[](int index);
private:
char* str;
};
限制:
无法“通过proxies调用真实对象的成员函数”:
Array stringArray;
cout<
无法将它们传递给“接受references to non-const objects”的函数:
void swap(char& a, char& b);
String s = "+C+";
swap(s[0], s[1]); // 发生隐式转换, proxy类被转换为char, 但是无法绑定到non-const引用参数上
proxy被隐式转换为它真正代表的对象时,无法执行真正对象需要的隐式转换了(编译器在“将调用端自变量转换为对应的被调用端(函数)参数过程中,运用“用户定制转换函数”的次数只限一次”);
--------预留------------------预留------------------预留------------------预留------------------预留------------预留--------