Overloading Operators

Operators such as + and * in C++ can be considered to be functions taking two arguments (these are often called binary operators). Because they are functions, they can be overloaded. This section shows why and how you can overload operators.

Adding Two Points Together

Points in the two-dimensional space of a graph can be added together by adding their coordinates. They can also be divided by a scalar (that is, a single number). For example, you calculate the average of two points by adding them together and dividing by 2. The result is a point that is midway between them in space. This shows how the average of two Point structures can be calculated, by defining add() and div() functions:

Point add(Point p1, Point p2) {
   Point p;
   p.x = p1.x + p2.x;
   p.y = p1.y + p2.y;
   return p;
 }
Point div(Point p, int val) {
   p.x /= val;
   p.y /= val;
   return p;
 }
;> Point p1 = make_point(1,4), p2 = make_point(5,2);
;> Point p = div(add(p1,p2),2);
;> show(cout,p);  // print out the average of (1,4) and (5,2)
 (3,3)

With function overloading, you don't have to worry about naming these functions add_point(), show_point(), and so on. But it would be nice to use the usual arithmetic notation, not the function notation. That is, people are more used to writing (p1+p2)/2 than writing div(add(p1,p2),2). Functional expressions like this are often more difficult to read, and bracket-counting is necessary. The next section shows how to use mathematic notation in this case.

Operators as Functions

C++ operators are functions that can be redefined. Here are overloaded versions of the addition and division operators that work with Point types:


Point operator+(Point p1, Point p2) {
   Point p;
   p.x = p1.x + p2.x;
   p.y = p1.y + p2.y;
   return p;
 }
Point operator/(Point p, int val) {
  return div(p,val);
}
;> Point p1 = make_point(1,2), p2 = make_point(5,2);
;> Point p = (p1 + p2)/2;
;> show(cout,p);
 (3,3);>

The definition of operator+() is precisely the same as a normal function definition, except that you use the keyword operator followed by the symbol (for example, operator/). It is now possible to write point arithmetic precisely like normal arithmetic! Java programmers call this “semantic sugar,” but people are very used to normal arithmetical notation, and it's easier to get right and (just as important) easier to read than functional notation. Just because sugar makes some programs (and people) fat doesn't mean we have to abandon sugar altogether.

Careful: The fact that you have redefined + and / for points doesn't mean that += and /= are automatically redefined. C++ does not check that your definitions for those operators are consistent: It's up to you to ensure that p=p+q is the same as p+=q. operator+= is a good idea for big structures because it can be defined entirely with references. This operator is passed two reference arguments, the first of which is not const because it will be modified by the operation. The following code defines an operator+= that returns a reference to the modified point, and no copying is involved. Bear this in mind if fast and compact code is essential (that is, you have to watch your sugar).

Point& operator+= (Point& p1, const Point& p2) {
  p1.x += p2.x;
  p1.y += p2.y;
  return p1;
}

There are some things you can't do with operator overloading, and there are some things you shouldn't do. You cannot redefine any C++ operator for the usual simple types, such as double and int. It would be very dangerous for people to be able to modify basic arithmetic. (Sometimes you need unlimited-precision arithmetic, but in those cases, you can define special structures and overload the arithmetic operators appropriately.) Some operators such as the member selection, or dot (.), operator can't be overloaded at all. You can't define your own operators, such as $, and you can't change the precedence of the operators (for example, operator* will always have a higher precedence than operator+). These rules prevent people from completely redefining the language to suit their own (possibly weird) tastes.

Obviously, you would not define - to mean addition, but the overloaded operator must still be meaningful. Say you had a Club struct and defined += so that it took a Person operand and had the effect of adding Person to Club. This would be silly because this addition here has nothing to do with mathematical addition. (+= does also mean string concatenation, but that is a well-established notation.) Naming functions requires thought, and deciding which operator to use requires particular consideration. Operator overloading is very powerful, but it can be abused.

Overloading << and >>

The insertion operator (<<) can be redefined similarly to other operators. In fact, it is more commonly used in its overloaded form than in its basic form. The original meaning of operator<< is to do bit shifting. Given the binary representation of an integer, << shifts the bits to the left by the specified number of places. It is equivalent to multiplication by powers of 2. So, for example, 10 << 1 would be 20.

Consider the business of building up a list ls of integers. Typing ls.push_back(i) can be tedious. It is convenient to redefine operator<< to mean “add to list”; I've simplified the definition of this operator by using the typedef symbol LI to mean list<int>:


;> typedef list<int> LI;
;> LI ls;
;> LI& operator<< (LI& ls, int val) {
;1}  ls.push_back(val);
;1}  return ls;
;1}  }
;> ls << 1 << 2;
(list<int>&) list<int> { }
;> ls.front(); ls.back();
(int&) 1
(int&) 2

operator<< is usually defined to return a reference to the first argument. This makes the preceding trick possible. ls << 1 << 2 can be written as (ls << 1) << 2. ls<<1 is evaluated first, and the result is ls, and so finally ls<<2 is evaluated. It is exactly the same as typing ls << 1; ls << 2;.

By far the most common use of overloading << is for output. This way of chaining << and values is how C++ stream output is done. You can overload operator<< for Point arguments like this:

ostream& operator<<(ostream& os, Point p) {
    os << '(' << p.x << ',' << p.y << ')';
    return os;
 }
;> cout << "the point is " << p1 << endl;
the point is (1,4)

The resulting operator can be used in any output expression. It operates on a general ostream, so you can use it for file output or writing to strings. This is exactly how the <iostream> I/O stream libraries are built up: by overloading operator<< for the basic types. (Look at the end of the <iostream> header in your UnderC include directory to see this in action.) A powerful feature of this library is how easy it is to extend it for user-defined types.

Similarly to overloading the << operator, you can overload the extraction (that is “get from”) operator for Point arguments:

istream& operator>>(istream& is, Point& p) {
    is >> p.x >> p.y;
    return is;
 }
;> Point p;
;> cin >> p;
							100 200
;> cout << p << endl;
(100,200)

Note that you must pass a reference to Point so that it will be modified by the input operation. Be careful about this, because forgetting the & leaves you with an operator that changes a local variable and nothing else. Again, operator>> operates on a general input stream.

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

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