您的位置:首页 > 运维架构

使用代理类区分operator[]进行的是读操作还是写操作 — 懒惰计算思想的运用

2016-11-21 11:31 302 查看
扮演其它对象的对象通常被称为代理类

在代理类的各种用法中,最神奇的是帮助区分通过operator[]进行的是读操作还是写操作

我们想区分将operator[]用作左值还是右值,因为,对于有引用计数的数据结构,读

操作的代价可以远小于写操作的代价。前面所讲的引用计数的使用,引用计数对象的写操作将导致整个数据结构的拷贝,而读不需要,只要简单地返回一个值。不幸的是,在operator[]内部,没有办法确定它是怎么被调用的,不可能区分出它是做左值还是右值。

我们的方法基于这个事实:也许不可能在operator[]内部区分左值还是右值操作,但

我们仍然能区别对待读操作和写操作,如果我们将判断读还是写的行为推迟到我们知道

operator[]的结果被怎么使用之后的话。 我们所需要的是有一个方法将读或写的判断推迟到operator[]返回之后。 (这是lazy原则 ) 

proxy类可以让我们得到我们所需要的时机,因为我们可以修改operator[]让它返回

一个(代理字符的)proxy对象而不是字符本身。我们可以等着看这个proxy怎么被使用。如果是读它,我们可以断定operator[]的调用是读。如果它被写,我们必须将operator[]的调用处理为写。

我们马上来看代码,但首先要理解我们使用的proxy类。在proxy类上只能做三件事:  

   1、创建它,也就是指定它扮演哪个字符。 

   2、将它作为赋值操作的目标,在这种情况下可以将赋值真正作用在它扮演的字符上。

这样被使用时,proxy类扮演的是左值。 

   3、用其它方式使用它。这时,代理类扮演的是右值。 

这里是一个被带引用计数的string类用作proxy类以区分operator[]是作左值还是右

值使用的例子:

 

class String {                    // reference-counted strings; 

public:                           // see Item 29 for details 

  class CharProxy {               // proxies for string chars 

  public: 

    CharProxy(String& str, int index);                // creation 

    CharProxy& operator=(const CharProxy& rhs);       // lvalue 

    CharProxy& operator=(char c);                     // uses 

    operator char() const;                            // rvalue 

                                                      // use 

    char * operator&(); 
    const char * operator&() const; 

  private: 

    String& theString;         // string this proxy pertains to 

    int charIndex;               // char within that string 

                                  // this proxy stands for   

   };

 

  // continuation of String class 

  const CharProxy 

  operator[](int index) const;   // for const Strings 

  CharProxy operator[](int index); // for non-const Strings 

  ... 

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& rhs) 



  // if the string is sharing a value with other String objects, 

  // break off a separate copy of the value for this string only 

  if (theString.value->isShared()) { 

    theString.value = new StringValue(theString.value->data); 

  } 

  // now make the assignment: assign the value of the char 

  // represented by rhs to the char represented by *this 

  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;



const char * String::CharProxy::operator&() const 



  return &(theString.value->data[charIndex]); 



String::CharProxy::operator&() 



  // make sure the character to which this function returns 

  // a pointer isn't shared by any other String objects 

  if (theString.value->isShared()) { 

    theString.value = new StringValue(theString.value->data); 

  } 

  // we don't know how long the pointer this function 

  // returns will be kept by clients, so the StringValue 

  // object can never be shared 

  theString.value->markUnshareable(); 

  return &(theString.value->data[charIndex]); 



除了增加的CharProxy类(我们将在下面讲解)外,这个String类与前面引用计数的使用中的最终版本相比,唯一不同之处就是所有的operator[]函数现在返回的是CharProxy对象。然而,String类的用户可以忽略这一点,并当作operator[]返回的仍然是通常形式的字符(或其引用)来编程:

String s1, s2;           // reference-counted strings 

                         // using proxies 

... 

cout << s1[5];           // still legal, still works 

s2[5] = 'x';             // also legal, also works 

s1[3] = s2[8];           // of course it's legal, 

                         // of course it works 

有意思的不是它能工作,而是它为什么能工作。

先看这条语句: 

cout << s1[5]; 

表达式s1[5]返回的是一CharProxy对象。没有为这样的对象定义输出流操作,所以编

译器努力地寻找一个隐式的类型转换以使得operator<<调用成功 。它们找到一个:在CahrProxy类内部申明了一个隐式转换到char的操作。于是自动调用这个转换操作,结果就是CharProxy类扮演的字符被打印输出了。这个CharProxy到char的转换是所

