Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1790995
  • 博文数量: 600
  • 博客积分: 10581
  • 博客等级: 上将
  • 技术积分: 6205
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-06 10:13
文章分类
文章存档

2016年(2)

2015年(9)

2014年(8)

2013年(5)

2012年(8)

2011年(36)

2010年(34)

2009年(451)

2008年(47)

分类: C/C++

2009-08-26 15:30:04

 

l        局限性

使用proxy类是个区分operator[]作左值还是右值的好方法,但它不是没有缺点的。我们很喜欢proxy对象对其所扮演的对象的无缝替代,但这很难实现。这是因为,右值不只是出现在赋值运算的情况下,那时,proxy对象的行为就和实际的对象不一致了。

再看一下来自于Item M29的代码,以证明我们为什么为每个StringValue对象增加一个共享标志。如果String::operator[]返回一个CharProxy而不是char &,下面的代码将不能编译:

String s1 = "Hello";

char *p = &s1[1];            // error!

表达式s1[1]返回一个CharProxy,于是“=”的右边是一个CharProxy *。没有从CharProxy *char *的转换函数,所以p的初始化过程编译失败了。通常,取proxy对象地址的操作与取实际对象地址的操作得到的指针,其类型是不同的。

要消除这个不同,你需要重载CharProxy类的取地址运算:

class String {

public:

  class CharProxy {

  public:

    ...

    char * operator&();

    const char * operator&() const;

    ...

  };

  ...

};

这些函数很容易实现。const版本返回其扮演的字符的const型的指针:

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

{

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

}

const版本有多一些操作,因为它返回的指针指项的字符可以被修改。它和Item M29中的非constString::operator[]行为相似,实现也很接近:

char * 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的其它成员函数有很多相同,所以我知道你将把它们封入一个私有成员函数。

char和代理它的CharProxy的第二个不同之处出现带引用计数的数组模板中如果我们想用proxy类来区分其operator[]作左值还是右值时:

template                        // reference-counted array

class Array {                            // using proxies

public:

  class Proxy {

  public:

    Proxy(Array& array, int index);

    Proxy& operator=(const T& rhs);

    operator T() const;

    ...

  };

  const Proxy operator[](int index) const;

  Proxy operator[](int index);

  ...

};

看一下这个数组可能被怎样使用:

Array intArray;

...

intArray[5] = 22;                    // fine

intArray[5] += 5;                    // error!

++intArray[5];                       // error!

如我们所料,当operator[]作最简单的赋值操作的目标时,是成功的,但当它出现operator+=operator++的左侧时,失败了。因为operator[]返回一个proxy对象,而它没有operator+=operator++操作。同样的情况存在于其它需要左值的操作中,包括operator*=operator<<=operator--等等。如果你想让这些操作你作用在operator[]上,必须为Arrar::Proxy类定义所有这些函数。这是一个极大量的工作,你可能不愿意去做的。不幸的是,你要么去做这些工作,要么没有这些操作,不能两全。

一个类似的问题必须面对:通过proxy对象调用实际对象的成员函数。想避开它是不可能的。例如,假设我们用带引用计数的数组处理有理数。我们将定义一个Rational类,然后使用前面看到的Array模板:

class Rational {

public:

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

  int numerator() const;

  int denominator() const;

  ...

};

Array 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 b

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并不能成为swapchar &参数,因为这个char是一个临时对象(它是operator char()的返回值),根据Item M19的解释,拒绝将临时对象绑定为非const的引用的形参是有道理的。

最后一种proxy对象不能无缝替换实际对象的情况是隐式类型转换。当proxy对象隐式转换为它所扮演的实际对象时,一个用户自定义的转换函数被调用了。例如,一个CharProxy对象可以转换为它扮演的char,通过调用operator char()函数。如Item M5解释的,编译器在调用函数而将参数转换为此函数所要的类型时,只调用一个用户自定义的转换函数。于是,很可能在函数调用时,传实际对象是成功的而传proxy对象是失败的。例如,我们有一个TVStation类和一个函数watchTV()

class TVStation {

public:

  TVStation(int channel);

  ...

};

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

借助于intTVStation的隐式类型转换(见Item M5),我们可以这么做:

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

                                        // 2.5 hours

然而,当使用那个用proxy类区分operator[]作左右值的带引用计数的数组模板时,我们就不能这么做了:

Array intArray;

intArray[4] = 10;

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

                                        // from Proxy to

                                        // TVStation

由于问题发生在隐式类型转换上,它很难解决。实际上,更好的设计应该是申明它的构造函数为explicit,以使得第一次的调用watchTV()的行为都编译失败。关于隐式类型转换的细节和explicit对此的影响见Item M5

l        评述

Proxy类可以完成一些其它方法很难甚至不可能实现的行为。多维数组是一个例子,左/右值的区分是第二个,限制隐式类型转换(见Item M5)是第三个。

同时,proxy类也有缺点。作为函数返回值,proxy对象是临时对象(见Item 19),它们必须被构造和析构。这不是免费的,虽然此付出能从具备了区分读写的能力上得到更多的补偿。Proxy对象的存在增加了软件的复杂度,因为额外增加的类使得事情更难设计、实现、理解和维护。

最后,从一个处理实际对象的类改换到处理proxy对象的类经常改变了类的语义,因为proxy对象通常表现出的行为与实际对象有些微妙的区别。有时,这使得在设计系统时无法选择使用proxy对象,但很多情况下很少有操作需要将proxy对象暴露给用户。例如,很少有用户取上面的二维数组例子中的Array1D对象的地址,也不怎么有可能将下标索引的对象(见Item M5)作参数传给一个期望其它类型的函数。在很多情况下,proxy对象可以完美替代实际对象。当它们可以工作时,通常也是没有其它方法可采用的情况。
阅读(1606) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~