220 likes | 244 Views
Understand the default behavior of special functions in C++ classes and learn to implement the Big Three for your custom classes. Explore deep copying and Pointer issues.
E N D
3. Big3 Yan Shi CS/SE 2630 Lecture Notes
The Big Three In C++, classes come with 3 special functions that are already written for you, known collectively as the big three. In many cases, you can accept the default behavior of these function. By default: • Destructor: calls the destructors of all members/release all primitive members. • Copy Constructor: applies copy constructors/assignments to each data member • operator=: applies assignment operator to each data member
Example: default class Cell { private: int value; public: intGet() { return value; } void Set( int x ) { value = x; } }; void main() { Cell a; a.Set(2); Cell b = a; Cell c(a); b.Set(8); c = b; a.Set(5); cout<< a.Get() << " " << b.Get() << " " << c.Get() << endl; } Default Big3!
Example: Default Big3 Cell( const Cell & c ) { value = c.value; } ~Cell(){ } Cell& operator=( const Cell &rhs ) { if ( this != & rhs ) // must check for self assignment! value = rhs.value; return *this; // return a reference to myself } thisis a pointer to the object that the member function is being called on. a = b is the same as a.operator=(b)
Pointers and the Big Three • If a class contains pointers, we may have problem with defaults!! • we must delete pointers by ourselves. • The copy constructor and operator= only do “shallow copying”: giving us two class instances with a pointer to the same object. • we expect “deep copying” • We need to implement the Big Three by ourselves!
Example: With Pointer Member class Cell { private: int *value; public: intGet() { return *value; } void Set( int x ) { *value = x; } }; void main() { Cell a; a.Set(2); Cell b = a; Cell c(a); b.Set(8); c = b; a.Set(5); cout<< a.Get() << " " << b.Get() << " " << c.Get() << endl; } Is this code correct? No! value is not initialized!
Example: With Pointer Member class Cell { private: int *value; public: Cell ( int x = 0 ) { value = new int(x); } intGet() { return *value; } void Set( int x ) { *value = x; } }; void main() { Cell a; a.Set(2); Cell b = a; Cell c(a); b.Set(8); c = b; a.Set(5); cout<< a.Get() << " " << b.Get() << " " << c.Get() << endl; } Function overloading: If there is no parameter x, assume x = 0.
Example: With Pointer Member class Cell { private: int *value; public: Cell ( int x = 0 ) { value = new int(x); } intGet() { return *value; } void Set( int x ) { *value = x; } }; void main() { Cell a; a.Set(2); Cell b = a; Cell c(a); b.Set(8); c = b; a.Set(5); cout<< a.Get() << " " << b.Get() << " " << c.Get() << endl; } // What is b’s value? // Will b and c’s values change? // Is there any memory leak?
Example: Implement Big3 Cell( const Cell & c ) { value = new int( *c.value ); } ~Cell(){ delete value; } Cell& operator=( const Cell &rhs ) { if ( this != & rhs ) // must check for self assignment! *value = *rhs.value; return *this; // return a reference to myself }
Destructor • Destructor is called when an object goes out of scope. • A class can have only one destructor without any parameters. • When there are pointer data members in a class, you should implement your own destructor to • delete all dynamically allocated memory space • other operations as needed
Operator Overloading • In C++, you can give special meanings to operators when they are used with user-defined classes. • = (assignment operator) • + - * (binary arithmetic operators) • += -= *= (compound assignment operators) • == != > < >= <= (comparison operators) • Implement operator overloads by providing special member-functions in your classes.
Assignment Operator= class MyClass { public: ... • // the = operator don’t change the rhs, only the lhs • // return a reference to allow operator chaining • // why not returning constMyClass&? • // to enable operations such as ( a = b ) = c • // "If it's good enough for ints, • // it's good enough for user-defined data-types." MyClass & operator=(constMyClass &rhs); ... } MyClass a, b, c, d; ... b = a; // Same as b.operator=(a); d = c = b = a; // assignment is right-associative. // same as d = ( c = ( b = a)));
Assignment Operator= The typical sequence of operations: MyClass & operator=(constMyClass &rhs) { //0. Self assignment check! • //1. Deallocate any memory that MyClass is using internally • //2. Allocate some memory to hold the contents of rhs • //3. Copy the values from rhs into this instance • //4. return *this; } the member function needs to return a reference to the object. So, it returns *this, which returns what thispoints at (i.e. the object). (In C++, instances are turned into references, and vice versa, because references are treated as alternative names for instances, so C++ implicitly converts *this into a reference to the current instance.)
Assignment Operator= You must check for self assignment!!! What happens if we do: MyClassobj; … obj = obj; obj will first release any memory it holds internally. This completely messes up the rest of the assignment operator's internals.
Assignment Operator = Self assignment checking: MyClass & operator=(constMyClass &rhs) { • // Check for self-assignment! • if (this != &rhs) // Same object? • { • ... // Deallocate, allocate new space, copy values... • } • return *this; • }
Compound Assignment Operators += -= *= // similar prototype as operator= MyClass & MyClass::operator+=(constMyClass &rhs) { ... // Do the compound assignment work. return *this; } MyClass a, b; ... b += a; // Same as b.operator+=(a); Compound assignment operators are destructive operators: they update or replace the values on lhs of the assignment. In general, beware of self-assignment as well.
Binary Arithmetic Operators + - * • Binary arithmetic operators don’t modify either operand, but actually return a new value from the two arguments • Use the compound operator (+=) implementation to implement binary (+) operator! // pass a const reference, return a constMyClass value constMyClassMyClass::operator+(constMyClass &other) const • { • MyClass result = *this; • result += other; • return result; • } MyClass a, b, c; ... c = a + b; // Same as c = a.operator+(b); //return MyClass(*this) += other;
Comparison Operator == and != • == and != return true or false • usually implement == first, then use == to implement != // pass a const reference, return a bool value boolMyClass::operator==(constMyClass &other) const • { // compare the values and return a bool result } • boolMyClass::operator!=(constMyClass &other) const • { return !(*this == other); } MyClass a, b; ... if ( a == b )// Same as if ( a.operator==(b) ) ...
Overloading << and >> • // return a reference for ostream to allow recursive << • // use friend keyword to access the private members of MyClass • friend ostream& operator<<(ostream& out, constMyClass & m) • { • out << ...what to print m...; • return out; • } • friend istream& operator>>(istream& in, MyClass & m) • { • // whatever code to read m: use in instead of cin • return in; • } MyClassa,b; cin >> a >> b; cout << a << b;
Friend Functions • A friend function is a function that is not a member of a class but has access to the class's private and protected members. • friendfunctions are not considered as class members; they are not in the class's scope, and they are not called using the dot notation. • A friend function is declared by the class that is granting access. • The friend declaration can be placed anywhere in the class declaration. It is not affected by the access control keywords.
Operator overloading restrictions • Cannot overload • scope resolution operator :: • member access operator ., .* • ternary conditional operator ?: • Cannot create new operators
After class exercise: • How to implement a growablearray? • Hint: define a growable array class; overload operator[] • How to overload operator++?