📄 mi30.htm
字号:
The second <CODE>CharProxy</CODE> assignment operator is almost identical:
<SCRIPT>create_link(58);</SCRIPT>
</P><A NAME="59001"></A>
<UL><PRE>String::CharProxy& String::CharProxy::operator=(char c)
{
if (theString.value->isShared()) {
theString.value = new StringValue(theString.value->data);
}
<A NAME="59002"></A>
theString.value->data[charIndex] = c;
<A NAME="59003"></A>
return *this;
}
</PRE>
</UL>
<A NAME="dingp59"></A><P><A NAME="58999"></A>
As an accomplished software engineer, you would, of course, banish the code duplication present in these two assignment operators to a private <CODE>CharProxy</CODE> member function that both would call. Aren't you the modular one?<SCRIPT>create_link(59);</SCRIPT>
</P>
<A NAME="dingp60"></A><P><font ID="mhtitle">Limitations</font><SCRIPT>create_link(60);</SCRIPT>
</P>
<A NAME="dingp61"></A><P><A NAME="25838"></A>
The use of a proxy class is a nice way to distinguish lvalue and rvalue usage of <CODE>operator[]</CODE>, but the technique is not without its drawbacks. We'd like proxy objects to seamlessly replace the objects they stand for, but this ideal is difficult to achieve. That's because objects are used as lvalues in contexts other than just assignment, and using proxies in such contexts usually yields behavior different from using real objects.
<SCRIPT>create_link(61);</SCRIPT>
</P>
<A NAME="dingp62"></A><P><A NAME="57150"></A>
Consider again the code fragment from <a href="./MI29_FR.HTM#6073" TARGET="_top">Item 29</A> that motivated our decision to add a shareability flag to each <CODE>StringValue</CODE> object. If <CODE>String</CODE>::<CODE>operator[]</CODE> returns a <CODE>CharProxy</CODE> instead of a <CODE>char&</CODE>, that code will no longer compile:<SCRIPT>create_link(62);</SCRIPT>
</P>
<A NAME="57159"></A>
<UL><PRE>String s1 = "Hello";
</PRE>
</UL><A NAME="57160"></A>
<UL><PRE>char *p = &s1[1]; // error!
</PRE>
</UL>
<A NAME="dingp63"></A><P><A NAME="57221"></A>
The expression <CODE>s1[1]</CODE> returns a <CODE>CharProxy</CODE>, so the type of the expression on the right-hand side of the "<CODE>=</CODE>" is <CODE>CharProxy*</CODE>. There is no conversion from a <CODE>CharProxy*</CODE> to a <CODE>char*</CODE>, so the initialization of <CODE>p</CODE> fails to compile. In general, taking the address of a proxy yields a different type of pointer than does taking the address of a real object.
<SCRIPT>create_link(63);</SCRIPT>
</P>
<A NAME="dingp64"></A><P><A NAME="82410"></A>
To eliminate this difficulty, you'll need to overload the address-of operators for the <CODE>CharProxy</CODE> class:<SCRIPT>create_link(64);</SCRIPT>
</P><A NAME="80818"></A>
<UL><PRE><A NAME="p224"></A>class String {
public:
</PRE>
</UL><A NAME="80845"></A>
<UL><PRE> class CharProxy {
public:
...
char * operator&();
const char * operator&() const;
...
};
</PRE>
</UL><A NAME="80848"></A>
<UL><PRE> ...
};
</PRE>
</UL>
<A NAME="dingp65"></A><P><A NAME="80843"></A>
These functions are easy to implement. The <CODE>const</CODE> function just returns a pointer to a <CODE>const</CODE> version of the character represented by the proxy:
<SCRIPT>create_link(65);</SCRIPT>
</P><A NAME="80885"></A>
<UL><PRE>const char * String::CharProxy::operator&() const
{
return &(theString.value->data[charIndex]);
}
</PRE>
</UL><P><A NAME="80859"></A>
<A NAME="dingp66"></A>The non-<CODE>const</CODE> function is a bit more work, because it returns a pointer to a character that may be modified. This is analogous to the behavior of the non-<CODE>const</CODE> version of <CODE>String</CODE>::<CODE>operator[]</CODE> in <a href="./MI29_FR.HTM#6073" TARGET="_top">Item 29</A>, and the implementation is equally analogous:<SCRIPT>create_link(66);</SCRIPT>
</P><A NAME="80888"></A>
<UL><PRE>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);
}
<A NAME="80897"></A>
// 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();
<A NAME="80860"></A>
return &(theString.value->data[charIndex]);
}
</PRE>
</UL>
<A NAME="dingp67"></A><P><A NAME="80857"></A>
Much of this code is common to other <CODE>CharProxy</CODE> member functions, so I know you'd encapsulate it in a private member function that all would call.
<SCRIPT>create_link(67);</SCRIPT>
</P>
<A NAME="dingp68"></A><P><A NAME="58000"></A>
A second difference between <CODE>char</CODE>s and the <CODE>CharProxy</CODE>s that stand for them becomes apparent if we have a template for reference-counted arrays that use proxy classes to distinguish lvalue and rvalue invocations of <CODE>operator[]</CODE>:
<SCRIPT>create_link(68);</SCRIPT>
</P><A NAME="57852"></A>
<UL><PRE><A NAME="p225"></A>
template<class T> // reference-counted array
class Array { // using proxies
public:
class Proxy {
public:
Proxy(Array<T>& array, int index);
Proxy& operator=(const T& rhs);
operator T() const;
...
};
<A NAME="25856"></A>
const Proxy operator[](int index) const;
Proxy operator[](int index);
...
};
</PRE>
</UL>
<A NAME="dingp69"></A><P><A NAME="25863"></A>
Consider how these arrays might be used:
<SCRIPT>create_link(69);</SCRIPT>
</P><A NAME="25857"></A>
<UL><PRE>Array<int> intArray;
<A NAME="25858"></A>
...
<A NAME="25859"></A>
intArray[5] = 22; // fine
<A NAME="25860"></A>
intArray[5] += 5; // error!
<A NAME="25861"></A>
++intArray[5]; // error!
</PRE>
</UL>
<A NAME="dingp70"></A><P><A NAME="58169"></A>
As expected, use of <CODE>operator[]</CODE> as the target of a simple assignment succeeds, but use of <CODE>operator[]</CODE> on the left-hand side of a call to <CODE>operator+=</CODE> or <CODE>operator++</CODE> fails. That's because <CODE>operator[]</CODE> returns a proxy, and there is no <CODE>operator+=</CODE> or <CODE>operator++</CODE> for <CODE>Proxy</CODE> objects. A similar situation exists for other operators that require lvalues, including <CODE>operator*=</CODE>, <CODE>operator<<=</CODE>, <CODE>operator--</CODE>, etc. If you want these operators to work with <CODE>operator[]</CODE> functions that return proxies, you must define each of these functions for the <CODE>Array<T></CODE>::<CODE>Proxy</CODE> class. That's a lot of work, and you probably don't want to do it. Unfortunately, you either do the work or you do without. Them's the breaks.<SCRIPT>create_link(70);</SCRIPT>
</P>
<A NAME="dingp71"></A><P><A NAME="25880"></A>
A related problem has to do with invoking member functions on real objects through proxies. To be blunt about it, you can't. For example, suppose we'd like to work with reference-counted arrays of rational numbers. We could define a class <CODE>Rational</CODE> and then use the <CODE>Array</CODE> template we just saw:<SCRIPT>create_link(71);</SCRIPT>
</P><A NAME="57862"></A>
<UL><PRE>class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
int numerator() const;
int denominator() const;
...
};
<A NAME="25902"></A>
Array<Rational> array;
</PRE>
</UL>
<A NAME="dingp72"></A><P><A NAME="25903"></A>
<A NAME="p226"></A>This is how we'd expect to be able to use such arrays, but, alas, we'd be disappointed:
<SCRIPT>create_link(72);</SCRIPT>
</P><A NAME="25919"></A>
<UL><PRE>cout << array[4].numerator(); // error!
<A NAME="25923"></A>
int denom = array[22].denominator(); // error!
</PRE>
</UL>
<A NAME="dingp73"></A><P><A NAME="25927"></A>
By now the difficulty is predictable; <CODE>operator[]</CODE> returns a proxy for a rational number, not an actual <CODE>Rational</CODE> object. But the <CODE>numerator</CODE> and <CODE>denominator</CODE> member functions exist only for <CODE>Rational</CODE>s, not their proxies. Hence the complaints by your compilers. To make proxies behave like the objects they stand for, you must overload each function applicable to the real objects so it applies to proxies, too.
<SCRIPT>create_link(73);</SCRIPT>
</P>
<A NAME="dingp74"></A><P><A NAME="58072"></A>
Yet another situation in which proxies fail to replace real objects is when being passed to functions that take references to non-<CODE>const</CODE> objects:
<SCRIPT>create_link(74);</SCRIPT>
</P><A NAME="58174"></A>
<UL><PRE>
void swap(char& a, char& b); // swaps the value of a and b
<A NAME="58076"></A>
String s = "+C+"; // oops, should be "C++"
<A NAME="58175"></A>
swap(s[0], s[1]); // this should fix the
// problem, but it won't
// compile
</PRE>
</UL>
<A NAME="dingp75"></A><P><A NAME="58183"></A>
<CODE>String</CODE>::<CODE>operator[]</CODE> returns a <CODE>CharProxy</CODE>, but <CODE>swap</CODE> demands that its arguments be of type <CODE>char&</CODE>. A <CODE>CharProxy</CODE> may be implicitly converted into a <CODE>char</CODE>, but there is no conversion function to a <CODE>char&</CODE>. Furthermore, the <CODE>char</CODE> to which it may be converted can't be bound to <CODE>swap</CODE>'s <CODE>char&</CODE> parameters, because that <CODE>char</CODE> is a temporary object (it's <CODE>operator</CODE> <CODE>char</CODE>'s return value) and, as <a href="./MI19_FR.HTM#41177" TARGET="_top">Item 19</A> explains, there are good reasons for refusing to bind temporary objects to non-<CODE>const</CODE> reference parameters.
<SCRIPT>create_link(75);</SCRIPT>
</P>
<A NAME="dingp76"></A><P><A NAME="58971"></A>
A final way in which proxies fail to seamlessly replace real objects has to do with implicit type conversions. When a proxy object is implicitly converted into the real object it stands for, a user-defined conversion function is invoked. For instance, a <CODE>CharProxy</CODE> can be converted into the <CODE>char</CODE> it stands for by calling <CODE>operator</CODE> <CODE>char</CODE>. As <a href="./MI5_FR.HTM#5970" TARGET="_top">Item 5</A> explains, compilers may use only one user-defined conversion function when converting a parameter at a call site into the type needed by the corresponding function parameter. As a result, it is possible for function calls that succeed when passed real objects to fail when passed proxies. For example, suppose we have a <CODE>TVStation</CODE> class and a function, <CODE>watchTV</CODE>:
<SCRIPT>create_link(76);</SCRIPT>
</P><A NAME="58774"></A>
<UL><PRE>class TVStation {
public:
TVStation(int channel);
...
<A NAME="58775"></A>
};
<A NAME="58776"></A>
<A NAME="p227"></A>void watchTV(const TVStation& station, float hoursToWatch);
</PRE>
</UL>
<A NAME="dingp77"></A><P><A NAME="58777"></A>
Thanks to implicit type conversion from <CODE>int</CODE> to <CODE>TVStation</CODE> (see<a href="./MI5_FR.HTM#5970" TARGET="_top"> Item 5</A>), we could then do this:
<SCRIPT>create_link(77);</SCRIPT>
</P><A NAME="58778"></A>
<UL><PRE>
watchTV(10, 2.5); // watch channel 10 for
// 2.5 hours
</PRE>
</UL>
<A NAME="dingp78"></A><P><A NAME="58790"></A>
Using the template for reference-counted arrays that use proxy classes to distinguish lvalue and rvalue invocations of <CODE>operator[]</CODE>, however, we could not do this:
<SCRIPT>create_link(78);</SCRIPT>
</P><A NAME="58748"></A>
<UL><PRE>
Array<int> intArray;
<A NAME="58749"></A>
intArray[4] = 10;
<A NAME="58793"></A>
watchTV(intArray[4], 2.5); // error! no conversion
// from Proxy<int> to
// TVStation
</PRE>
</UL>
<A NAME="dingp79"></A><P><A NAME="58820"></A>
Given the problems that accompany implicit type conversions, it's hard to get too choked up about this. In fact, a better design for the <CODE>TVStation</CODE> class would declare its constructor <CODE>explicit</CODE>, in which case even the first call to <CODE>watchTV</CODE> would fail to compile. For all the details on implicit type conversions and how <CODE>explicit</CODE> affects them, see <a href="./MI5_FR.HTM#5970" TARGET="_top">Item 5</A>.
<SCRIPT>create_link(79);</SCRIPT>
</P>
<P><font ID="mhtitle">Evaluation</font><SCRIPT>create_link(80);</SCRIPT>
</P>
<A NAME="dingp81"></A><P><A NAME="25953"></A>
Proxy classes allow you to achieve some types of behavior that are otherwise difficult or impossible to implement. Multidimensional arrays are one example, lvalue/rvalue differentiation is a second, suppression of implicit conversions (see<a href="./MI5_FR.HTM#5970" TARGET="_top"> Item 5</A>) is a third.
<SCRIPT>create_link(81);</SCRIPT>
</P>
<A NAME="dingp82"></A><P><A NAME="59028"></A>
At the same time, proxy classes have disadvantages. As function return values, proxy objects are temporaries (see<a href="./MI19_FR.HTM#41177" TARGET="_top"> Item 19</A>), so they must be created and destroyed. That's not free, though the cost may be more than recouped through their ability to distinguish write operations from read operations. The very existence of proxy classes increases the complexity of software systems that employ them, because additional classes make things harder to design, implement, understand, and maintain, not easier.
<SCRIPT>create_link(82);</SCRIPT>
</P>
<A NAME="dingp83"></A><P><A NAME="59080"></A>
Finally, shifting from a class that works with real objects to a class that works with proxies often changes the semantics of the class, because proxy objects usually exhibit behavior that is subtly different from that of the real objects they represent. Sometimes this makes proxies a poor choice when designing a system, but in many cases there is little need for the operations that would make the presence of proxies apparent to clients. For instance, few clients will want to take the address <A NAME="p228"></A>of an <CODE>Array1D</CODE> object in the two-dimensional array example we saw at the beginning of this Item, and there isn't much chance that an <CODE>ArrayIndex</CODE> object (see <a href="./MI5_FR.HTM#5970" TARGET="_top">Item 5</A>) would be passed to a function expecting a different type. In many cases, proxies can stand in for real objects perfectly acceptably. When they can, it is often the case that nothing else will do.
<SCRIPT>create_link(83);</SCRIPT>
</P>
<DIV ALIGN="CENTER"><FONT SIZE="-1">Back to <A HREF="./MI29_FR.HTM" TARGET="_top">Item 29: Reference counting</A> <BR> Continue to <A HREF="./MI31_FR.HTM" TARGET="_top">Item 31: Making functions virtual with respect to more than one object</A></FONT></DIV>
</BODY>
</HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -