1 / 33

Operator Overloading

Operator Overloading. Operator Overloading More than just Syntactic Sugar. Binary operators Unary operators Conversion Operators Proxy Classes (simulating a reference) bitset example Special operators Indexing Pre-post increment/decrement Function call.

kishi
Download Presentation

Operator Overloading

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Operator Overloading

  2. Operator OverloadingMore than just Syntactic Sugar • Binary operators • Unary operators • Conversion Operators • Proxy Classes (simulating a reference) • bitset example • Special operators • Indexing • Pre-post increment/decrement • Function call

  3. Complex NumbersA Canonical Example of a Value Type • Complex numbers are pairs of real numbers • (real, imaginary), e.g., (2,3), (1.04, -12.4) • like points in the x-y plane • (a, b) + (c, d) = (a+c, b+d) • applications in Electrical Engineering • Compare function-style operations to using operator functions…

  4. A complex Class #include <iostream> class complex { double real; double imag; public: complex(double real=0, double imag=0) { this->real = real; this->imag = imag; } complex add(const complex& c) const { complex result; result.real = this->real + c.real; result.imag = this->imag + c.imag; return result; } void display(std::ostream& os) const { os << '(' << real << ',' << imag << ')'; } };

  5. Using the complex Class using namespace std; int main() { complex z(1,2), w(3,4); complex a = z.add(w); // want a = z+w a.display(cout); } (4,6)

  6. Operator Functions • operator keyword together with an operator token • operator+ • operator- • etc. • Can overload all operators except: • :: • . • .* • ?:

  7. complex with operator+A member-function implementation class complex { // ... public: // ... complex operator+(const complex& c) const { complex result; result.real = this->real + c.real; result.imag = this->imag + c.imag; return result; } // ... }; int main() { complex z(1,2), w(3,4); complex a = z + w; // z.operator+(w) a.display(cout); }

  8. Rules • operator+ is the function name • you can also invoke it directly as z.operator+(w) • a binary function, of course • Normal precedence rules apply • Can be either a global or member function • If a member, this is the left operand • If global, at least one argument must be user-defined

  9. Conversion Constructors and Operator Overloading • Single-arg constructors provide implicit conversions • For example T::T(int) • Can use 2 initialization syntaxes: • T t(1); • T t = 1; • Provide implicit conversions • t + 1 becomes t + T(1) • Can disable with the explicitkeyword

  10. complex Conversion class complex { public: complex(double real = 0, double imag = 0) { this->real = real; this->imag = imag; } // … }; int main() { complex w(3,4); complex a = w + 1; // w + (1,0) a.display(cout); } (4,4)

  11. Member vs. Non-memberOperators • The expression 1 + wwon’t compile. • Left-operand must be a class object to match a member operator • A non-member operator will apply an implicit conversion to either operand via a single-arg constructor, if available • In general, binary operators should be non-members • For math-like operators, anyway • In general, unary operators should be members

  12. Non-member complexOperators class complex { public: complex(double real = 0, double imag = 0) { this->real = real; this->imag = imag; } double getReal() const {return real;} double getImag() const {return imag;} // ... }; complex operator+(const complex& c1, const complex& c2) { double real = c1.getReal() + c2.getReal(); double imag = c1.getImag() + c2.getImag(); complex result(real, imag); return result; }

  13. A Unary complex Operator class complex { public: complex operator-() const { return complex(-real, -imag); } // … }; int main() { complex w(3,4); complex a = -w; a.display(cout); } (-3,-4)

  14. Stream Operators • operator<< and operator>> • Must be global • because the left operand is a stream! • Stream is passed by reference • for efficiency • it disallows copy anyway • Should also return the stream • to support chaining insertions and extractions

  15. complex Stream Output ostream& operator<<(ostream& os, const complex& c) { os << '(' << c.getReal() << ',’ << c.getImag() << ')'; return os; } int main() { complex w(3,4); complex a = -w; cout << a << endl; } (-3,-4)

  16. A “Complete” complex • Would provide all pertinent operators • including assignment ops such as +=, -=, etc. • assignment ops must be members • typically implement op in terms of op= • Provide stream insertion and extraction • Global functions are class friends • not necessary, just a convenience

  17. Implementing op+ and op+=Typical implementation pattern • Develop operator+= first • it should return a reference to this • Write operator+ in terms of operator+=:T operator+(T t1, const T& t2) { return t1 += t2;} • (Note: t1 is passed by value)

  18. operator[] • A Unary Operator • And takes an integer index argument, of course • Must be a member • For “array-like” things • vectors, strings • Usually should provide two versions: • a version for const objects • a version for non-const objects • other operators may require this pattern

  19. A “Safe” Array classAlso a Useless class :-) class Index { enum {N = 100}; int data[N]; int size; public: Index(int n) { if (n > N) throw "dimension error"; for (int i = 0; i < n; ++i) data[i] = i; size = n; } int getSize() const {return size;} int& operator[](int i) { if (i < 0 || i >= size) throw "index error"; return data[i]; } };

  20. Using Index #include <iostream> using namespace std; int main() { Index a(10); for (int i = 0; i < a.getSize(); ++i) cout << a[i] << ' '; cout << endl; a[5] = 99; cout << a[5] << endl; cout << a[10] << endl; } 0 1 2 3 4 5 6 7 8 9 99 abnormal program termination

  21. Using a const Index #include <iostream> using namespace std; int main() { const Index a(10); // a const Index for (int i = 0; i < a.getSize(); ++i) cout << a[i] << ' '; // COMPILE ERROR! cout << endl; }

  22. Supporting a constIndexThat pesky Reference Return Problem again! class Index { // ... int& operator[](int i) { if (i < 0 || i >= size) throw "index error"; return data[i]; } int operator[](int i) const // A const version { if (i < 0 || i >= size) throw "index error"; return data[i]; } };

  23. operator[][] • Doesn’t exist! • Just like multi-dimensional arrays don’t exist • Simulate it • Using the “array of arrays” paradigm • a[i][j] == (a.operator[](i)).operator[](j) • The outer operator[] should return something that has another operator[] to invoke • Example: op2db.cpp

  24. Conversion Operators • The complement to single-arg constructors • Provide implicit conversions to another type • Member function with the signature: operator T() const;

  25. Index-to-double Conversion class Index { // ... operator double() const { double sum = data[0]; for (int i = 1; i < size; ++i) sum += data[i]; return sum / size; } }; int main() { const Index a(10); double x = a + 1; cout << x << endl; } 5.5

  26. Warning! • Consider the expression w + 1 where w is complex. • Design heuristic: Don’t allow types to be implicitly converted to one another • You can disable implicit conversions via constructors and conversion operators via the explicit keyword

  27. lvalue vs. rvalueDetection IdiomUsed in Program 3 (see returnref.cpp) • For operator[ ] • Because bits are not individually addressable • Setting and resetting a bit are very different operations • Uses a Proxy class • Returned by bitset<N>::operator[] • Stores the bit position and a reference to the bitset • Overloads operator=(bool) • Converts implicitly to bool

  28. Stream State • if (stream) tests true if the stream state is good (!bad() & !fail() & !eof()) • How is this accomplished? • Two choices: • operator void* (historical) • operator bool • Examples: opbool.cpp, opvoid.cpp

  29. Review Program 3 Spec

  30. Other Operators • -> (for “smart pointers”) • we covered this already • ++, -- • Both pre and post versions • ( ) • “function-call” operator • We’ll see more of this when we cover STL

  31. Overloading ++ and -- • Must distinguish between pre and post • Post versions take an extraneous int argument • The post versions must save current value • That’s why the pre versions are more efficient • They should also return a const object • To disallow x++++ • Illegal, modifies temporary • Examples: PrePost.cpp, SafeArrayPtr.cpp

  32. Overloading operator( ) • The “Function Call” Operator • Constitutes a Function Object • An object that behaves like a function • If class T::operator( ) exists: • Then t( ) acts like a function call • Example: findGreater.cpp, findGreater2.cpp

  33. Operator Overloading Checkpoint  • When should you make an operator function a member? • When should you make an operator function a non-member? • Why should you provide two versions of operator[]? • What side effect ensues from defining a constructor that can called with a single argument? • When should you use a conversion operator? • When shouldn’t you use a conversion operator?

More Related