Back to Item 29: Reference counting
Continue to Item 31: Making functions virtual with respect to more than one object

Item 30: Proxy classes.

Though your in-laws may be one-dimensional, the world, in general, is not. Unfortunately, C++ hasn't yet caught on to that fact. At least, there's little evidence for it in the language's support for arrays. You can create two-dimensional, three-dimensional — heck, you can create n-dimensional — arrays in FORTRAN, in BASIC, even in COBOL (okay, FORTRAN only allows up to seven dimensions, but let's not quibble), but can you do it in C++? Only sometimes, and even then only sort of.

This much is legal:

    int data[10][20];                          // 2D array: 10 by 20

The corresponding construct using variables as dimension sizes, however, is not:

    void processInput(int dim1, int dim2)
    {
      int data[dim1][dim2];                     // error! array dimensions
      ...                                       // must be known during
    }                                           // compilation

It's not even legal for a heap-based allocation:

    int *data =
      new int[dim1][dim2];                      // error!

Implementing Two-Dimensional Arrays

Multidimensional arrays are as useful in C++ as they are in any other language, so it's important to come up with a way to get decent support for them. The usual way is the standard one in C++: create a class to represent the objects we need but that are missing in the language proper. Hence we can define a class template for two-dimensional arrays:

    template<class T>
    class Array2D {
    public:
      Array2D(int dim1, int dim2);
      ...

    };

Now we can define the arrays we want:

    Array2D<int> data(10, 20);             // fine

    Array2D<float> *data =
      new Array2D<float>(10, 20);          // fine

    void processInput(int dim1, int dim2)
    {
      Array2D<int> data(dim1, dim2);       // fine
      ...
    }

Using these array objects, however, isn't quite as straightforward. In keeping with the grand syntactic tradition of both C and C++, we'd like to be able to use brackets to index into our arrays,

    cout << data[3][6];

but how do we declare the indexing operator in `Array2D` to let us do this?

Our first impulse might be to declare `operator[][]` functions, like this:

    template<class T>
    class Array2D {
    public:

      // declarations that won't compile
      T& operator[][](int index1, int index2);
      const T& operator[][](int index1, int index2) const;

      ...

    };

We'd quickly learn to rein in such impulses, however, because there is no such thing as `operator[][]`, and don't think your compilers will forget it. (For a complete list of operators, overloadable and otherwise, see Item 7.) We'll have to do something else.

If you can stomach the syntax, you might follow the lead of the many programming languages that use parentheses to index into arrays. To use parentheses, you just overload `operator()`:

    template<class T>
    class Array2D {
    public:

      // declarations that will compile
      T& operator()(int index1, int index2);
      const T& operator()(int index1, int index2) const;

      ...

    };

Clients then use arrays this way:

    cout << data(3, 6);

This is easy to implement and easy to generalize to as many dimensions as you like. The drawback is that your `Array2D` objects don't look like built-in arrays any more. In fact, the above access to element (3, 6) of `data` looks, on the face of it, like a function call.

If you reject the thought of your arrays looking like FORTRAN refugees, you might turn again to the notion of using brackets as the indexing operator. Although there is no such thing as `operator[][]`, it is nonetheless legal to write code that appears to use it:

    int data[10][20];

    ...

    cout << data[3][6];          // fine

What gives?

What gives is that the variable `data` is not really a two-dimensional array at all, it's a 10-element one-dimensional array. Each of those 10 elements is itself a 20-element array, so the expression `data[3][6]` really means `(data[3])[6]`, i.e., the seventh element of the array that is the fourth element of `data`. In short, the value yielded by the first application of the brackets is another array, so the second application of the brackets gets an element from that secondary array.

We can play the same game with our `Array2D` class by overloading `operator[]` to return an object of a new class, `Array1D`. We can then overload `operator[]` again in `Array1D` to return an element in our original two-dimensional array:

    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;
      ...

    };

The following then becomes legal:

    Array2D<float> data(10, 20);

    ...

    cout << data[3][6];          // fine

Here, `data[3]` yields an `Array1D` object and the `operator[]` invocation on that object yields the float in position (3, 6) of the original two-dimensional array.

Clients of the `Array2D` class need not be aware of the presence of the `Array1D` class. Objects of this latter class stand for one-dimensional array objects that, conceptually, do not exist for clients of `Array2D`. Such clients program as if they were using real, live, honest-to-Allah two-dimensional arrays. It is of no concern to `Array2D` clients that those objects must, in order to satisfy the vagaries of C++, be syntactically compatible with one-dimensional arrays of other one-dimensional arrays.

