CSCI 104 Operator Overloading & Copy Semantics

1 CSCI 104 Operator Overloading & Copy SemanticsMark Rede...
Author: Derrick Watts
0 downloads 2 Views

1 CSCI 104 Operator Overloading & Copy SemanticsMark Redekopp David Kempe

2 Get the Example Code Download the code$ wget $ tar xvf str_ops.tar $ wget $ tar xvf complex.tar Str should mimic the C++ string class Properly handle memory allocation Let you treat it like an array where you can do '[i]' indexing Let you do comparison on string objects with '==' and other operators, etc. Complex should mimic a complex number

3 List/Array Indexing Enter C++ operator overloading#ifndef LLISTINT_H #define LLISTINT_H class LListInt{ public: LList(); // Constructor ~LList(); // Destructor int& get(int loc); ... private: Item* head_; }; #endif int main() { LListInt my_list(); my_list.push_back(5); my_list.push_back(7); cout << my_list.get(0) << endl; cout << my_list[0] << endl; return 0; } Arrays and vectors allow indexing using square brackets: [ ] E.g. my_list[i] equivalent to my_list.get(i) It would be nice to allow that indexing notation for our List class But if we just try it won't compile…How does the compiler know what to do when it sees a List object followed by square brackets Enter C++ operator overloading Allows us to write our own functions that will be "tied" to and called when a symbolic operator (+, -, *, [ ]) is used

4 Function Overloading What makes up a signature (uniqueness) of a function name number and type of arguments No two functions are allowed to have the same signature; the following 3 functions are unique and allowable… void f1(int); void f1(double); void f1(List&); void f1(int, int); void f1(double, int); We say that “f1” is overloaded 5 times

