第五章,技术
30-Proxy classes(替身类、代理类)
实现二维数组
C++中数组的大小必须在编译期已知,因此如下的代码就不会通过编译
void processInput(int dim1, int dim2) { int data[dim1][dim2]; int *data = new int[dim1][dim2]; }
|
要解决上面不能直接定义的问题,往往可以通过定义一个class来实现二维数组
template<class T> class Array2D{ public: Array2D(int dim1, int dim2); ... };
Array2D<int> data(10,30);
Array2D<float> *data = new Array2D<float>(10,20);
void processInput(int dim1, dim2); { Array2D<int> data(dim1, dim2); ... }
|
那么怎么像内置类型的数组那样通过[][]得到数组元素?重载operator [][] ? 但是根本没有operator [][] 这种东西。那么用operator()来替代呢?就像下面这样
template<class T> class Array2D{ public: T& operator()(int index1, int index2); const T& operator()(int index1, int index2) const; ... }
Array2D<int> data(10,30); ... cout << data(3,6);
|
嗯,重载operator()在调用的时候更像函数调用,而不是内建数组;或者回到以方括号为索引操作符的思路上,虽然没有operatpr[][],但下面的写法是合法的:
int data[10][20]; ... cout << data[3][6];
|
按照这个思路,回到Array2D class的设计,将operator[]重载,令它返回一个Array1D对象,然后再对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];
|
在这个例子中,Array1D就是一个替身对象proxy objects;替身对象: “临时占位的假对象”,它本身不做实际业务功能,只用来占位置、帮真正的对象完成构造、初始化、内存分配、类型匹配等工作
区分operator[]的读写动作
哇,在原著”开口”前,我要说,又是这家伙,上次说是没法区分,现在终于可以看看怎么区分了…
在proxy classes的各种用途之中,最先驱的当属协助区分operator[]的读写动作了。
String s1, s2; ... cout << s1[5]; s2[5] = 'x'; s1[3] = s2[8];
class String{ public: const char& operator[](int index) const; char& operator[](int index); ... };
class String{ public: class CharProxy{ public: CharProxy(String& str, int index); CharProxy& operator=(const CharProxy& rhs); CharProxy& operator=(char c);
operator char() const;
private: String& theString; int charIndex; }; const CharProxy; operator[](int index) const; CharProxy operator[](int index); ... friend class CharProxy;
private: RCPtr<StringValue> value; };
String s1,s2; cout << s1[5];
s2[5] = 'x';
s1[3] = s2[8];
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); }
String::CharProxy::CharProxy(String& str, int index) : theString(str), charIndex(index) {}
String::CharProxy::operator char() const { return theString.value->data[charIndex]; }
String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs) { if(theString.value->isShared()){ theString.value = new StringValue(theString.value->data); }
theString.value->data[charIndex] = rhs.theString.value->data[rhs.charIndex]; return *this }
String::CharProxy& String::CharProxy::operator=(char c) { if(theString.value->isShared()){ theString.value = new StringValue(theString.value->data); } theString.value->data[charIndex] = c; return *this }
class String{ public: class CharProxy{ public: ... CharProxy& operator=(const CharProxy& rhs); CharProxy& operator=(char c); private: void cow(); ... }; ... };
void String::CharProxy::cow() { if(theString.value->isShared()){ theString.value = new StringValue(theString.value->data); } }
String::CharProxy& String::CharProxy::operator=(char c) { cow(); theString.value->data[charIndex] = c; return *this }
String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs) { theString.value->data[charIndex] = static_cast<char>(rhs); return *this }
|
限制
Proxy class很适合用于区分operator[]的左值运用和右值运用,但是这项技术仍有一些缺点。
String s1 = "Hellow"; char *p = &s1[1];
class String{ class CharProxy{ public: ... char * operator&(); const char * operator&()const; ... }; ... };
const char * String::CharProxy::operator&() const { return &(theString.value->data[charIndex]); }
char * String::CharProxy::operator&() { if(theString.value->isShared()){ theString.value = new StringValue(theString.value->data); }
theString.value->makeUnShareable(); return &(theString.value->data[charIndex]); }
template<class T> class Array{ class Proxy{ public: Proxy(Array<T>& array, int index); Proxy& operator=(const T& rhs); operator T() const; ... };
const Proxy operator[] (int index) const; Proxy operator[](int index); ... };
Array<int> intArray; ... intArray[5] = 22; intArray[5] += 5; intArray[5]++;
class Rational{ public: Rational(int numerator = 0, int denominator = 1); int numerator() const; int denominator() const; ... };
Array<Rational> array; cout << array[4].numerator(); int denom = array[22].denominator();
void swap(char& a, char& b); String s = "+C+"; swap(s[0],s[1]);
class TVStation{ public: TVStation(int channel); ... };
void watchTV(const TVStation& station, float hoursToWatch);
watchTV(10,2.5);
Array<int> intArray; intArray[4] = 10; watchTV(intArray[4], 2.5);
|
总结
Proxy classes允许我们完成一些看似不可能的功能。多维数组的实现、左/右值的区分,压抑隐式转换。
但是也有缺点,如果扮演函数返回值的角色,那些proxy objects将是一种临时对象,需要被产生和被销毁,也就带来了构造析构的成本。此外也增加了软件系统的复杂度,使产品更难设计、实现、了解、 维护。
最后,当class的身份从“与真实对象合作”转移到“与替身对象(peoxies)合作”,往往会造成class语义的改变,因为proxy objects展现的行为常常和真正对象的行为有些隐微差异。有时会造成proxies在系统设计上的弱势,不过其实很少需要用到“proxies和真实对象有异”的那些操作行为,比如很少会有需求对一个使用proxy实现的Array1D对象取地址,它往往是不可见的,辅助实现功能;也很少会将proxy替身类对象作为参数传递给函数。
在多数情况下,proxies可以完美取代所代表的真正的对象,而两者间的隐微差异不是重点。