©Fraser Hutchinson & Cliff Green C++ Certificate Program C++ Intermediate Operator Overloading
©Fraser Hutchinson & Cliff Green Why? Operator overloading provides no new capabilities in C++ –Semantic sugar –Provides conventional (and more convenient) notation for manipulating class objects –Often better presents class semantics (particularly for value classes)
©Fraser Hutchinson & Cliff Green Generic Programming and Operators However, operator overloading can be crucial in template functions and classes: template T sum (T x) { return (x < 1 ? 0 : x + sum(x-1)); } The above function treats class and built-in types equivalently (with the same syntax)
©Fraser Hutchinson & Cliff Green Traditional String Manipulation char const * str1 (“abc”); char const * str2 (“def”); char const * str3 (“ghi”); char full_string[64]; strcpy(full_string, str1); strcat(full_string, str2); strcat(full_string, str3); fprintf(“string is now ‘%s’.\n”, full_string); // outputs “string is now ‘abcdefghi’.”
©Fraser Hutchinson & Cliff Green Operators Overloaded std::string const str1 (“abc”); std::string const str2 (“def”); std::string const str3 (“ghi”); std::string full_string (str1); // copy ctor full_string += str2; // operator += overloaded full_string += str3); cout << “string is now ‘“ << full_string << ‘’.\n”; // outputs “string is now ‘abcdefghi’.”
©Fraser Hutchinson & Cliff Green ->, = Operators ‘pointer to’ operator T *tp = new class T; tp->myMethod(); // same as (*tp).myMethod(); Equality operator a == b; // overload needed for class types
©Fraser Hutchinson & Cliff Green Limitations Anything you can do with operators can be done via methods (reverse is not true) Some operators may not be overloaded Can only overload existing operators – new operators may not be defined Limited to syntax defined in the C++ grammar (explained in more detail later)
©Fraser Hutchinson & Cliff Green What Can be Overloaded ‘Normal’ Operators + - * / % & ^ | > < ! = ~+= -= *= /= %= &= ^= |= >= <= != == ~= && || >>= <<= ‘Special’ operators [] () -> ->*, > new, delete, new[], delete[]
©Fraser Hutchinson & Cliff Green What Cannot be Overloaded ::..* Scope resolution operator, as in namespace::identifier Member selection, as in T.member Member selection through pointer to member, as in T.*member These all take a name rather than a value as the second operand
©Fraser Hutchinson & Cliff Green Declaration returntype ‘operator’ symbol (parameters) Perhaps most common is the assignment operator T& operator= (T const &);
©Fraser Hutchinson & Cliff Green Binary Operators Non-static member function taking one argument class X { public: X & operator = (X const &); } The LHS is the object itself
©Fraser Hutchinson & Cliff Green Binary Operators Non-member function taking two arguments void operator + ( X a, X b ); This function has no special access to internals of class X For can be interpreted as (bb) or
©Fraser Hutchinson & Cliff Green Unary Operators Non-static member function taking no arguments class X { public: X & operator ++(); // prefix ++ }; Again, the LHS is the object itself can be interpreted as () or
©Fraser Hutchinson & Cliff Green Unary Operators Non-member function taking one argument void operator* (X& x); // deref op As with binary operators, this function has no special access to internals of class X
©Fraser Hutchinson & Cliff Green Member vs Non-Member Recommendations Method (member function): All unary operators Must be a method: = () [] -> Method: += -= /= *= ^= &= |= %= >>= <<= All other binary operators: Non-member (global) function
©Fraser Hutchinson & Cliff Green “Special” operators May be thought of as having no strongly predefined meaning For example: “Array” subscript operator, functor aids, memory allocation, conversion operators
©Fraser Hutchinson & Cliff Green Operator Details Most binary and unary operators can be either methods or global functions Method versions are applied to LHS of the expression (binary ops) or directly to object (unary ops) Example expression: x = y + 2; either a global or a member version works
©Fraser Hutchinson & Cliff Green Grammatical Coherence Operator “arity” cannot be changed class X { X operator/(); // err, not unary op X operator &(X,X); // err, not ternary }; X operator % (X); // err, not unary
©Fraser Hutchinson & Cliff Green Predefined Meanings Only a few assumptions are made operator= operator[] operator() operator-> Must be non-static member functions, are assured an L-value is possible
©Fraser Hutchinson & Cliff Green Redundant Operators and Pre- Defined Meanings Built-in type operator equivalences not carried over – for int : a++ means a+=1, means a=a+1 For class operator overloading, no equivalences: X operator++(int) // postfix version –does not automatically define X operator+= (int) Associativity and precedence cannot be changed from built-in grammar rules
©Fraser Hutchinson & Cliff Green Redundancy Given x = x + 2, x = 2 + x is NOT necessarily valid (transitivity is not assumed) Compiler cannot assume intent from operator semantics For example, two operators are needed: T operator+ (int left, T const& right ); T operator+ (T const& left, int right ); First version (left op is integer) can’t be method (since would require adding method to built-in type int )
©Fraser Hutchinson & Cliff Green Redundant Operators x = x + 1; x += 1; x++; ++x; Gives 5 possible operations – all must be overloaded for class (user-defined) types (although copy assignment might be compiler generated)
©Fraser Hutchinson & Cliff Green Operators and Class Types An operator function must be either a member function or take at least one argument of class (user-defined) type C++ not intended to be mutable - cannot change expression rules, but meanings in the context of a user-defined object can be defined Thus, operators cannot be defined that operate exclusively on pointers For member functions, the first operand must be a user-defined object
©Fraser Hutchinson & Cliff Green Operators in Namespaces namespace std { class ostream { ostream &operator << (char const *); }; extern ostream cout; typedef basic_string string; ostream & operator << (ostream &, string const &); } // end namespace std int main() { char const * p = “hello”; std::string s (“world”); std::cout << p << “, “ << s << endl; }
©Fraser Hutchinson & Cliff Green Actual Function Calls std::cout << p becomes std::cout.operator <<(char const *) and std::cout << str becomes std::operator<<(std::cout, s)
©Fraser Hutchinson & Cliff Green Name Lookup Rules Name resolution follows same rules as regular functions, namespace std searched for appropriate operator overload: y is resolved: –If X is class type, look for as a member of X or base of X –Look for declarations of in the context surrounding y, and –If X is defined in namespace N, look for declarations of in N –If Y is defined in namespace M, look for declarations of in M
©Fraser Hutchinson & Cliff Green Constructors and Conversions int operator +(X,Y); int operator +(Y,X); int operator +(X,Z); int operator +(Z,X); int operator +(Y,Z); int operator +(Z,X);
©Fraser Hutchinson & Cliff Green Constructors and Conversions If conversions defined: X(Y); X(Z); Would only then need to code: int operator +(X,X); The statement int val (x + y); would result in x + X(y ) invoking int operator + (X,X);
©Fraser Hutchinson & Cliff Green Prefer Prefix Increment vs Postfix Using prefix ++ typically more efficient than postfix (never less efficient) Postfix ++ requires a temporary value to be stored and returned by value Built-in types may be optimized, class types typically not (in particular, iterator classes) Good style – prefer prefix increment, only use postfix when necessary as part of expression
©Fraser Hutchinson & Cliff Green Share Implementations When Appropriate class BigInt { // … BigInt& operator+= (int); BigInt& operator++ (); // prefix increment BigInt operator++ (int); // return by value }; BigInt& BigInt::operator+= (int) { // … return *this; } BigInt& operator++ () { return *this += 1; } BigInt operator++ (int) { BigInt tmp(*this); *this += 1; return tmp; }