5 Operator Overloading class User{ public: User(string n); // Constructor string get_name(); private: int id_; string name_; }; C/C++ defines operators (+,*,-,==,etc.) that work with basic data types like int, char, double, etc. C/C++ has no clue what classes we’ll define and what those operators would mean for these yet-to-be-defined classes Class complex { public: double real, imaginary; }; Complex c1,c2,c3; c3 = c1 + c2; // should add component-wise Class List { … }; List l1,l2; l1 = l1 + l2; // should concatenate l2 items to l1 user.h #include “user.h” User::User(string n) { name_ = n; } string User::get_name(){ return name_; } user.cpp #include #include “user.h” int main(int argc, char *argv[]) { User u1(“Bill”), u2(“Jane”); // see if same username // Option 1: if(u1 == u2) cout << “Same”; // Option 2: if(u1.get_name() == u2.get_name()) { cout << “Same” << endl; } return 0: } user_test.cpp

6 Operator Overloading w/ Global FunctionsCan define global functions with name "operator{+-…}" taking two arguments LHS = Left Hand side is 1st arg RTH = Right Hand side is 2nd arg When compiler encounters an operator with objects of specific types it will look for an "operator" function to match and call it int main() { int hour = 9; string suffix = "p.m."; string time = hour + suffix; // WON'T COMPILE…doesn't know how to // add an int and a string return 0; } string operator+(int time, string suf) { stringstream ss; ss << time << suf; return ss.str(); } int main() { int hour = 9; string suffix = "p.m."; string time = hour + suffix; // WILL COMPILE TO: // string time = operator+(hour, suffix); return 0; }

7 Operator Overloading for ClassesC++ allows users to write functions that define what an operator should do for a class Binary operators: +, -, *, /, ++, -- Comparison operators: ==, !=, <, >, <=, >= Assignment: =, +=, -=, *=, /=, etc. I/O stream operators: <<, >> Function name starts with ‘operator’ and then the actual operator Left hand side is the implied object for which the member function is called Right hand side is the argument class Complex { public: Complex(int r, int i); ~Complex(); Complex operator+(const Complex &rhs); private; int real, imag; }; Complex Complex::operator+(const Complex &rhs) { Complex temp; temp.real = real + rhs.real; temp.imag = imag + rhs.imag; return temp; } int main() { Complex c1(2,3); Complex c2(4,5); Complex c3 = c1 + c2; // Same as c3 = c1.operator+(c2); cout << c3.real << "," << c3.imag << endl; // can overload '<<' so we can write: // cout << c3 << endl; return 0; } In complex example code: Write global level operator+ that uses add() Move to a class member function operator+ Now try to support c1 + 5 (Complex + int) Then add comparison (==) comparator Now go back and try to support (int + Complex) Need for global level function…but show compile error due to private access Need for friend function. Finally add ostream operator as friend

8 Binary Operator OverloadingFor binary operators, do the operation on a new object's data members and return that object Don’t want to affect the input operands data members Difference between: x = y + z; vs. x = x + z; Normal order of operations and associativity apply (can’t be changed) Can overload each operator with various RHS types… See next slide

9 Binary Operator Overloadingclass Complex { public: Complex(int r, int i); ~Complex() Complex operator+(const Complex &rhs); Complex operator+(int real); private: int real, imag; }; Complex Complex::operator+(const Complex &rhs) { Complex temp; temp.real = real + rhs.real; temp.imag = imag + rhs.imag; return temp; } Complex Complex::operator+( int real) { Complex temp = *this; temp.real += real; return temp; } int main() { Complex c1(2,3), c2(4,5), c3(6,7); Complex c4 = c1 + c2 + c3; // (c1 + c2) + c3 // c4 = c1.operator+(c2).operator+(c3) // = anonymous-ret-val.operator+(c3) c3 = c1 + c2; c3 = c3 + 5; }

10 Relational Operator OverloadingCan overload ==, !=, <, <=, >, >= Return bool class Complex { public: Complex(int r, int i); ~Complex(); Complex operator+(const Complex &rhs); bool operator==(const Complex &rhs); int real, imag; }; bool Complex::operator==(const Complex &rhs) return (real == rhs.real && imag == rhs.imag); } int main() Complex c1(2,3); Complex c2(4,5); // equiv. to c1.operator==(c2); if(c1 == c2) cout << “C1 & C2 are equal!” << endl; return 0; Nothing will be displayed

11 Practice Add the following operators to your Str class Operator[]Operator==(const Str& rhs); If time do these as well but if you test them they may not work…more on this later! Operator+(const Str& rhs); Operator+(const char* rhs);

12 Non-Member Functions What if the user changes the order?int main() { Complex c1(2,3); Complex c2(4,5); Complex c3 = 5 + c1; // ?? 5.operator+(c1) ?? // ?? int.operator+(c1) ?? // there is no int class we can // change or write return 0; } What if the user changes the order? int on LHS & Complex on RHS No match to a member function b/c to call a member function the LHS has to be an instance of that class We can define a non-member function (good old regular function) that takes in two parameters (both the LHS & RHS) May need to declare it as a friend Doesn't work Complex operator+(const int& lhs, const Complex &rhs) { Complex temp; temp.real = lhs + rhs.real; temp.imag = rhs.imag; return temp; } int main() { Complex c1(2,3); Complex c2(4,5); Complex c3 = 5 + c1; // Calls operator+(5,c1) return 0; Still a problem with this code Can operator+(…) access Complex's private data?

13 Friend Functions A friend function is a function that is not a member of the class but has access to the private data members of instances of that class Put keyword ‘friend’ in function prototype in class definition Don’t add scope to function definition class Dummy { public: Dummy(int d) { dat = d }; friend int inc_my_data(Dummy &dum); private: int dat; }; // don’t put Dummy:: in front of inc_my_data(...) int inc_my_data(Dummy &dum) dum.dat++; return dum.dat; } int main() Dummy dumb(5); dumb.dat = 8; // WON'T COMPILE int x = inc_my_data(dumb); cout<< x << endl; 6

14 Non-Member Functions Revisiting the previous problem Now things work!class Complex { public: Complex(int r, int i); ~Complex(); // this is not a member function friend Complex operator+(const int&, const Complex& ); private: int real, imag; }; Complex operator+(const int& lhs, const Complex &rhs) { Complex temp; temp.real = lhs + rhs.real; temp.imag = rhs.imag; return temp; } int main() Complex c1(2,3); Complex c2(4,5); Complex c3 = 5 + c1; // Calls operator+(5,c1) return 0; Revisiting the previous problem Now things work!

15 Why Friend Functions? Can I do the following? class Complex {error: no match for 'operator<<' in 'std::cout << c1' /usr/include/c++/4.4/ostream:108: note: candidates are: /usr/include/c++/4.4/ostream:165: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char, _Traits = std::char_traits] /usr/include/c++/4.4/ostream:169: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(long unsigned int) [with _CharT = char, _Traits = std::char_traits] /usr/include/c++/4.4/ostream:173: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(bool) [with _CharT = char, _Traits = std::char_traits] /usr/include/c++/4.4/bits/ostream.tcc:91: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short int) [with _CharT = char, _Traits = std::char_traits] class Complex { public: Complex(int r, int i); ~Complex(); Complex operator+(const Complex &rhs); private: int real, imag; }; int main() Complex c1(2,3); cout << c1; // equiv. to cout.operator<<(c1); cout << endl; return 0; }

16 Why Friend Functions? cout is an object of type ‘ostream’<< is just an operator But we call it with ‘cout’ on the LHS which would make “operator<<“ a member function of class ostream Ostream class can’t define these member functions to print out user defined classes because they haven’t been created Similarly, ostream class doesn’t have access to private members of Complex class Complex { public: Complex(int r, int i); ~Complex(); Complex operator+(const Complex &rhs); private: int real, imag; }; int main() Complex c1(2,3); cout << “c1 = “ << c1; // cout.operator<<(“c1 = “).operator<<(c1); // ostream::operator<<(char *str); // ostream::operator<<(Complex &src); cout << endl; return 0; }

17 Template for adding ostream capabilities:Ostream Overloading class Complex { public: Complex(int r, int i); ~Complex(); Complex operator+(const Complex &rhs); friend ostream& operator<<(ostream&, const Complex &c); private: int real, imag; }; ostream& operator<<(ostream &os, const Complex &c) os << c.real << “,“ << c.imag << “j”; //cout.operater<<(c.real).operator<<(“,”).operator<<... return os; } int main() Complex c1(2,3), c2(4,5); cout << c1 << c2; // operator<<(cout, c1); cout << endl; return 0; Can define operator functions as friend functions LHS is 1st arg. RHS is 2nd arg. Use friend function so LHS can be different type but still access private data Return the ostream& (i.e. os which is really cout) so you can chain calls to '<<' and because cout/os object has changed Template for adding ostream capabilities: friend ostream& operator<<(ostream &os, const T &rhs); (where T is your user defined type)

18 Summary Make the operator a member function of a class…IF the left hand side of the operator is an instance of that class The member function should only take in one argument which is the RHS object Make the operator a friend function of a class if… IF the left hand side of the operator is an instance of another class and right hand side is an instance of the class This function requires two arguments, first is the LHS object and second is the RHS object

19 Practice Add an ostream operator ('<<') to your Str class

20 Exercises For Home #include #include "listint.h" using namespace std; int main() { List m1, m2; m1.push_back(5); m2.push_back(5); if(m1 == m2){ cout << "Should print!"; } cout << "0-th item is " << m1[0]; cout << endl; m1[0] = 7; if(m1 == m2){ cout << "Should not print!"; << endl; } return 0; } Write a '[]' operator member function for you List class Have it throw an exception if the index is out of bounds Write an '==' operator to check if two lists have exactly the same contents in the exactly the same order Write a '+' operator to append one list to the end of another

21 Copy constructors and assignment operatorsCopy Semantics

22 Get the Code On your VM run the command:wget

23 d1 is implicitly passed to shuffle() Compiler-generated codethis Pointer How do member functions know which object’s data to be operating on? d1 is implicitly passed via a special pointer call the ‘this’ pointer 0x7e0 cards[52] 37 21 4 9 16 43 20 39 d2 top_index #include #include “deck.h” int main(int argc, char *argv[]) { Deck d1, d2; d1.shuffle(); d1.shuffle(); } 0x2a0 cards[52] 41 27 8 39 25 4 11 17 d1 poker.cpp top_index 1 this int main() { Deck d1; d1.shuffle(); } void Deck::shuffle(Deck *this) { this->cut(); // calls cut() // for this object for(i=0; i < 52; i++){ int r = rand() % (52-i); int temp = this->cards[r]; this->cards[r] = this->cards[i]; this->cards[i] = temp; } } d1 is implicitly passed to shuffle() #include #include “deck.h” void Deck::shuffle() { cut(); // calls cut() // for this object for(i=0; i < 52; i++){ int r = rand() % (52-i); int temp = cards[r]; cards[r] = cards[i]; cards[i] = temp; } } 0x2a0 deck.cpp deck.cpp Actual code you write Compiler-generated code

24 Another Use of 'this' class Student { public: Student(string name, int id, double gpa); ~Student(); // Destructor private: string name; int id; double gpa; }; Student::Student(string name, int id, double gpa) { // which is the member and which is the arg? name = name; id = id; gpa = gpa; } Student::Student(string name, int id, double gpa) { // Now it's clear this->name = name; this->id = id; this->gpa = gpa; } This can be used to resolve scoping issues with similar named variables

25 Struct/Class AssignmentAssigning one struct or class object to another will perform an element by element copy of the source struct/class to the destination struct/class #include using namespace std; enum {CS, CECS }; struct student { char name[80]; int id; int major; }; int main(int argc, char *argv[]) { student s1,s2; strncpy(s1.name,”Bill”,80); s1.id = 5; s1.major = CS; s2 = s1; return 0; } Memory 0x00 ‘B’ name 0x01 ‘i’ s1 0x4F 00 0x50 5 id 0x54 1 major ‘B’ name ‘i’ s2 00 5 id 1 major

26 Multiple ConstructorsCan have multiple constructors with different argument lists class Student { public: Student(); // Constructor 1 Student(string name, int id, double gpa); // Constructor 2 ~Student(); // Destructor string get_name(); int get_id(); double get_gpa(); void set_name(string name); void set_id(int id); void set_gpa(double gpa); private: string _name; int _id; double _gpa; }; Sutdent.h #include #include “student.h” int main() { Student s1; // calls Constructor 1 string myname; cin >> myname; s1.set_name(myname); s1.set_id(214952); s1.set_gpa(3.67); Student s2(myname, 32421, 4.0); // calls Constructor 2 } Student::Student() { _name = “”, _id = 0; _gpa = 2.0; } Student::Student(string name, int id, double gpa) { _name = name; _id = id; _gpa = gpa; } Student.cpp

27 Copy Constructors Write a prototype for the constructor that would want to be called by the red line of code Realm of Reasonable Answers: Complex(Complex) We will see that this can't be right… Complex(Complex &) Complex(const Complex &) We want a constructor that will build a new Complex object (c3) by making a copy of another (c1) class Complex { public: Complex(int r, int i); // What constructor definition do I // need for c3's declaration below ~Complex() private: int real, imag; }; int main() { Complex c1(2,3), c2(4,5) Complex c3(c1); }

28 Assignment & Copy ConstructorsC++ compiler automatically generates a default copy constructor Constructor called when an object is allocated and initializes the object to be a copy of another object of the same type Signature would look like Complex(const Complex &); Called by either of the options shown in the code Simply performs an element by element copy C++ compiler automatically generates a default assignment function Called when you assign to an object that is already allocated (memory already exists) Complex& operator=(const Complex &); class Complex { public: Complex(int r, int i); // compiler will provide by default: // Complex(const Complex& ); // Complex& operator=(const Complex&); ~Complex() private: int real, imag; }; int main() { Complex c1(2,3), c2(4,5) Complex c3(c1); // copy constructor Complex c4 = c1; // copy constructor c4 = c2; // default assignment oper. // c4.operator=(c2) } Class Complex int real_ int imag_ c4 c2 int real_ int real_ int imag_ int imag_

29 Assignment & Copy ConstructorsC++ compiler automatically generates a default copy constructor C++ compiler automatically generates a default assignment function See picture below of what a1 looks like as it is constructed class MyArray { public: MyArray(int d[], int num); //normal ~MyArray(); int len; int *dat; }; // Normal constructor MyArray::MyArray(int d[], int num) dat = new int[num]; len = num; for(int i=0; i < len; i++){ dat[i] = d[i]; } int main() int vals[] = {9,3,7,5}; MyArray a1(vals,4); MyArray a2(a1); // calls default copy MyArray a3 = a1; // calls default copy MyArray a4; a4 = a1; // calls default assignment // how are the contents of a2, a3, a4 // related to a1 Exercises while we code Try to modify one and see them all modified a1.set(0,11); cout << a2.get(0) << endl; printVals(a1); Need a copy constructor but before we do that… Do we need a destructor yes…add it Run it and see corruption at end Run valgrind to show memory errors/double-free Add a cout to see when destructor is getting called See a 5th call…where Okay now add copy constructor But still double-free…why? Add assignment operator Gotchas vals 1 2 3 9 3 7 5 a1.dat 0x200 a1.len 4 0x200 1 2 3 0x200 1 2 3 9 3 7 5 After 'new' After constructor

30 Assignment & Copy Constructorsvals 1 2 3 class MyArray { public: MyArray(int d[], int num); //normal ~MyArray(); int len; int *dat; }; // Normal constructor MyArray::MyArray(int d[], int num) dat = new int[num]; len = num; for(int i=0; i < len; i++){ dat[i] = d[i]; } int main() int vals[] = {9,3,7,5}; MyArray a1(vals,4); MyArray a2(a1); // calls default copy MyArray a3 = a1; // calls default copy MyArray a4; a4 = a1; // calls default assignment // how are the contents of a2, a3, a4 // related to a1 9 3 7 5 A1 a1.len 4 a1.dat 0x200 0x200 1 2 3 9 3 7 5 After constructor A2 a2.len 4 a2.dat 0x200 A3 a3.len 4 a3.dat 0x200 Default copy constructor and assignment operator make a SHALLOW COPY (data members only) rather than a DEEP copy (data members + what they point at) A4 a4.len 4 a41.dat 0x200

31 When to Write Copy ConstructorDefault copy constructor and assignment operator ONLY perform SHALLOW copies SHALLOW COPY (data members only) DEEP copy (data members + what they point at) [Like saving a webpage to your HD…it makes a shallow copy and doesn't copy the pages linked to] You SHOULD/MUST define your own copy constructor and assignment operator when a DEEP copy is needed When you have pointer data members that point to data that should be copied when a new object is made Often times if your data members are pointing to dynamically allocated data, you need a DEEP copy If a Shallow copy is acceptable, you do NOT need to define a copy constructor

32 Defining Copy Constructorsclass MyArray {public: MyArray(int d[], int num); MyArray(const MyArray& rhs); ~MyArray(); private: int *dat; int len; } // Normal constructor MyArray::MyArray(int d[], int num) { dat = new int[num]; len = num; // copy values from d to dat } // Copy constructor MyArray::MyArray(const MyArray &rhs){ len = rhs.len; dat = new int[len]; // copy from rhs.dat to dat int main() intvals[] = {9,3,7,5}; MyArray a1(vals,4); MyArray a2(a1); MyArray a3 = a1; // how are the contents of a2 and a1 related? Same name as normal constructor but should take in an argument of the object type: Usually a const reference MyArray(const MyArray&);

33 Implicit Calls to Copy ConstructorRecall pass-by-value passes a copy of an object…If defined the copy constructor will automatically be called to make this copy otherwise the default copy will perform a shallow copy class Complex { public: Complex(intr, inti); Complex Complex(const Complex &rhs); ~Complex(); int real, imag; }; // Copy constructor Complex::Complex(const Complex &c) { cout << "In copy constructor" << endl; real = c.real; imag = c.imag; } // ** Copy constructor called for pass-by-value int dummy(Complex rhs) cout << "In dummy" << endl; } intmain() Complex c1(2,3), c2(4,5); int x = dummy(c1); // ** Copy Constructor called on c1 **

34 Copy Constructors Write a prototype for the constructor that would want to be called by the red line of code Now we see why the first option can't be right…because to pass c1 by value requires a call to the copy constructor which we are just now defining (circular reference/logic) Complex(Complex) We will see that this can't be right… The argument must be passed by reference Complex(const Complex &) class Complex { public: Complex(int r, int i); Complex(Complex c); // Bad b/c pass // by value req. copy to be made // ...chicken/egg problem Complex(const Complex &c); // Good ~Complex() private: int real, imag; }; int main() { Complex c1(2,3), c2(4,5) Complex c3(c1); }

35 Practice Add a copy constructor to your Str class

36 Defining Copy Assignment Operatorclass MyArray { public: MyArray(); MyArray(int d[], int num); MyArray(const MyArray& rhs); MyArray& operator=(const MyArray& rhs); ~MyArray(); int*dat; intlen; } MyArray::MyArray(const MyArray &rhs){ len = rhs.len; dat = new int[len]; // copy from rhs.dat to dat MyArray& MyArray::operator=(const MyArray &rhs){ { len = rhs.len; dat = new int[len]; int main() intvals[] = {9,3,7,5}; MyArray a1(vals,4); MyArray a2; a2 = a1; // operator=() since a2 already exists Operator=() is called when an object already exists and then you assign to it Copy constructor called when you assign during a declaration: E.g. MyArray a2=a1; Can define operator for '=' to indicate how to make a copy via assignment Gotchas?

37 Defining Copy Assignment Operatorclass MyArray { public: MyArray(); MyArray(int d[], int num); MyArray(const MyArray& rhs); MyArray& operator=(const MyArray& rhs); ~MyArray(); int *dat; int len; } MyArray::MyArray(const MyArray &rhs){ { len = rhs.len; dat = new int[len]; // copy from rhs.dat to dat MyArray& MyArray::operator=(const MyArray &rhs){ { if(this == &rhs) return *this; if(dat) delete dat; len = rhs.len; dat = new int[len]; // copy from rhs.dat to dat return *this; int main() int vals1[] = {9,3,7,5}, vals2[] = {8,3,4,1}; MyArray a1(vals1,4); MyArray a2(vals2,4); a1 = a1; a2 = a1; Gotchas? Dest. object may already be initialized and simply overwriting data members may lead to a memory leak Self assignment (which may also lead to memory leak or lost data)

38 Assignment Operator PracticalsRHS should be a const reference Const so we don't change it Reference so we don't pass-by-value and make a copy (which would actually call a copy constructor) Return value should be a reference Allows for chained assignments Should return (*this) Reference so another copy isn't made class Complex { public: Complex(int r, int i); ~Complex() Complex operator+(Complex right_op); Complex &operator=(const Complex &rhs); private: int real, imag; }; Complex &Complex::operator=(const Complex & rhs) { real = rhs.real; imag = rhs.imag; return *this; } int main() { Complex c1(2,3), c2(4,5); Complex c3, c4; c4 = c3 = c2; // same as c4.operator=( c3.operator=(c2) ); }

39 Assignment Operator Overloadingclass Complex { public: Complex(int r, int i); ~Complex(); Complex operator+(const Complex &rhs); Complex &operator=(const Complex &r); Complex &operator=(const int r); int real, imag; }; Complex &Complex::operator=(const int& r) real = r; imag= 0; return *this; } int main() Complex c1(3,5); Complex c2,c3,c4; c2 = c3 = c4 = 5; // c2 = (c3 = (c4 = 5) ); // c4.operator=(5); // Complex::operator=(int&) // c3.operator=(c4); // Complex::operator=(Complex&) // c2.operator=(c3); // Complex::operator=(Complex&) return 0; If a different type argument can be accepted we can overload the = operator

40 Copy Constructor SummaryIf you are okay with a shallow copy, you don’t need to define a copy constructor or assignment operator Rule of Three: Usually if you have dynamically allocated memory, you’ll need a copy constructor, an assignment operator, and a destructor (i.e. if you need 1 you need all 3) Copy constructor should accept a const reference of the same object type Assignment operators should be careful to cleanup initialized members and check for self-assignment Assignment operators should return a reference type and return *this

41 Exercises Add an assignment operator to your Str classAlso add a '+=' operator to your Str class