USING OVERLOADED OPERATORS ■ 419 ᮀ Calling Operator Functions The following expressions are valid for the operators in the Euro class. Example: Euro wholesale(15,30), retail, profit(7,50), discount(1,75); retail = wholesale + profit; // Call: wholesale.operator+( profit) retail -= discount; // Call: retail.operator-=( discount) retail += Euro( 1.49); // Call: retail.operator+=( Euro(1.49)) These expressions contain only Euro type objects, for which operator functions have been defined. However, you can also add or subtract int or double types. This is made possible by the Euro constructors, which create Euro objects from int or double types. This allows a function that expects a Euro value as argument to process int or double values. As the program opposite shows, the statement Example: retail += 9.49; is valid. The compiler attempts to locate an operator function that is defined for both the Euro object and the double type for +=. Since there is no operator function with these characteristics, the compiler converts the double value to Euro and calls the existing operator function for euros. ᮀ Symmetry of Operands The available constructors also allow you to call the operator functions of + and – with int or double type arguments. Example: retail = wholesale + 10; // ok wholesale = retail - 7.99; // ok The first statement is equivalent to retail = wholesale.operator+( Euro(10)); But the following statement is invalid! Example: retail = 10 + wholesale; // wrong! Since the operator function was defined as a method, the left operand must be a class object. Thus, you cannot simply exchange the operands of the operator +. However, if you want to convert both operands, you will need global definitions for the operator functions. 420 ■ CHAPTER 19 OVERLOADING OPERATORS = () [] - > Assignment operator Function call Subscript operator Class member access Operators Meaning The function call operator () is used to represent operations for objects like function calls. The overloaded operator -> enables the use of objects in the same way as pointers. ✓ NOTE // Euro.h // The class Euro represents a Euro with // global operator functions implemented for + and // #ifndef _EURO_H_ #define _EURO_H_ // class Euro { // Without operator functions for + and // Otherwise unchanged, specifically with regard to // the operator functions implemented for += and -=. }; // // Global operator functions (inline) // Addition: inline Euro operator+( const Euro& e1, const Euro& e2) { Euro temp(e1); temp += e2; return temp; } // Subtraction: inline Euro operator-( const Euro& e1, const Euro& e2) { Euro temp(e1); temp -= e2; return temp; } #endif // _EURO_H_ ■ GLOBAL OPERATOR FUNCTIONS Operators overloadable by methods only The operator functions of the following operators have to be methods: The new Euro class GLOBAL OPERATOR FUNCTIONS ■ 421 ᮀ Operator Functions: Global or Method? You can define an operator function as a global function instead of a method. The four operators listed opposite are the only exceptions. += -= *= /= %= These operators always require a so-called l-value as their left operand, that is, they require an object with an address in memory. Global operator functions are generally preferable if one of the following situations applies: ■ the operator is binary and both operands are symmetrical, e.g. the arithmetic operators + or * ■ the operator is to be overloaded for another class without changing that class, e.g. the << operator for the ostream class. ᮀ Defining Global Operator Functions The operands for a global operator function are passed as arguments to that function. The operator function of a unary operator thus possesses a single parameter, whereas the operator function of a binary operator has two. The Euro class has been modified to provide a global definition of the operator func- tions for the operators + and Example: Euro operator+(const Euro& e1, const Euro& e2); Both operands are now peers. More specifically, conversion of int or double to Euro is performed for both operands now. Given a Euro object net, the following expres- sions are valid and equivalent: Example: net + 1.20 and 1.20 + net They cause the following function calls: operator+( net, 1.20) and operator+( 1.20, net) However, a global function cannot access the private members of the class. The func- tion operator+() shown opposite therefore uses the += operator, whose operator function is defined as a method. A global operator function can be declared as a “friend” of the class to allow it access to the private members of that class. 422 ■ CHAPTER 19 OVERLOADING OPERATORS // Euro.h // The class Euro with operator functions // declared as friend functions. // #ifndef _EURO_H_ #define _EURO_H_ // class Euro { private: long data; // Euros * 100 + Cents public: // Constructors and other methods as before. // Operators -(unary), +=, -= as before. // Division Euro / double : Euro operator/( double x) // Division *this/x { // = *this * (1/x) return (*this * (1.0/x)); } // Global friend functions friend Euro operator+( const Euro& e1, const Euro& e2); friend Euro operator-( const Euro& e1, const Euro& e2); friend Euro operator*( const Euro& e, double x) { Euro temp( ((double)e.data/100.0) * x) ; return temp; } friend Euro operator*( double x, const Euro& e) { return e * x; } }; // Addition: inline Euro operator+( const Euro& e1, const Euro& e2) { Euro temp; temp.data = e1.data + e2.data; return temp; } // Subtraction: inline Euro operator-( const Euro& e1, const Euro& e2) { Euro temp; temp.data = e1.data - e2.data; return temp; } #endif // _EURO_H_ ■ FRIEND FUNCTIONS Class Euro with friend functions FRIEND FUNCTIONS ■ 423 ᮀ The Friend Concept If functions or individual classes are used in conjunction with another class, you may want to grant them access to the private members of that class. This is made possible by a friend declaration, which eliminates data encapsulation in certain cases. Imagine you need to write a global function that accesses the elements of a numerical array class. If you need to call the access methods of the class each time, and if these methods perform range checking, the function runtime will increase considerably. How- ever, special permission to access the private data members of the class can dramatically improve the function’s response. ᮀ Declaring Friend Functions A class can grant any function a special permit for direct access to its private members. This is achieved by declaring the function as a friend. The friend keyword must pre- cede the function prototype in the class definition. Example: class A { // . . . friend void globFunc( A* objPtr); friend int B::elFunc( const A& objRef); }; Here the global function globFunc() and the method elFunc() of class B are declared as friend functions of class A. This allows them direct access to the private members of class A. Since these functions are not methods of class A, the this pointer is not available to them. To resolve this issue, you will generally pass the object the func- tion needs to process as an argument. It is important to note that the class itself determines who its friends are. If this were not so, data encapsulation could easily be undermined. ᮀ Overloading Operators with Friend Functions The operator functions for + and - in the Euro class are now defined as friend func- tions, allowing them direct access to the private member data. In order to compute interest, it is necessary to multiply and divide euros by double values. Since both the expression Euro*num and num*Euro are possible, friend func- tions are implemented to perform multiplications. As the example shows, friend func- tions can also be defined inline in a class. 424 ■ CHAPTER 19 OVERLOADING OPERATORS // Result.h // The class Result to represent a measurement // and the time the measurement was taken. // #ifndef _RESULT_ #define _RESULT_ #include "DayTime.h" // Class DayTime class Result { private: double val; DayTime time; public: // Constructor and access methods friend class ControlPoint; // All methods of }; // ControlPoint are friends. #include Result.h class ControlPoint { private: string name; // Name of control point Result measure[100]; // Table with results // . . . public: // Constructor and the other methods // . . . // Compute static values of measurement results // (average, deviation from mean, ). bool statistic(); // Can access the private // members of measure[i]. }; ■ FRIEND CLASSES Class Result Class ControlPoint FRIEND CLASSES ■ 425 ᮀ Declaring Friend Classes In addition to declaring individual friend functions, you can also make entire classes “friends” of another class. All the methods in this “friendly” class automatically become friend functions in the class containing the friend declaration. This technique is useful if a class is used in such close conjunction with another class that all the methods in that class need access to the private members of the other class. For example, the class ControlPoint uses objects of the Result class. Calcula- tions with individual measurements are performed repeatedly. In this case, it makes sense to declare the ControlPoint class as a friend of the Result class. Example: class Result { // . . . friend class ControlPoint; }; It is important to note that the ControlPoint class has no influence over the fact that it is a friend of the Result class. The Result class itself decides who its friends are and who has access to its private members. It does not matter whether a friend declaration occurs in the private or public section of a class. However, you can regard a friend declaration as an extension of the public interface. For this reason, it is preferable to place a friend declaration in the public area of a class. ᮀ Using Friend Functions and Classes Using friend functions and friend classes helps you to create efficient programs. More specifically, you can utilize global friend functions where methods are not suited to the task in hand. Some common uses are global operator functions declared as friend functions. However, extensive use of friend techniques diffuses the concept of data encapsula- tion. Allowing external functions to manipulate internal data can lead to inconsistency, especially if a class is modified or extended in a later version. For this reason, you should take special care when using friend techniques. 426 ■ CHAPTER 19 OVERLOADING OPERATORS // Array_t.cpp // A simple class to represent an array // with range checking. // #include <iostream> #include <cstdlib> // For exit() using namespace std; #define MAX 100 class FloatArr { private: float v[MAX]; // The array public: float& operator[](int i); static int MaxIndex(){ return MAX-1; } }; float& FloatArr::operator[]( int i ) { if( i < 0 || i >= MAX ) { cerr << "\nFloatArr: Outside of range!" << endl; exit(1); } return v[i]; // Reference to i-th element. } int main() { cout << "\n An array with range checking!\n" << endl; FloatArr random; // Create array. int i; // An index. // Fill with random euros: for( i=0; i <= FloatArr::MaxIndex(); ++i) random[i] = (rand() - RAND_MAX/2) / 100.0F; cout << "\nEnter indices between 0 and " << FloatArr::MaxIndex() << "!" << "\n (Quit by entering invalid input)" << endl; while( cout << "\nIndex: " && cin >> i ) cout << i << ". element: " << random[i]; return 0; } ■ OVERLOADING SUBSCRIPT OPERATORS A class representing arrays OVERLOADING SUBSCRIPT OPERATORS ■ 427 ᮀ Subscript Operator The subscript operator [] is normally used to access a single array element. It is a binary operator and thus has two operands. Given an expression such as v[i], the array name v will always be the left operand, whereas the index i will be the right operand. The subscript operator for arrays implies background pointer arithmetic, for example, v[i] is equivalent to *(v+i). Thus, the following restrictions apply to non-overloaded index operators: ■ an operand must be a pointer—an array name, for example ■ the other operand must be an integral expression. ᮀ Usage in Classes These restrictions do not apply if the index operator is overloaded for a class. You should note, however, that the operator function is always a class method with a parameter for the right operand. The following therefore applies: ■ the left operand must be a class object ■ the right operand can be any valid type ■ the result type is not defined. This allows for considerable flexibility. However, your overloading should always reflect the normal use of arrays. More specifically, the return value should be a reference to an object. Since an index can be of any valid type, the possibilities are unlimited. For example, you could easily define associative arrays, that is, arrays whose elements are referenced by strings. ᮀ Notes on the Sample Program Range checking is not performed when you access the elements of a normal array. An invalid index can thus lead to abnormal termination of an application program. How- ever, you can address this issue by defining your own array classes, although this may impact the speed of your programs. The opposite page shows a simple array class definition for float values. The sub- script operator [] has been overloaded to return a reference to the i-th array element. However, when the array is accessed, range checking is performed to ensure that the index falls within given boundaries. If an invalid index is found, the program issues an error message and terminates. The class FloatArr array has a fixed length. As we will see, variable lengths are pos- sible using dynamic memory allocation. 428 ■ CHAPTER 19 OVERLOADING OPERATORS // Euro.h : Class Euro to represent an Euro // #ifndef _EURO_H_ #define _EURO_H_ // class Euro { // The class is left unchanged. // The print() method is now superfluous. }; // // Declaration of shift operators: ostream& operator<<(ostream& os, const Euro& e); istream& operator>>(istream& is, Euro& e); #endif // _EURO_H_ // Euro_io.cpp // Overload the shift operators // for input/output of Euro type objects. // #include "Euro.h" #include <iostream> using namespace std; // Output to stream os. ostream& operator<<(ostream& os, const Euro& e) { os << e.asString() << " Euro"; return os; } // Input from stream is. istream& operator>>(istream& is, Euro& e) { cout << "Euro amount (Format x,xx): "; int euro = 0, cents = 0; char c = 0; if( !(is >> euro >> c >> cents)) // Input. return is; if( (c != ',' && c != '.') || cents>=100) // Error? is.setstate( ios::failbit); // Yes => Set else // fail bit. e = Euro( euro, cents); // No => Accept return is; // value. } ■ OVERLOADING SHIFT-OPERATORS FOR I/O Declaration of the operator functions Definition of operator functions . operands for a global operator function are passed as arguments to that function. The operator function of a unary operator thus possesses a single parameter, whereas the operator function of a. declaration, which eliminates data encapsulation in certain cases. Imagine you need to write a global function that accesses the elements of a numerical array class. If you need to call the access methods. concept of data encapsula- tion. Allowing external functions to manipulate internal data can lead to inconsistency, especially if a class is modified or extended in a later version. For this reason,