Operators as Methods

C++ operators are functions, and like all functions they can be members of a class. In Chapter 6, “Overloading Functions and Operators” you saw how operators can be redefined for user-defined types. There are some operators that can be defined only as class members, however.

The [] Operator

We previously overloaded operator+= for Point; here is how it looks as a method of Point:

struct Point {
  ...
   Point& operator+= (const Point& p) {
     x += p.x;  y += p.y;
     return *this;
   }
 };
;> Point p(20,30), offs(10,10);
;> p
							+= offs;
(Point) p = Point { }
;> p.x; p.y;
(int) 30
(int) 40

The += operator is, in fact, just another name for the translate operator—that is, it moves the point along. The non-member version has two arguments, which here is implicit as the this pointer, but both versions are used the same way. In fact, you must either define operator+= as a member or a non-member.

Some operators can be defined only as members. For instance, the operator = can be only a method because an object must have full control of assignment (and any other copy operations). Allowing client code could redefine what assignment means and would lead to confusion.

The operator [] is interesting. Array access is considered an operator, and you can redefine it. Here is a very simple Array class; it isn't resizable, but (unlike regular arrays) it is range-checked:


const int N = 100;

class Array {
   int m_arr[N];
 public:
   int& operator[] (int i)
   {
     if (i < 0 || i > N) {
       cerr << i << ": array index out of bounds
";
       throw 0;
     }
     return m_arr[i];
   }
  };
;> Array a;
;> a[10] = 2;
(int&) 2
;> a[10];
(int&) 2
;> a[1021];
1021: array index out of bounds
uncaught exception: 11 array.h

Note that a[index] can be used on both the right- and the left-hand sides of an assignment. Array::operator[] returns a reference to an integer; assigning to this reference causes the value to be updated.

Using Methods versus Functions

C++ can be confusing because there is often more than one way to do a particular thing. For instance, we can define operator+= as either a member or a nonmember operator. In the case of operator+=, the member form is both simpler and more appropriate than the nonmember form because += is an assignment operator.

In the past, people overused methods because there was no other way to create a separate namespace. Explicit namespaces now offer an alternative to defining everything as a method. Methods should represent the basic operations that need raw access to the data. In particular, you shouldn't add very specialized operations that are useful only in some applications. Instead, you should be able to define functions (in some namespace) to do the job. (Alternatively, as will be discussed in Chapter 8, “Inheritance and Virtual Methods,” you can use inheritance, which is a better way to specialize a class for a particular job. )

A useful way of looking at classes is the idea of abstract data types (ADTs). An ADT is defined by a set of operations (called the interface), rather than by representation. For instance, in mathematics integers are defined by addition, subtraction, multiplication, and division. Some operations, such as the remainder (modulo) operation, can be defined in terms of these functions, but the remainder operation is generally useful and so it's made into an operator. Counting the number of digits in a decimal representation, on the other hand, is not a basic operation and would be made a function.

For instance, consider the idea of a stack which you met in Chapter 3, in the section “Stacks and Queues.” Considered as an ADT, a stack is defined by the operations push(), pop(), and depth(); it's also convenient to have empty(), which is the same as depth()==0. Here is an implementation of a Stack class, using a list:


class Stack {
private:
   list<int> m_list;
public:
   void push(int i)    // push a value onto the stack
   {
    m_list.push_back(i);
   }
   int pop ()         //  pop a value from the stack
   {
     int val = m_list.back();
     m_list.pop_back();
     return val;
   }
   int depth() const  //  how deep is the stack?
   {
     return m_list.size();
   }
   bool empty() const //  is the stack empty?
   {
     return depth()==0;
   }
};
;> Stack s;
;> s.push(20);
;> s.push(30);
;> s.depth();
(int) 2
;> s.pop();
(int) 30
;> s.depth();
(int) 1
;> s.pop();
(int) 20

It is interesting to compare this to the version of this example in Chapter 4, “Programs and Libraries,” which uses an array. That previous version defined a stack within a namespace. The advantage of using a class is that you can have a number of stacks. The advantage of using a list is that it can grow as the stack grows, without using more memory than necessary; the disadvantage is that it may be too slow for mission-critical tasks, such as the UnderC stack machine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.123.155