当我们在自定义的类中重载[]的时候,无论我们定义的对象是读操作还是写操作,这个operator都无法对其进行判别,于是我们起初的操作是,把所有调用operator[] 的操作默认为写操作,继续上一章的内容,当作写操作的话,需要判定是否可isShared(),然后StringValue就要new一份新对象给左操作数。如果只是读操作,还进行这个流程无非是一种浪费。
所以,这次的做法是延缓赋值操作,直到知道该动作是读取还是写入。如果是读取,我们可以写一个隐式转换函数,如果害是写入就可以按照之前的做法。涉及到StringValue的内容请看见上一章内容
下面的代码是more effective Item M30章节的:
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;
};
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 &ths){
if (theString.value->isShared()){
theString.value = new StringValue(theString.value->data);
}
theString.value->data[charIndex] = rhs.theString.value->data[ths.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;
}
这堆代码需要解释几点地方,
第一个,operator char(),这个是为了实现操作只读取str[2];通过下标运算符得到了一个CharProxy对象,为了输出,编译器会查找是否可以转换为内置类型或者说是编译器能够实施的隐世类型转换,于是发现了char(),转为char便可以输出。
第二个,定义了两个不同的operator=的目的是 为了实现这种操作:
String s1,s2;
s1[2]='x';
s1[2]=s2[1];
第三点,将嵌套类CharProxy定义为String的友元,原因是,在operator=中会有对String的私有变量value的操作
这种操作也有弊端而且还不少;
不如进行以下操作:
String s1=“string”; char *p=&s1[1];
会在第二步发生错误,因为不存在一个隐式转换将CharProxy*转为char*,摘自书上一句话:“一般而言,对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&(){
if (theString.value->isSharead()){
theString.value = new StringValue(theString.value->data);
}
theString.value->makeUnshareable();
return &(theString.value->data[charIndex]);
}
另一个存在的问题就是使用proxy与chars的差别还有就是一下情况,operator[]返回的是一个proxy,而proxy里并没有关于+=和++的重载,所以这些都是未定义错误,若想让这些正常,就得自己一个一个的重载这些运算符。
class Array{
public:
class Proxy{
public:
Proxy(Array<T>&arr, int index);
Proxy&operator=(const T&rhs);
operator T()const;
};
const Proxy operator[](int index)const;
Proxy operator[](int index);
};
int main(void){
Array<int >intArray;
intArray[2] = 2;//accept
intArray[2] += 2;//wrong
intArray[2]++;//wrong
}
大多数问题都是在operator[]的返回类型引起的,他是一个替身类并不能调用真实对象的member functions。
最后一个问题是在他的隐式转换,编译器在将调用端自变量转换为对应的被调用端参数的过程中,运用用户定制转换函数的次数只限一次,于是可能会有以下情况发生:以真实对象传给函数,成功;以proxies传给函数,失败。这个问题也是operator的返回类型无法隐式转换为要求的类型导致的。
最后的最后说说proxy的各方面优劣把,proxy class可以做多维数组,可以区别左右值,可以压抑隐式转换。但是问题也是出现在他是替身对象,会出现一些返回类型与要求不符的情况,还有就是如果是函数返回值的角色,proxy objects就是一种临时变量,需创建和销毁,这也是一部分的消耗。