Each `Array1D` object stands for a one-dimensional array that is absent from the conceptual model used by clients of `Array2D`. Objects that stand for other objects are often called proxy objects, and the classes that give rise to proxy objects are often called proxy classes. In this example, `Array1D` is a proxy class. Its instances stand for one-dimensional arrays that, conceptually, do not exist. (The terminology for proxy objects and classes is far from universal; objects of such classes are also sometimes known as surrogates.)

Distinguishing Reads from Writes via `operator[]`

The use of proxies to implement classes whose instances act like multidimensional arrays is common, but proxy classes are more flexible than that. Item 5, for example, shows how proxy classes can be employed to prevent single-argument constructors from being used to perform unwanted type conversions. Of the varied uses of proxy classes, however, the most heralded is that of helping distinguish reads from writes through `operator[]`.

Consider a reference-counted string type that supports `operator[]`. Such a type is examined in detail in Item 29. If the concepts behind reference counting have slipped your mind, it would be a good idea to familiarize yourself with the material in that Item now.

A string type supporting `operator[]` allows clients to write code like this:

    String s1, s2;           // a string-like class; the
                             // use of proxies keeps this
                             // class from conforming to
                             // the standard string
    ...                      // interface

    cout << s1[5];           // read s1

    s2[5] = 'x';             // write s2

    s1[3] = s2[8];           // write s1, read s2

Note that `operator[]` can be called in two different contexts: to read a character or to write a character. Reads are known as rvalue usages; writes are known as lvalue usages. (The terms come from the field of compilers, where an lvalue goes on the left-hand side of an assignment and an rvalue goes on the right-hand side.) In general, using an object as an lvalue means using it such that it might be modified, and using it as an rvalue means using it such that it cannot be modified.

We'd like to distinguish between lvalue and rvalue usage of `operator[]` because, especially for reference-counted data structures, reads can be much less expensive to implement than writes. As Item 29 explains, writes of reference-counted objects may involve copying an entire data structure, but reads never require more than the simple returning of a value. Unfortunately, inside `operator[]`, there is no way to determine the context in which the function was called; it is not possible to distinguish lvalue usage from rvalue usage within `operator[]`.

"But wait," you say, "we don't need to. We can overload `operator[]` on the basis of its `const`ness, and that will allow us to distinguish reads from writes." In other words, you suggest we solve our problem this way:

    class String {
    public:
      const char& operator[](int index) const;       // for reads
      char& operator[](int index);                   // for writes
      ...

    };

Alas, this won't work. Compilers choose between `const` and non-`const` member functions by looking only at whether the object invoking a function is `const`. No consideration is given to the context in which a call is made. Hence:

    String s1, s2;

    ...

    cout << s1[5];                        // calls non-const operator[],
                                          // because s1 isn't const

    s2[5] = 'x';                          // also calls non-const
                                          // operator[]: s2 isn't const

    s1[3] = s2[8];                        // both calls are to non-const
                                          // operator[], because both s1
                                          // and s2 are non-const objects

Overloading `operator[]`, then, fails to distinguish reads from writes.

In Item 29, we resigned ourselves to this unsatisfactory state of affairs and made the conservative assumption that all calls to `operator[]` were for writes. This time we shall not give up so easily. It may be impossible to distinguish lvalue from rvalue usage inside `operator[]`, but we still want to do it. We will therefore find a way. What fun is life if you allow yourself to be limited by the possible?

Our approach is based on the fact that though it may be impossible to tell whether `operator[]` is being invoked in an lvalue or an rvalue context from within `operator[]`, we can still treat reads differently from writes if we delay our lvalue-versus-rvalue actions until we see how the result of `operator[]` is used. All we need is a way to postpone our decision on whether our object is being read or written until after `operator[]` has returned. (This is an example of lazy evaluation — see Item 17.)

A proxy class allows us to buy the time we need, because we can modify `operator[]` to return a proxy for a string character instead of a string character itself. We can then wait to see how the proxy is used. If it's read, we can belatedly treat the call to `operator[]` as a read. If it's written, we must treat the call to `operator[]` as a write.

We will see the code for this in a moment, but first it is important to understand the proxies we'll be using. There are only three things you can do with a proxy:

- Create it, i.e., specify which string character it stands for.
- Use it as the target of an assignment, in which case you are really making an assignment to the string character it stands for. When used in this way, a proxy represents an lvalue use of the string on which `operator[]` was invoked.
- Use it in any other way. When used like this, a proxy represents an rvalue use of the string on which `operator[]` was invoked.

