CpSc212 Goddard Notes Chapter 6 Yet More on Classes We discuss the problems of comparing, copying, passing, outputting, and destructing objects. 6.1 Object Storage, Allocation and Destructors Some objects are created and destroyed automatically: localvariablesinafunction or block, member variables in a object, and temporary variables during expression evaluation. In contrast, global static variables are created before main is entered. Some objects are created dynamically: thisusesthenew command and the result is usually assigned to a pointer. The user must deallocate these and release them to the system to avoid memory leaks. The delete command takes a pointer and deletes what the pointer points to. Note that an array that is new ed needs to be deallocated with delete[]. Aclassshouldhaveadestructor; this has the name of the class preceded by a tilde, and is called to properly release memory. For example, our linked list classes will need a user-supplied destructor. This frees up the nodes when the linked-list object is deleted. 6.2 Pointers and References In C++ functions, you can pass parameters by value or by address/reference. Passby-value uses a separate copy of the variable; changing this copy does not a ect the variable in the calling function. Pass by value is ine cient if the object is large. Pass-by-reference/address provides access to the original variable; changing the variable in the function does a ect the variable in the calling function. In C, pass-by-reference/address is achieved by pointers. This is still used in C++. For example, we saw that arrays are implicitly passed this way. C++ introduced the idea of references or aliases. This allows a method direct access to an object ( sharing ) but uses a di erent syntax. An ampersand & indicates an object passed by sharing; inside the function it is treated as if it were a local variable. In C++, a function can return an object or a pointer to it. However, it is also common to have pass-by-reference arguments that are changed. It is also possible to return a reference to an object. However, note that an object created with a declaration is automatically destroyed at the end of its scope; thus one gets a compiler warning if one returns a reference to a local variable. Nevertheless, there are times when return areferencecanbeused:seethecodeforthe<< operator. 14
6.3 Operator Overloading In general, the term overloading means having multiple functions with the same name (but di erent parameter lists). For example, this can be used to add the usual mathematical operators for a user-defined class. Thus, one might have the prototype for a member function: Fraction operator+(const Fraction & other) const; If the user has some code where two fractions are added, e.g. A+B, then this member function is called on A, withb as the argument. In the actual code for the function, the data members of the first fraction are accessed direct; those of the second are accessed with other. notation. 6.4 Equality Testing To allow one to test whether two objects are equal, one should provide a function that tests for equality. In C++, this is achieved by overloading the == function. The argument to the == function is a reference to another such object. class Foo int bar; bool operator== ( const Foo &other ) const return (bar == other.bar); ; Most binary operators are left-to-right associative. calling function we have It follows that when in the Foo X,Y; if( X==Y ) the boolean condition invokes X.operator==(Y) 6.5 Inputting or Outputting a Class Output of a class can be achieved by overloading the stream insertion operator <<. This is usually a separate global function (that is, not a member function). In order to access the private variables of your class, you usually need to make it a friend of your class (by adding its prototype inside the class). 15
class Foo private: int bar1,bar2; friend ostream &operator<< (ostream &, const Foo &); ; ostream &operator<< (ostream &out, const Foo &myfoo) out << myfoo.bar1 << ":" << myfoo.bar2 << endl; return out; Note that the arguments are passed by reference, and the stream itself is returned by reference (so that the operator works with successive <<). One can use the same approach to read an object from the user. Usually the user data is read into a string and then parsed internally. This is to handle malformed data without crashing. 6.6 Copying and Cloning When a class is passed by value (into or out of a function), a copy is made using the copy constructor. Often the default compiler-inserted copy constructor is fine. This provides a shallow copy only the declared variables are copied. For example, if the class contains the header pointer to a linked list, the pointer will be copied, but both the header in the new object and the old object will point to the same Node in memory. This is usually not what you want. Instead you might want a deep copy that produces a completely separate object. class Foo private: Bar *barptr; public: Foo( const Foo &other ) barptr = new Bar( *(other.barptr) ); ; Note that the code A=B uses the assignment operator. There is a fundamental trinity: either the default copy constuctor, assignment operator and destructor are 16
okay, or you need to provide all three. operator later. We will see how to create an assignment 6.7 Sample Code: Fraction.cpp We create a class called Fraction. In what follows we have first the header file Fraction.h, then the implementation file Fraction.cpp, and then a sample program that uses the class TestFraction.cpp. Note that the compiler is called by g++ Fraction.cpp TestFraction.cpp The fraction is stored in simplest form. // Fraction.h - wdg 2009 #ifndef FRACTION_H #define FRACTION_H #include <iostream> using namespace std; class Fraction public: Fraction(int whole); Fraction(int n,int d); Fraction(const Fraction & other); Fraction operator+(const Fraction & other) const; Fraction operator*(const Fraction & other) const; bool operator==(const Fraction & other ) const; friend ostream & operator<< (ostream & out, const Fraction & fraction); friend istream & operator>> (istream & in, Fraction & fraction); static const char SLASH = / ; private: int numer; int denom; void simplify( ); static int gcd(int a, int b); ; 17
#endif // Fraction.cpp - wdg 2009 #include <sstream> using namespace std; #include "Fraction.h" int Fraction::gcd(int a,int b) if(b==0) return a; else return gcd (b, a%b) ; // uses recursion Fraction::Fraction(int whole) : numer(whole), denom(1) Fraction::Fraction(int n,int d) : numer(n), denom(d) simplify( ); void Fraction::simplify( ) if(denom<0) denom=-denom; numer=-numer; int h = Fraction::gcd( numer<0?-numer:numer, denom); numer/=h; denom/=h; Fraction::Fraction(const Fraction & other) : numer(other.numer), denom(other.denom) 18
ostream & operator<< (ostream & out, const Fraction & fraction) if(fraction.denom==1) out << fraction.numer; else out << fraction.numer << Fraction::SLASH << fraction.denom; return out; istream & operator>> (istream & in, Fraction & fraction) string input; in >> input; // size_t is an unsigned integer type size_t slashpos = input.find( Fraction::SLASH ); if( slashpos == string::npos ) fraction.denom = 1; // C++11 has stoi function istringstream N(input); if(!(n >> fraction.numer ) ) fraction.numer=0; else istringstream N(input.substr(0,slashPos)); if(!(n >> fraction.numer) ) fraction.numer=0; istringstream D(input.substr(slashPos+1)); if(!(d >> fraction.denom) ) fraction.denom=1; fraction.simplify( ); return in; Fraction Fraction::operator+(const Fraction & other) const int newnumer = numer*other.denom + denom*other.numer; int newdenom = denom * other.denom; return Fraction(newNumer,newDenom); 19
Fraction Fraction::operator*(const Fraction & other) const int newnumer = numer*other.numer; int newdenom = denom * other.denom; return Fraction(newNumer,newDenom); bool Fraction::operator==(const Fraction & other) const return (numer==other.numer && denom==other.denom); #include <iostream> using namespace std; #include "Fraction.h" int main( ) Fraction F(1), G(3,-5), H(7,28), I(H); F = F+G; G = I*H; cout << F << " " << G << " " << H << " " << I << endl; cout << boolalpha << (F==I) << endl; cout << "Input fraction: "; cin >> F; cout << endl << "You inputted " << F << endl; return 0; 20