Section 14.7. Increment and Decrement Operators


14.7. Increment and Decrement Operators

The increment (++) and decrement (--) operators are most often implemented for classes, such as iterators, that provide pointer like behavior on the elements of a sequence. As an example, we might define a class that points to an array and provides checked access to elements in that array. Ideally, our checked-pointer class could be used on arrays of any type, which we'll learn how to do in Chapter 16 when we cover class templates. For now, our class will handle arrays of ints:

      /*       * smart pointer: Checks access to elements throws an out_of_range       *                exception if attempt to access a nonexistent element       * users allocate and free the array       */      class CheckedPtr {      public:          // no default constructor; CheckedPtrs must be bound to an object          CheckedPtr(int *b, int *e): beg(b), end(e), curr(b) { }          // dereference and increment operations      private:          int* beg;   // pointer to beginning of the array          int* end;   // one past the end of the array          int* curr;  // current position within the array      }; 

Like ScreenPtr, this class has no default constructor. We must supply pointers to an array when we create a CheckedPtr. A CheckedPtr has three data members: beg, which points to the first element in the array; end, which points one past the end of the array; and curr, which points to the array element to which this CheckedPtr object currently refers.

The constructor takes two pointers: one pointing to the beginning of the array and the other one past the end of the array. The constructor initializes beg and end from these pointers and initializes curr to point to the first element.

Defining the Increment/Decrement Operators

There is no language requirement that the increment or decrement operators be made members of the class. However, because these operators change the state of the object on which they operate, our preference is to make them members.



Before we can define the overloaded increment and decrement operators for CheckedPtr, we must think about one more thing. For the built-in types, there are both prefix and postfix versions of the increment and decrement operators. Not surprisingly, we can define both the prefix and postfix instances of these operators for our own classes as well. We'll look at the prefix versions first and then implement the postfix ones.

Defining Prefix Increment/Decrement Operators

The declarations for the prefix operators look as one might expect:

      class CheckedPtr {      public:          CheckedPtr& operator++();        // prefix operators          CheckedPtr& operator--();          // other members as before       }; 

For consistency with the built-in operators, the prefix operations should return a reference to the incremented or decremented object.



This increment operator ensures that the user can't increment past the end of the array by checking curr against end. We throw an out_of_range exception if the increment would move curr past end; otherwise, we increment curr and return a reference to the object:

      // prefix: return reference to incremented/decremented object      CheckedPtr& CheckedPtr::operator++()      {          if (curr == end)              throw out_of_range                    ("increment past the end of CheckedPtr");          ++curr;                // advance current state          return *this;      } 

The decrement operator behaves similarly, except that it decrements curr and checks whether the decrement would move curr past beg:

      CheckedPtr& CheckedPtr::operator--()      {          if (curr == beg)              throw out_of_range                ("decrement past the beginning of CheckedPtr");          --curr;              // move current state back one element          return *this;      } 

Differentiating Prefix and Postfix Operators

There is one problem with defining both the prefix and postfix operators: They each take the same number and type of parameters. Normal overloading cannot distinguish between whether the operator we're defining is the prefix version or the postfix.

To solve this problem, the postfix operator functions take an extra (unused) parameter of type int. When we use the postfix operator, the compiler supplies 0 as the argument for this parameter. Although our postfix function could use this extra parameter, it usually should not. That parameter is not needed for the work normally performed by a postfix operator. Its sole purpose is to distinguish the definition of the postfix function from the prefix version.

Defining the Postfix Operators

We can now add the postfix operators to CheckedPtr:

      class CheckedPtr {      public:          // increment and decrement          CheckedPtr operator++(int);       // postfix operators          CheckedPtr operator--(int);          // other members as before      }; 

For consistency with the built-in operators, the postfix operators should return the old (unincremented or undecremented) value. That value is returned as a value, not a reference.



The postfix operators might be implemented as follows:

      // postfix: increment/decrement object but return unchanged value      CheckedPtr CheckedPtr::operator++(int)      {          // no check needed here, the call to prefix increment will do the check          CheckedPtr ret(*this);        // save current value          ++*this;                      // advance one element, checking the increment          return ret;                   // return saved state      }      CheckedPtr CheckedPtr::operator--(int)      {          // no check needed here, the call to prefix decrement will do the check          CheckedPtr ret(*this);  // save current value          --*this;                // move backward one element and check          return ret;             // return saved state       } 

The postfix versions are a bit more involved than the prefix operators. They have to remember the current state of the object before incrementing the object. These operators define a local CheckedPtr, which is initialized as a copy of *this that is, ret is a copy of the current state of this object.

Having kept a copy of the current state, the operator calls its own prefix operator to do the increment or decrement, respectively:

      ++*this 

calls the CheckedPtr prefix increment operator on this object. That operator checks that the increment is safe and either increments curr or throws an exception. Assuming no exception was thrown, the postfix function completes by returning the stored copy in ret. Thus, after the return, the object itself has been advanced, but the value returned reflects the original, unincremented value.

Because these operators are implemented by calling the prefix versions, there is no need to check that the curr is in range. That check, and the tHRow if necessary, is done inside the corresponding prefix operator.

The int parameter is not used, so we do not give it a name.



Calling the Postfix Operators Explicitly

As we saw on page 509, we can explicitly call an overloaded operator rather than using it as an operator in an expression. If we want to call the postfix version using a function call, then we must pass a value for the integer argument:

      CheckedPtr parr(ia, ia + size);        // iapoints to an array of ints      parr.operator++(0);                    // call postfix operator++      parr.operator++();                     // call prefix operator++ 

The value passed usually is ignored but is necessary to alert the compiler that the postfix version is desired.

Ordinarily it is best to define both the prefix and postfix versions. Classes that define only the prefix version or only the postfix version will surprise users who are accustomed to being able to use either form.



Exercises Section 14.7

Exercise 14.23:

The class CheckedPtr represents a pointer that points to an array of ints. Define an overloaded subscript and dereference for this class. Have the operator ensure that the CheckedPtr is valid: It should not be possible to dereference or index one past the end of the array.

Exercise 14.24:

Should the dereference or subscript operators defined in the previous exercise also check whether an attempt is being made to dereference or index one before the beginning of the array? If not, why not? If so, why?

Exercise 14.25:

To behave like a pointer to an array, our CheckedPtr class should implement the equality and relational operators to determine whether two CheckedPtrs are equal, or whether one is less-than another, and so on. Add these operations to the CheckedPtr class.

Exercise 14.26:

Define addition and subtraction for ScreenPtr so that these operators implement pointer arithmetic (Section 4.2.4, p. 123).

Exercise 14.27:

Discuss the pros and cons of allowing an empty array argument to the CheckedPtr constructor.

Exercise 14.28:

We did not define a const version of the increment and decrement operators. Why?

Exercise 14.29:

We also didn't implement arrow. Why?

Exercise 14.30:

Define a version of CheckedPtr that holds an array of Screens. Implement the overloaded increment, decrement, dereference, and arrow operators for this class.




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net