Here are the class definitions for a reference-counted `String` class using a proxy class to distinguish between lvalue and rvalue usages of `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
      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;
    };

Other than the addition of the `CharProxy` class (which we'll examine below), the only difference between this `String` class and the final `String` class in Item 29 is that both `operator[]` functions now return `CharProxy` objects. Clients of `String` can generally ignore this, however, and program as if the `operator[]` functions returned characters (or references to characters — see Item 1) in the usual manner:

    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

What's interesting is not that this works. What's interesting is how it works.

Consider first this statement:

    cout << s1[5];

The expression `s1[5]` yields a `CharProxy` object. No output operator is defined for such objects, so your compilers labor to find an implicit type conversion they can apply to make the call to `operator<<` succeed (see Item 5). They find one: the implicit conversion from `CharProxy` to `char` declared in the `CharProxy` class. They automatically invoke this conversion operator, and the result is that the string character represented by the `CharProxy` is printed. This is representative of the `CharProxy`-to-`char` conversion that takes place for all `CharProxy` objects used as rvalues.

Lvalue usage is handled differently. Look again at

    s2[5] = 'x';

As before, the expression `s2[5]` yields a `CharProxy` object, but this time that object is the target of an assignment. Which assignment operator is invoked? The target of the assignment is a `CharProxy`, so the assignment operator that's called is in the `CharProxy` class. This is crucial, because inside a `CharProxy` assignment operator, we know that the `CharProxy` object being assigned to is being used as an lvalue. We therefore know that the string character for which the proxy stands is being used as an lvalue, and we must take whatever actions are necessary to implement lvalue access for that character.

Similarly, the statement

    s1[3] = s2[8];

calls the assignment operator for two `CharProxy` objects, and inside that operator we know the object on the left is being used as an lvalue and the object on the right as an rvalue.

"Yeah, yeah, yeah," you grumble, "show me." Okay. Here's the code for `String`'s `operator[]` functions:

    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);
    }

Each function just creates and returns a proxy for the requested character. No action is taken on the character itself: we defer such action until we know whether the access is for a read or a write.

Note that the `const` version of `operator[]` returns a `const` proxy. Because `CharProxy::operator=` isn't a `const` member function, such proxies can't be used as the target of assignments. Hence neither the proxy returned from the `const` version of `operator[]` nor the character for which it stands may be used as an lvalue. Conveniently enough, that's exactly the behavior we want for the `const` version of `operator[]`.

Note also the use of a `const_cast` (see Item 2) on `*this` when creating the `CharProxy` object that the `const` `operator[]` returns. That's necessary to satisfy the constraints of the `CharProxy` constructor, which accepts only a non-`const` `String`. Casts are usually worrisome, but in this case the `CharProxy` object returned by `operator[]` is itself `const`, so there is no risk the `String` containing the character to which the proxy refers will be modified.

Each proxy returned by an `operator[]` function remembers which string it pertains to and, within that string, the index of the character it represents:

    String::CharProxy::CharProxy(String& str, int index)
    : theString(str), charIndex(index) {}

Conversion of a proxy to an rvalue is straightforward — we just return a copy of the character represented by the proxy:

    String::CharProxy::operator char() const
    {
      return theString.value->data[charIndex];
    }

If you've forgotten the relationship among a `String` object, its `value` member, and the `data` member it points to, you can refresh your memory by turning to Item 29. Because this function returns a character by value, and because C++ limits the use of such by-value returns to rvalue contexts only, this conversion function can be used only in places where an rvalue is legal.

We thus turn to implementation of `CharProxy`'s assignment operators, which is where we must deal with the fact that a character represented by a proxy is being used as the target of an assignment, i.e., as an lvalue. We can implement `CharProxy`'s conventional assignment operator as follows:

    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;
    }

If you compare this with the implementation of the non-`const String`::`operator` in Item 29, you'll see that they are strikingly similar. This is to be expected. In Item 29, we pessimistically assumed that all invocations of the non-`const` `operator[]` were writes, so we treated them as such. Here, we moved the code implementing a write into `CharProxy`'s assignment operators, and that allows us to avoid paying for a write when the non-`const` `operator[]` is used only in an rvalue context. Note, by the way, that this function requires access to `String`'s private data member `value.` That's why `CharProxy` is declared a friend in the earlier class definition for `String`.

The second `CharProxy` assignment operator is almost identical:

    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;
    }

As an accomplished software engineer, you would, of course, banish the code duplication present in these two assignment operators to a private `CharProxy` member function that both would call. Aren't you the modular one?

Limitations

The use of a proxy class is a nice way to distinguish lvalue and rvalue usage of `operator[]`, 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.

Consider again the code fragment from Item 29 that motivated our decision to add a shareability flag to each `StringValue` object. If `String`::`operator[]` returns a `CharProxy` instead of a `char&`, that code will no longer compile:

    String s1 = "Hello";

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

The expression `s1[1]` returns a `CharProxy`, so the type of the expression on the right-hand side of the "`=`" is `CharProxy*`. There is no conversion from a `CharProxy*` to a `char*`, so the initialization of `p` 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.

To eliminate this difficulty, you'll need to overload the address-of operators for the `CharProxy` class:

    class String {
    public:

      class CharProxy {
      public:
        ...
        char * operator&();
        const char * operator&() const;
        ...
      };

      ...
    };

These functions are easy to implement. The `const` function just returns a pointer to a `const` version of the character represented by the proxy:

    const char * String::CharProxy::operator&() const
    {
      return &(theString.value->data[charIndex]);
    }

The non-`const` 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-`const` version of `String`::`operator[]` in Item 29, and the implementation is equally analogous:

    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]);
    }

Much of this code is common to other `CharProxy` member functions, so I know you'd encapsulate it in a private member function that all would call.

A second difference between `char`s and the `CharProxy`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 `operator[]`:

    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;
        ...
      };

      const Proxy operator[](int index) const;
      Proxy operator[](int index);
      ...
    };

Consider how these arrays might be used:

    Array<int> intArray;

    ...

    intArray[5] = 22;                    // fine

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

    ++intArray[5];                       // error!

As expected, use of `operator[]` as the target of a simple assignment succeeds, but use of `operator[]` on the left-hand side of a call to `operator+=` or `operator++` fails. That's because `operator[]` returns a proxy, and there is no `operator+=` or `operator++` for `Proxy` objects. A similar situation exists for other operators that require lvalues, including `operator*=`, `operator<<=`, `operator--`, etc. If you want these operators to work with `operator[]` functions that return proxies, you must define each of these functions for the `Array<T>`::`Proxy` 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.

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 `Rational` and then use the `Array` template we just saw:

    class Rational {
    public:
      Rational(int numerator = 0, int denominator = 1);
      int numerator() const;
      int denominator() const;
      ...
    };

    Array<Rational> array;

This is how we'd expect to be able to use such arrays, but, alas, we'd be disappointed:

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

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

By now the difficulty is predictable; `operator[]` returns a proxy for a rational number, not an actual `Rational` object. But the `numerator` and `denominator` member functions exist only for `Rational`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.

Yet another situation in which proxies fail to replace real objects is when being passed to functions that take references to non-`const` objects:

    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[]` returns a `CharProxy`, but `swap` demands that its arguments be of type `char&`. A `CharProxy` may be implicitly converted into a `char`, but there is no conversion function to a `char&`. Furthermore, the `char` to which it may be converted can't be bound to `swap`'s `char&` parameters, because that `char` is a temporary object (it's `operator` `char`'s return value) and, as Item 19 explains, there are good reasons for refusing to bind temporary objects to non-`const` reference parameters.

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 `CharProxy` can be converted into the `char` it stands for by calling `operator` `char`. As Item 5 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 `TVStation` class and a function, `watchTV`:

    class TVStation {
    public:
      TVStation(int channel);
      ...

    };

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

Thanks to implicit type conversion from `int` to `TVStation` (see Item 5), we could then do this:

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

Using the template for reference-counted arrays that use proxy classes to distinguish lvalue and rvalue invocations of `operator[]`, however, we could not do this:

    Array<int> intArray;

    intArray[4] = 10;

    watchTV(intArray[4], 2.5);              // error! no conversion
                                            // from Proxy<int> to
                                            // TVStation

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 `TVStation` class would declare its constructor `explicit`, in which case even the first call to `watchTV` would fail to compile. For all the details on implicit type conversions and how `explicit` affects them, see Item 5.

Evaluation

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 Item 5) is a third.

At the same time, proxy classes have disadvantages. As function return values, proxy objects are temporaries (see Item 19), 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.

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 of an `Array1D` object in the two-dimensional array example we saw at the beginning of this Item, and there isn't much chance that an `ArrayIndex` object (see Item 5) 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.

Back to Item 29: Reference counting
Continue to Item 31: Making functions virtual with respect to more than one object