Chapter 11 Operators as Functions ================================= When a class is designed a new data type is being added to the C++ language. The ability to overload a C++ operator allows the programmer to define the meaning of operators relative to the class being defined. Operators are overloaded and selected based on the signature- matching algorithm for finding overloaded functions. The keyword **operator** is used to define a type conversion member function. It is also used to overload the built-in C operators. Just as a function name can be given a variety of meanings, with each meaning being different depending upon the arguments being passed, so can an operator, such as **+**, be given additional meanings. Overloading operators allows infix expressions of both ADTs and built-in types to be written. It is an important notational convenience and in many instances leads to shorter and more readable programs. 11.1 Defining an Overload Operator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Defining operators for a class is easy once you know how the application of an operator is translated to a function call. For a unary operator such as **&**, when you write the following: :: where **X** is an instance of some class, the C++ compiler applies the operator by calling this function: :: X.operator&() The compiler automatically passes a pointer to the class instance to the function. For binary operators such as **+**, an expression such as this: :: X + Y where **X** and **Y** are class instances, the compiler calls the function: :: X.operator+(Y) As you can see, the C++ compiler reduces the application of operators to function calls. Thus, you can overload an operator by defining a function whose name begins with the keyword **operator** followed by the symbolic notation of that operator. 11.2 Arguments to Operator Functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Like all member functions, operator functions receive a pointer to the class instance in the hidden argument named this. Because this argument is implicit, unary operator functions are defined with no arguments at all. Binary operator functions that are members of the class take a single argument, which is the right- hand side of the operator expression. The ternary operator, **?:**, cannot be overloaded. However, you can define an operator function as a friend instead of as a member function of the class. By defining an operator overload function as a friend, the class instance pointer is not passed to the function. There are times when the programmer needs to define friend operator functions. When declared as a friend, the operator function requires all arguments explicitly. Thus, to declare **operator+** as a friend function of **class X**, the following would be written: :: friend X operator+(X&, Y&); // assume X is a class and thereafter, to evaluate the expression **x1 + x2** for two instances of **class X**, the C++ compiler will call the function **operator+(x1, x2)**. 11.3 Operators That Can Be Overloaded ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ All C++ operators can be overloaded except for the following: :: Member access operator x.y Dereferencing pointer to member x.*y Scope resolution operator x::y Conditional operator x?y:z Those that can be overloaded are: :: + - * / % ^ & | - ! , = < > <= >= ++ -- << >> == != && || += -= /= %= ^= &= |= <<= >>= [] () -> new delete Although C++ enables the programmer to redefine the meaning of most of the built-in operator symbols for a class, there is no ability to change the precedence rules that dictate the order in which operators are evaluated. C++ operators have the same precedence as those of their ANSI C counterparts. Even if, for some class, the programmer were to define **operators +** and ***** to have entirely different meanings from addition and multiplication, in an expression such as this: :: a + b * c // a, b, c are some class instances the C++ compiler will still invoke the **operator*** function to evaluate **b * c** before calling **operator+**. The following tips will help in designing classes with overloaded operators (assume that **a** and **b** are instances of appropriate class types). #. C++ does not "understand" the meaning of an overloaded operator. It's the programmer's responsibility to provide meaningful overloaded functions. #. C++ is not able to derive complex operators from simple ones. For instance, if you define overloaded operator functions **operator*** and **operator=**, C++ cannot evaluate the expression **a *= b** correctly. #. The programmer may never change the syntax of an overloaded operator. Operators that are binary must remain binary. Unary operators must remain unary. #. The programmer cannot invent new operators for use in expressions. Only those operators listed for the syntax of the language can be overloaded. However, the programmer can always write functions for special cases. #. The programmer may overload the operators **++** and **--**. As an example of operator overloading, consider the **+** operator, the binary version, for the **CString** class. A good interpretation of this operator for the **CString** class would be to concatenate two **CString** objects. A typical use of the **+** operator for **CString** might be this: :: CString s1("This "), s2("and that"), s3; s3 = s1+s2; The programmer can get this functionality by defining the following function as a member of the CString class; Fig. 11-1 ````````` :: // ********************************************************* // overload + // Member function to concatenate two String objects CString& CString::operator+( const CString& str ) { size_t lgth = len + str.len; char *t = new char[lgth + 1]; strcpy( t, s ); strcat( t, str.s ); return CString(t); } Because this version of the **operator+** is a member function of the **CString** class, it takes only one argument, a reference to the **CString** on the right-hand side of the **+** operator. The function returns a new **CString** object that is a concatenation of the two **CString** items that are being added. Although the member function **operator+** works well when adding **CString** items, it cannot handle another type of use for the operator. Because a **CString** is meant to model a dynamic array of characters, it is natural to allow the use of the operator in expressions such as this: :: CString str = "World!". CString s2 = "Hello, "+ str; // expecting "Hello, World!" In this case, the C++ compiler will interpret the right-hand side of the expression as the following: :: "Hello".operator+(str) This is an error, because "Hello" is not an instance of a class and therefore has no member **operator+** function that can be applied to "Hello". One might think that a solution would be to convert "Hello" to a **CString** and then apply the **operator+** function of the **CString** class. But, this does not happen because the C++ compiler does not automatically convert the left-hand operand of any member operator functions. However, if the programmer were to define a nonmember friend **operator+** function in the **CString** class: :: friend CString operator+(const char *s1, const String& s2); the compiler would convert the expression call this function with the pointer to "Hello" as the first argument and str as the second argument. This would evaluate to the following function call: :: operator+("Hello", str) The definition of the friend **operator+** function is similar to the member function, except that it does not take an implied pointer to the current instance of the class as the first argument, and the body of the function has to refer to each argument explicitly. Following is a definition of the function: Fig. 11-2 ````````` :: //*********************************************************** // CString concatenation operator, declared as "friend" CString operator+(const char *a, CString ) // overload { CString temp; strcpy( temp.s, a ); if( (strlen(a) + b.len ) < MAXLEN ) { strcat( temp.s, b.s ); temp.len = strlen( temp.s ); } else cerr << "Max length exceeded in concatenation.\n"; return temp; } 11.4 Copying Class Instances ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When copying one class instance to another instance of a compatible type, the results can be unexpected. C++ makes copies of instances not only in assignment expressions but at other times. So you must be aware of the consequences of copying class instances, even if you do not explicitly assign them with the **=** operator. There are four times when a copy of a class instance will be made: #. When one instance is used to initialize a newly defined instance of the same class. #. When an instance is passed to a function's value parameter of the class type. #. When a function returns a class instance (not a reference or pointer to the class). #. When a statement assigns one instance to another. The first three of these cases initialize new copies of class instances using the value of an existing instance. The fourth case assigns the value of an existing instance to another instance that was previously defined. In all cases, the result is an object that contains copies of the data fields of another object. After making such copies, all may seem well, but trouble lurks unseen when the class instances contain pointer fields that address variables allocated space on the heap. Problems can also arise when creating copies of instances that contain fields of other class types, which may contain their own pointer fields. If two or more pointers happen to address the same location in memory, deleting one of those pointers will cause the others to address invalid data. Worse, deleting the same space more than once can corrupt the heap and cause a major bug. Because classes often inherit properties of many other classes, a simple assignment or function call might create dozens of duplicate pointers. And if those pointers address instances were allocated by new, class destructors might deallocate the same memory spaces multiple times, which will almost always corrupt the heap. To avoid such problems, the programmer should always provide a copy constructor for any class that includes dynamically allocated members. Fig. 11-3 ````````` :: // ********************************************************* // CString(const String&) // Create a new CString as a copy of another CString // This is called the "copy constructor" CString::CString( const CString& str ) { len = s.len; maxlen = s.maxlen; s = new char[maxlen]; strcpy( s, str.s ); } 11.5 Overloading the Input and Output Operators ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When the programmer starts to define his/her own classes such as the CString class, he/she might want to overload the definitions of the **<<** and **>>** operators so that they work with the class. For example, once the **>>** operator is overloaded, it is possible to read characters from an input stream into a **CString** object by writing: :: CString user_input; cin >> user_input; // accept user's input Similarly, to display a **CString**, the programmer would write the following: :: CString greetings = "Hello, World!"; cout << greetings << endl; 11.5.1 Input Operator ~~~~~~~~~~~~~~~~~~~~~ The stream extraction operator, **>>**, is easy to implement. The following version assumes a maximum string length of 256 characters including the null byte and uses the get function of the input stream to read in the characters into an internal array. Then it creates a new **CString** object from that character array and returns the **CString**. Fig. 11-4 ~~~~~~~~~ :: //*********************************************************** // Stream extraction operator for CString class istream& operator>>(istream& is, CString& str) { char buf[MAXLEN]; if( is.get(buf,MAXLEN) ) str = String( buf ); return is; } 11.5.2 Output Operator ~~~~~~~~~~~~~~~~~~~~~~ To overload the insertion operator, **<<**, a public member function is needed for the class that can handle the actual output. For the **CString** class, a print function is defined that performs the output as follows: Fig. 11-5 ````````` :: //*********************************************************** // Output the CString to the specified output stream void CString::print( ostream& os ) const { os << s ; } Once the print function is defined, you can overload the **<<** operator for a **CString** argument as follows: Fig. 11-6 ````````` :: //*********************************************************** // Stream insertion operator for CString class ostream& operator<<(ostream& os, String& str) { str.print( os ); return os; } As can be seen, this operator function does its work by calling the member function named print within the **Cstring** class. Note that the **ostream** class declares **operator<<** as a **friend** function. 11.6 Templates ~~~~~~~~~~~~~~ Templates, also called generics or parameterized types, allow the programmer to construct a family of related functions or classes. 11.6.1 Function Templates ~~~~~~~~~~~~~~~~~~~~~~~~~ Consider a function **max(x,y)** that returns the larger of its two arguments, **x** and **y** can be of any type that has the ability to be ordered. But, since C++ is a strongly typed language, it expects the types of the parameters **x** and **y** to be declared at compile time. Without using **templates**, many overloaded versions of **max()** are required, one for each data type to be supported, even though the code for each version is essentially identical. Each version compares the arguments and returns the larger. For example, :: int max( int x, int y ) { return ( x < y) ? x : y ; } long max( long x, long y ) { return ( x > y) ? x : y; } One way around this problem is to use a macro: :: #define max(x,y) ((x>y) ? x : y ) However, using the **#define** circumvents the type-checking mechanism that makes C++ such an improvement over C. In fact, this use of macros is almost obsolete in C++. Clearly, the intent of **max(x,y)** is to compare compatible types. Unfortunately, using the macro allows a comparison between an **int** and a **struct**, which are incompatible. Another problem with the macro approach is that substitution will be performed where it is not desired: :: class Foo { public: int max(int, int); // syntax error; this gets expanded }; By using a **template** instead, the programmer can define a pattern for a family of related overloaded functions by letting the data type itself be a parameter: :: template T max( T x, T y ) { return ( x > y ) ? x : y ; } The data type is represented by the **template** argument: ****. When used in an application, the compiler generated the appropriate function according to the data type actually used in the call: :: int i; Myclass a, b; int j = max(i,0); // arguments are integers Myclass m = max(a,b); // arguments are type Myclass Any data type (not just a class) can be used for ****. The compiler takes care of calling the appropriate **operator>()**, so that the call to max can be used with arguments of any type for which **operator>()** is defined. 11.6.2 Class Templates ~~~~~~~~~~~~~~~~~~~~~~ A class **template** (also called a generic class or class generator) allows the programmer to define a pattern for class definitions. Generic container classes are good examples. Consider the following example of a stack class. Whether the stack is for integers, character strings or of any other type, the basic operations performed on the type are the same (insert, delete, index, and so on). With the element type treated as a type parameter to the class, the system will generate type-safe class definitions on the fly: Listing 11-1 ```````````` :: // ************************************************************* // Interface File : CStack.h // Implementation File : CStack.cpp // Description : A template class that implements // : a stack. // ************************************************************* // #pragma interface #ifndef _CSTACK_H #define _CSTACK_H #include // #ifndef _BOOL // #define _BOOL // enum bool { false, true }; // #endif template class CStack { protected: // // Structure that describes each Node of the linked list // struct StackNode { T nodeData; StackNode *next; }; unsigned _cnt; // count of nodes in the stack bool _newErr; // error indicator for new StackNode *_tos; // pointer to the top of the stack CStack& copy( CStack& ); public: // // Constructors // CStack(); // // Destructors // ~CStack(); // // Accessors // bool isempty() const { return ( _cnt == 0 ) ? true : false; } bool getNewErr() const { return _newErr; } // // Mutators // void push( T ); bool pop( T& ); void clean( void ); CStack& operator=( CStack& item ) { copy( item ); return *this; } }; #endif // ************************************************************* // Interface File : CStack.h // Implementation File : CStack.h // Description : Contructs a template class for a // : stack; // ************************************************************* // #pragma implementation // #include "cstack.h" template CStack::CStack() { _cnt = 0; _newErr = false; _tos = NULL; } template CStack::~CStack() { clean(); } template CStack& CStack::copy( CStack& item ) { StackNode *current, *nextNode, *temp; // // clear the stack if not already empty // if( _tos ) clean(); // // copy the data members across // _cnt = item._cnt; _newErr = item._newErr; // // assign NULL to the top of stack pointer // _tos = NULL; // // exit if the source is empty // if( !item._tos ) return *this; // // allocate a new stack element // _tos = new StackNode; // // exit if error in allocation // if( !_tos ) { _newErr = true; return *this; } // // initialize pointers to copy all of stack // _tos->next = NULL; _tos->nodeData = item._tos->nodeData; nextNode = item._tos->next; current = _tos; // // duplicate the source stack elements // while( nextNode ) { // // create a new stack // temp = new StackNode; if( !temp ) { _newErr = true; return *this; } // // copy data // temp->nodeData = nextNode->nodeData; temp->next = NULL; current->next = temp; current = current->next; nextNode = nextNode->next; } return *this; } template void CStack::clean() { T temp; // // pop everything from the stack // while( pop(temp) ); } template void CStack::push( T item ) { StackNode *temp; _newErr = false; // // first node on the stack? // if( _tos ) // no { // // get a new stack node // temp = new StackNode; if( !temp ) { _newErr = true; return; } temp->nodeData = item; temp->next = _tos; _tos = temp; } else // yes { _tos = new StackNode; if( _tos == NULL ) { _newErr = true; return; } _tos->nodeData = item; _tos->next = NULL; } ++_cnt; } template bool CStack::pop( T& item ) { StackNode *temp; // // anything in the stack? // if( _cnt ) { // // save the data being popped into the reference // data item // item = _tos->nodeData; // // save the current top of stack address // temp = _tos; // // move top of stack to the next node on the list // _tos = _tos->next; // // delete the old top of stack // delete temp; // // decrement the count of nodes // --_cnt; return true; } else return false; } Notice that in the above example that both the interface and the implementation appear in the header file. There is no separate **.cpp** file that holds the implementation. Most compilers require that both the interface code and the implementation be in one file for templates. Listing 11-2 ```````````` :: // ************************************************************ // Source File : tstack.cpp // Description : Test program to exercise the parametric // : class CStack. // ************************************************************ #include #include "cstack.h" #include "cstring.h" int main() { CStack myData; int values[5]; int ch; cout << "Enter 5 integers: "; cin >> values[0] >> values[1] >> values[2] >> values[3] >> values[4]; for( int i = 0; i < 5; ++i ) myData.push( values[i] ); for( i = 0; i < 5; ++i ) { myData.pop( values[i] ); cout << "Value at[ " << i << " ]= " << values[i] << endl; } cout << "End of integer Stack Demo"; CString list[5]; char buf[80]; CStack names; for( i = 0; i < 5; ++i ) { cout << "\nEnter a name: "; cin >> buf; list[i] = buf; names.push( list[i] ); } for( i = 0; i < 5; ++i ) { names.pop( list[i] ); cout << "Value at[ " << i << " ]= " << (const char *)list[i] << endl; } cout << "End of CString Stack Demo - Press RETURN: "; cin >> ch; return 0; } As with function templates, an explicit template class definition may be provided to override the automatic definition for a given type: :: class CStack {...}; The symbol **CStack** must always be accompanied by a data type in angle brackets. It cannot appear alone, except in some cases in the original template definition. 11.7 Exceptions ~~~~~~~~~~~~~~~ Another method available for handling out of memory conditions and other exceptions appears only in the latest versions of C++. As of this writing only the Borland C++ version 4.xx compiler and the GNU C++ version 2.7.0 compiler implements the specifications of the ANSI/ISO C++ committee on exception handling. Exception handling involves three keywords in C++, **throw**, **try** , and **catch**. Throwing an exception lets you gather information at the throw point that could be used in troubleshooting the causes that led to the program failure. The **throw** keyword is analogous to using **raise()** with **signal()**. A C++ program can directly raise an exception in a **try** block by using the **throw** expression. The trying of code that may result in an exception is protected by having a **catch** routine. The catching of an exception allows the program to take actions that could led to a graceful program termination. Only synchronous exceptions are handled, meaning that the failure is generated from within the program, not from an external source, such an pressing CTRL-C or CTRL-BREAK. If asynchronous exceptions are to be handled then the **signal()** function must still be used along with, possibly, the **raise()** function. Fig. 11-7 ````````` :: Array::Array( int elements ) { if( elements < 1 ) throw ( elements ); pArray = new int[elements]; if( pArray == (int *)NULL ) throw( "NO SPACE on the FREE store" ); } void foo() { try { Array first( 10000 ), b( -5 ); .... } catch( int x ) { ... // code to handle incorrect number of elements ... // in an Array type } catch( char *errMsg ) { ... // code to handle no more free store } } The try-block specified by **try** must be followed immediately by the handler specified by **catch**. If an exception is thrown in the try- block, program control is transferred to the appropriate exception handler. 11.7.1 throw ~~~~~~~~~~~~ The **throw** keyword comes in two syntactic forms: :: (1) throw (2) throw expression The second format, **throw expression**, will cause an exception to be raised. The innermost try block in which an exception is raised is used to select the **catch **throw**, causes the current exception to be rethrown. It is used when a second handler is needed to complete processing of an exception. With the second format, the expression thrown is a static, temporary item that persists until the **catch** handler is exited. The **catch** handler may use this expression in its processing. :: void sample() { int x; ... throw x; } main() { try { sample(); } catch( int value ) { ... } } The value thrown by **throw** in the **sample()** function persists until the handler with the signature **catch( int value )** exits. The argument **value** is available for use within the catch routine as would any argument being passed to a function. When a function called from another function throws an exception, the process stack is unwound until an exception handler is found. This means that by exiting from the local function, any local automatic storage class variables will be destroyed. :: void func2() { int local_2A, local_2B; ... throw local_2A; } void func1() { int local_1A; ... func2(); } main() { try { func1(); // func2() is exited with local_2A and // local_2B destroyed } catch( int value ) { ... throw; // exception is re-thrown, possibly due to // some condition within this catch() function } If the thrown expression was of integer type, the rethrown exception is of the same integer value and will be handled by the nearest handler suitable for that type, but not by the handler that re-threw the exception. Although C++ allows an exception to be of almost any type, it is useful to make exception classes. The exception object is treated exactly the way any object would be treated. An exception carries information from the point where the exception is caught. Fig. 11-8 ````````` :: enum ErrorType { BOUNDS, HEAP, OTHER }; class CArrayError { public: CArrayError( ErrorType, int, int ); // for out of BOUNDS CArrayError( ErrorType, int ); // for out of HEAP CArrayError( ErrorType ); // for OTHER ... private: ErrorType _errType; int _upperBound , _index , _size ; }; Now a **throw expression** using an object of type **CArrayError** can be more informative to a handler than just throwing simple expressions. :: ... throw CArrayError( BOUNDS, i, _upperBound ); ... 11.7.2 try ~~~~~~~~~~ The **try** keyword has the form :: try compound statement handler list The **try** block is where the decision is made as to what handler is called to handle a raised exception. The order in which handlers are defined determines the order in which the handlers with matching argument lists are tried. A **throw expression** matches the **catch** argument if it is #. An exact match. #. A public base class of a derived type which is what is thrown. #. A thrown object type that is a pointer type convertible to a pointer type that is the catch argument. It is unwise to list handlers in an order that prevents them from being called. For example: :: catch( void * ); // would also catch any "char *" arguments catch( char * ); catch( BaseClass& ); // would also catch any "DerivedClass&" calls catch( DerivedClass& ); **try** blocks can be nested. If no matching handler is available in the immediate **try** block, a handler is selected from its immediately surrounding **try** block. If no handler can be found that matches, then a default behavior is used. 11.7.3 catch ~~~~~~~~~~~~ The **catch** keyword has the following form :: catch( formal argument ) compound statement The **catch** appears to be a function declaration of one argument without a return type. **catch** routines do not accept more than one argument, because that would imply that more than one exception was being handled at once. The **catch** routine may have an argument list of ellipses, ..., which matches any argument. In addition, the formal argument can be an abstract declaration, which means it can have a type without a variable name. The **catch** handler is called by an appropriate **throw expression**. At that point the **try** block, which was in effect when the exception was thrown, is exited. Upon exit from the **try** block, the destructors for any objects that are local to the **try** block are invoked. 11.7.4 Exception Specifications ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An exception specification can be part of the syntax of a function declaration :: function_header throw( type list ) The type list is the list of types that a **throw expression** within the function can have. If no type list is specified, the compiler can assume that no **throw** will be executed by the function. :: void func1() throw( int, CArrayError ); void func2( int x ) throw(); If an exception specification is left off, then the assumption is that an arbitrary exception can be thrown by the function. It is recommended that those exceptions that will be handled in a function be specified with the function declaration. Violations of the specifications are a run-time error. 11.7.5 terminate() and unexpected() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When no other handler has been specified to deal with an exception, the system provides the **terminate()** handler as a default. Within the **terminate()** handler, the **abort()** function is called. The programmer can use the **set_terminate() **abort()**, by the default termination handler. When a function throws an exception that was not in its exception specification list, the system provides the handler **unexpected()** which will be called. By default the **abort()** function is called by the **unexpected()** handler. The programmer can use the **set_unexpected()** function to change the function called by the handler. The predefined exceptions in the C++ language are as follows: :: Exception Type Header file ---------------------------------------------------------------- Bad_cast class typeinfo.h Bad_typeid class typeinfo.h set_new_handler function new.h set_terminate function except.h set_unexpected function except.h terminate function except.h Type_info class typeinfo.h unexpected function except.h xalloc class except.h xmsg class except.h In the following example, an allocation failure will result in the predefined exception **xalloc** being thrown. Your program should always be prepared to **catch** the **xalloc** exception before trying to access the new object. To handle the catching of an exception you must implement a **try {}** and **catch {}** pair of operations. Listing 11-3 ```````````` :: // // LINES.CPP - dynamically allocate space from the free store // using try and catch for exceptions. This will only work // with the most recent BorlandC++ v4.xx or Microsoft Visual C++ // v2.x or Microsoft Visual C++ v4.0 compilers. // #include #include int main() { char **buf; int height, width, i; // // acquire parameters concerning data to be input // cout << "Enter number of lines: "; cin >> height; cout << "Enter width of lines: " ; cin >> width; // // allocate array of buffers for text // try // test for exceptions { buf = new char *[height]; for( i = 0; i < height; ++i ) buf[i] = new char[width]; } catch( xalloc ) // enter this only if xalloc is // thrown { cout << "ERROR: Could not allocate space." << endl; return 1; } // // get text from keyboard and save in array // for( i = 0; i < height; ++i ) { cout << "Enter text: "; cin >> buf[i]; } // // move down on the screen // cout << "\n\n\n\n\n"; // // place text on the screen // for( i = 0; i < height; ++i ) cout << buf[i] << endl; // // return space to free store // for( i = 0; i < height; ++i ) delete buf[i]; delete buf; return 0; }