【C++ techniques】利用Proxy classes(代理类)实现:多维数组、区分左/右值运用、限制隐式类型转换

一、实现多维数组

C++中支持多维数组的方法:产生一个class,用以表现我们有需要却被语言遗漏的对象;

//定义一个类模板如下:
template<class T>
class Array2D
{
public:
	Array2D(int dim1,int dim2);
	...
};
  1. operator[]重载,令它返回一个Array1D对象;
  2. 对Array1D重载operator[],令它返回原来二维数组中的一个元素:
template<class T>
class Array2D
{
public:
	class Array1D
	{
	public:
		T& operator[](int index);
		const T& operator[](int index) const;
		...
	};
	
	Array1D operator[](int index);
	const Array1D operator[](int index) const;
	...
};

//调用
Array2D<float> data(10,20);
...
cout << data[3][6];//ok

data[3]获得一个Array1D对象,对该对象再施行operator[],获得原二维数组中(3, 6)位置的浮点数;Array2D类的用户不需要知道Array1D类的存在。

  • 凡“用来代表(象征)其他对象”的对象,常被称为proxy objects(替身对象);
  • 用以表现proxy objects者,我们称为proxy classes

二、区分operator[] 的读写动作

对于一个proxy,你只有3件事情可做:

  1. 产生它,本例也就是指定它代表哪一个字符串中的哪一个字符;
  2. 以它作为赋值动作的目标(接受端),这种情况下你是对它所代表的字符串内的字符做赋值动作。如果这么使用,proxy代表的将是“调用operator[]函数”的那个字符串的左值运用
  3. 以其他方式使用之。如果这么使用,proxy表现的是“调用operatorp[]函数”的那个字符串的右值运用
//一个reference-counted String class,
//其中利用proxy class来区分operator[]的左值运用和右值运用:

class String 	//reference-counted strings见上一篇细节
{
public:
	class CharProxy
	{
	public:
		CharProxy(String& str,int index);				//构造
		CharProxy& operator = (const CharProxy& rhs);	//左值运用
		CharProxy& operator = (char c);					//右值运用
		
		operator char() const;
		
	private:
		String& theString;						//这个proxy附属的字符串
		int charIndex;							//这个proxy所代表的字符串字符
	};
	
	const CharProxy operator[](int index) const; //针对const strings
	CharProxy operator[](int index);			 //针对non-const strings
	...
	friend class CharProxy;
	
private:
	RCPtr<StringValue> value;
};

//调用
String s1,s2;
...
cout << s1[5];    //合法(右值运用)
s2[5] = 'x';	  //合法(左值运用)
s1[3] = s2[8];    //合法(左值、右值)

//String operator[]实现:
const String::CharProxy String::operator[](int index) const
{
	return CharProxy(const_cast<String&>(*this), index);
}
 
 String::CharProxy String::operator[](int index)
 {
	 return CharProxy(*this, index);
 } 

每个函数都只是产生并返回“被请求之字符”的一个替代品。没有任何动作施加于此字符身上:我们延缓此等行为,直到知道该行为是“读取”或者“写”。

operator[]返回的每一个proxy都会记住它所附属的字符串,以及它所在的索引位置:

String::CharProxy::CharProxy(String& str,int index)
	:theString(str),charIndex(index){}

//将proxy转换为右值:只需要返回该proxy所表现的字符串副本就行:
 
String::CharProxy::operator char() const
{
	return theString.value->data[index];
}
//CharProxy的赋值操作符:
String::CharProxy&
String::CharProxy::operator = (const CharProxy& rhs)
{
	//如果本字符串与其他String对象共享一个实值
	//将实值复制一份,供本字符串单独使用
	if(theString.value->isShared()){
		theString.value = new StringValue(theString.value->data);
	}
	//现在进行赋值动作:将rhs所代表的字符值
	//赋予*this所代表的字符
	theString.value->datap[charIndex] = 
				rhs.theString.value->data[rhs.charIndex];
	return *this;

//第二个CharProxy赋值操作符和上述传统版本几乎雷同:
CharProxy& String::CharProxy::operator = (char c)
{
	if(theString.value->isShared())
		theString.value = new StringValue(theString.value->data);
	
	theString.value->data[charIndex] = c;
	return *this;
}

//将上述两个赋值操作符的重复代码抽出来放进一个私有的CharProxy成员函数
//然后让两个操作符都去调用它

三、限制隐式类型转换

难点:“对proxy取址”所获取的指针类型和“对真实对象取址”获取的指针类型不同。

解决:需要在CharProxy类内将取址操作符加以重载:

class String
{
public:
	class CharProxy
	{
	public:
		...
		char* operator&();
		const char* operator() const;
		...
	};
	...
};
const char* String::CharProxy::operator() const
{
	return &(theString.value->data[charIndex]);
}
 
char* String::CharProxy::operator&()
{
	//确定“标的字符”所属的字符串实值不为任何其他任何String对象共享
	if(theString.value->isShared()){
		theString.value = new StringValue(theString.value->data);
	}
	//我们不知道clients会将本函数返回的指针保留多久,所以“目标字符”所属的字符串实值绝不可以被共享
	theString.value->markUnshanreable();
	
	return &(theString.value->data[charIndex]);
}

总结

  • Proxy类的优点:
    允许我们完成某些几乎不可能完成的行为:多维数组、左值/右值的区分、压抑隐式转换就是其三。

  • Proxy类的缺点:
    如果扮演函数返回值的角色,那些proxy对象将是一种临时对象,需要被产生和销毁,构造和析构带来的成本远大于proxies带来的好处,且软件复杂度和也随之增加。

你可能感兴趣的:(C++进阶,c++,开发语言,笔记,算法)