有代理对象作右值使用时发生的典型行为。 

作左值时的处理就不一样了。再看: 

s2[5] = 'x'; 

和前面一样, 表达式s2[5]返回的是一个CharProxy对象, 但这次它是赋值操作的目标。

由于赋值的目标是CharProxy类,所以调用的是CharProxy类中的赋值操作。这至关重要,因为在CharProxy的赋值操作中, 我们知道被赋值的CharProxy对象是作左值使用的。 因此,我们知道proxy类扮演的字符是作左值使用的, 必须执行一些必要的操作以实现字符的左值操作。 

同理,语句 

s1[3] = s2[8]; 

调用作用于两个CharProxy对象间的赋值操作,在此操作内部,我们知道左边一个是

作左值,右边一个作右值 。

局限性:

当operator[]作最简单的赋值操作的目标时,是成功的,但当它出现

operator+=和operator++的左侧时,失败了。因为operator[]返回一个proxy对象,而它

没有operator+=和operator++操作。同样的情况存在于其它需要左值的操作中,包括

operator*=、operator<<=、operator--等等。如果你想让这些操作你作用在operator[]上,必须为Arrar<T>::Proxy类定义所有这些函数。这是一个极大量的工作,你可能不愿意去做的。不幸的是,你要么去做这些工作,要么没有这些操作,不能两全。 

一个类似的问题必须面对:通过proxy对象调用实际对象的成员函数。想避开它是不

可能的。例如,假设我们用带引用计数的数组处理有理数。我们将定义一个Rational类,

然后使用前面看到的Array模板:  

class Rational { 

public: 

  Rational(int numerator = 0, int denominator = 1); 

  int numerator() const; 

  int denominator() const; 

  ... 

}; 

Array<Rational> array; 

这是我们所期望的使用方式,但我们很失望: 

cout << array[4].numerator();                     // error! 

int denom = array[22].denominator();              // error! 

现在,不同之处很清楚了;operator[]返回一个proxy对象而不是实际的Rational对

象。但成员函数numerator()和denominator()只存在于Rational对象上,而不是其proxy

对象。因此,你的编译器发出了抱怨。要使得proxy对象的行为和它们所扮演的对象一致,你必须重载可作用于实际对象的每一个函数。 

另一个proxy对象替代实际对象失败的情况是作为非const的引用传给函数: 

void swap(char& a, char& b);                      // swaps the value of a and 



String s = "+C+";                                 // oops, should be "C++" 

swap(s[0], s[1]);                                 // this should fix the 

                                                  // problem, but it won't 

                                                  // compile String::operator[]返回一个CharProxy对象,但swap()函数要求它所参数是char &类型。一个CharProxy对象可以印式地转换为一个char,但没有转换为char  &的转换函数。而它可能转换成的char
b860
并不能成为swap的char &参数, 因为这个char是一个临时对象 (它是operator char()的返回值) ,拒绝将临时对象绑定为非const的引用的形参是有道理的。 

最后一种proxy对象不能无缝替换实际对象的情况是隐式类型转换。当proxy对象隐

式转换为它所扮演的实际对象时,一个用户自定义的转换函数被调用了。例如,一个

CharProxy对象可以转换为它扮演的char,通过调用operator char()函数。编译器在调用函数而将参数转换为此函数所要的类型时,只调用一个用户自定义的转换函数。于是,很可能在函数调用时,传实际对象是成功的而传proxy对象是失败的。例如,

我们有一个TVStation类和一个函数watchTV(): 

class TVStation { 

public: 

  TVStation(int channel); 

  ... 

}; 

void watchTV(const TVStation& station, float hoursToWatch); 

借助于int到TVStation的隐式类型转换 ,我们可以这么做: 

watchTV(10, 2.5);                       // watch channel 10 for 

                                        // 2.5 hours 

然而,当使用那个用proxy类区分operator[]作左右值的带引用计数的数组模板时,

我们就不能这么做了:

 

Array<int> intArray; 

intArray[4] = 10; 

watchTV(intArray[4], 2.5);              // error! no conversion 

                                        // from Proxy<int> to 

                                        // TVStation 

由于问题发生在隐式类型转换上,它很难解决。实际上,更好的设计应该是申明它的

构造函数为explicit,以使得第一次的调用watchTV()的行为都编译失败。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: