《More Effective C++》学习笔记(五)——第二部分

技术    Techniques, Idioms, Patterns

条款29:Reference counting(引用计数)

    Reference counting,允许多个等值对象共享同一实值,目的是:第一,简化heap objects的回收工作,建构出垃圾回收机制(grabage collection)的一个简单形式,第二,相同的值不需要存储多次,节省内存和加快程序的速度;

   一个reference-counted String(引用计数的字符串)类

    为每一个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(外部不能访问该类),可以方便的让该类的所有成员有权处理该类,又能禁止任何其他人访问这个类;

    Copy-on-Write(写时复制)

    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
}

    Pointer,References,以及Copy-on-Write

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

    一个 Reference-Counting(引用计数)基类

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只开放了计数器的接口;

    自动操作Referece Count(引用计数)

    计数器的改变依赖于在类中手动安插好,但是可以通过在智能指针对计数器的计数进行控制,具体实现和之前的智能指针相差不大;

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();
}

将Reference Counting 加到既有的Classes身上

    对库的组件加入这类功能可以加入一层间接性,使得RCObject为间接类的组成部分,并使用智能指针管理实际资源,而库原本使用实际资源的部分,就用间接类代替;

条款 30:Proxy classes(替身类, 代理类)

    实现二维数组  

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[][]划分为两个操作符动作,在内部类实现第二维度动作(抽象来说,每个二维数组都是一个数组内每一个元素都是一维数组的一维数组);

    区分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被隐式转换为它真正代表的对象时,无法执行真正对象需要的隐式转换了(编译器在“将调用端自变量转换为对应的被调用端(函数)参数过程中,运用“用户定制转换函数”的次数只限一次”);

条款 31:让函数根据一个以上的对象类型来决定如何虚化

    --------预留------------------预留------------------预留------------------预留------------------预留------------预留--------

你可能感兴趣的:(学习笔记)