687286748 -1- Chapter 5 Pointers and Dynamic Memory Assignment and Initialization The miniVector Class The Matrix Class 687286748 -2- Pointers and Dynamic Memory The memory allocation operator new - C++ uses the operator new to allocate memory for data during program execution. If sufficient memory is not available the operator returns 0 (NULL). The operator new requires a data type T as a parameter, memory for the variable of type T is allocated, and the address of the newly created memory is returned. T *p; p = new T; int *ptr1; // size of int is 2 - address of an int is assigned to ptr1 long *ptr2; // size of long is 4 - address if a long is assigned to ptr2 ptr1 = new int; // pointer1 points to an integer ptr2 = new long; // pointer2 points to a long The contents of the allocated memory has no initial value. Initial value can be supplied as a parameter; P = new T ( value ) ; ptr2 = new long(100000); Dynamic Array Allocation p = new T [n] ; // allocates an array of n items of type T long *p; p = new long [5] ; // Allocates an array of 5 longs if ( p == NULL ) { cerr << “ Memory allocation error! << endl; exit (1); } The Memory Deallocation Operator delete long *p; p = new long [50 ]; delete [ ] p; // deallocates 50 long integers int *p = new(10); delete p; // deallocates one integer 687286748 -3- Dynamically Allocated Objects Essential actions of member functions in handling dynamically allocated objects: #include <iostream.h> template <class T> class DynamicClass { private: // variable of type T and a pointer to data of type T T member1; T *member2; public: // constructors DynamicClass ( const T& m1, const T& m2 ); DynamicClass ( const DynamicClass<T>& obj); // destructor ~DynamicClass ( void ); // assignment operator DynamicClass<T>& operator= ( const DynamicClass<T>& rhs ); }; // constructor with parameters to initialize member data template <class T> DynamicClass<T>::DynamicClass(const T& m1, const T& m2 ) { member1 = m1; member2 = new T (m2 ); cout << “constructor: “ << member1 << ‘/’ << *member2 << endl; } 687286748 -4- Dynamically Allocated Objects Ex: DynamicClass<int> staticObj ( 1, 100 ); // DynamicClass object DynamicClass<int> *dynamicObj; // pointer variable dynamicObj = new DynamicClass<int> ( 2, 200);// allocate object // and init data // using constructor Deallocating Object Data: the Destructor The destructor is called whenever the object is deleted - when a program terminates ( for global objects ) or when a program exits a block ( for local objects) If the destructor does not call delete the object may be destroyed but the memory is not freed up. template <class T> DynamicClass<T> : : ~DynamicClass(void) { cout << “Destructor: “ << member1 << ‘/’ << *member2 << endl; delete member2; // deallocates memory } 687286748 -5- More than one dynamic object #include <iostream.h> #include “dynamic.h” void DestroyDemo( int m1, int m2 ) { DynamicClass<int> obj( m1, m2 ); // upon return Objects are // destroyed memory is not // deallocated without a // destructor } void main ( void ) { DynamicClass<int> Obj_1 ( 1, 100 ); DynamicClass<int> *Obj_2; // create automatic object // Declare Pointer to an object // Allocate dynamic object and initialize members Obj_2 = new DynamicClass<int> ( 2, 200 ); // Call function DestroyDemo passing parameters DestroyDemo ( 3, 300 ); // Explicitly delete Obj2_2 delete Obj_2; cout << ready to exit program.” << endl; } /* Run of program */ Constructor: 1/100 Constructor: 2/200 Constructor: 3/300 Destructor: 3/300 Destructor: 2/200 Ready to exit program. Destructor: 1/100 687286748 -6- Assignment and Initialization Assignment IssuesDynamicClass<int> objA( 1,2 ), objB(3,4); Before Assignment: objA Member1=1 objB Member2 Member1=3 Member2 4 2 Heap memory *objA.member2 =2 Heap memory *objB.member2 = 4 objA = objB ; // causes a byte by byte assignment of object B to object A After Assignment: objA member1=1 objB member2 2 Unreferenced Heap memory member1=3 member2 4 Heap memory - now referenced by two objects because pointers are assigned - and both objects member2 pointer variable points to the same memory location. When returning from the function - 687286748 -7- deallocating memory by the destructor could be a fatal. To solve the problem what is required is an overloaded assignment operator. After Assignment using overloaded assignment operator The assignemt objA = objB; objA member1=1 objB member2 member1=1 4 member2 4 Heap memory Heap memory Copy value *objA.member3 to *objB.member2 The Overloading the Assignment Operator properly handles object Assignment. Syntax for overloaded assignment operator metnod: DynamicClass<T>& operator= ( const DynamicClass<T>& rhs ); A = B; // implemented as A.operator = (B) The overloaded operator explicitly assigns all data including private and public data members as well as the data pointed to by these members. The right hand side is passed as a reference variable - ( const - prevents alterations ) 687286748 -8- Implementation: template <class T> DynamicClass<T>& DynamicClass<T> ::operator= ( const DynamicClass<T>& rhs ) { // copy static data member from rhs to the current object member1 = rhs.member1; *member2 = *rhs.member2; cout << “Assignment Operator: “ << member 1 << ‘/’ cout *member2 << endl; return *this; // returns reference to current object - allows // chaining of two or more assignment statements // C = B = A; assignment right to left } The This pointer - Each C++ object has a pointer named this - the identifier this is a reserved word and can only be used inside a class member function. - this point to the current object, *this is the object itself ( this->member1, the data value of member1 ) or (*this).member1 The assignment operator returns *this. The return value is a reference argument – this allows for chaining= For example: A=B=C; //is assigned from right to left 687286748 -9- Initialization Issues: Object initialization creates a new object that is a copy of another object. DynamicClass<int> objA ( 3, 5 ) , objB = objA; // Instantiate objB and // Initialize object objB // with object objA Initialization also occurs when: - passing an object as a value parameter - when returning an object as a value of a function to properly handle dynamic memory C++ provides a copy constructor. Creating a copy constructor DynamicClass ( const DynamicClass<T>& X ); // Declaration template <class T> DynamicClass<T>:: DynamicClass ( const DynamicClass<T>& obj ) { member1 = obj.member1; // copy static member // allocate dynamic memory and initialize it member2 = new T (*obj.member2 ); cout << “ Copy Constructor: “ << member1 << ‘/’ << *member2 << endl; } 687286748 - 10 - Using DynamicClass #include <iostream.h> #include “dynamic.h” template <class T> DynamicClass<int> Demo ( DynamicClass<T> one, DynamicClass<T>& two, T m ) { DynamicClass<T> obj (m, m); // pass by value // passed by ref // pass by value // calls constructor // member1 = m, *member2 = m return obj ; // a copy of obj is made and returned // ALL local (stack) variables are destroyed } void main (void ) { DynamicClass<int> A ( 3, 5 ), B = A, C ( 0, 0 ); C = Demo ( A, B, 5 ); // assignment operator // all remaining objects are destroyed upon program exit } /* Run of program */ Constructor: 3/5 Copy constructor:3/5 Constructor : 0/0 Copy Constructor: 3/5 Constructor: 5/5 Copy constructor: 5/5 Destructor: 5/5 Destructor: 3/5 Assignment Operator: 5/5 Destructor: 5/5 Destructor: 5/5 Destructor: 3/5 Destructor: 3/5 // A(3,5) // B = A // C (0,0) // Demo ( A , // in member function Demo obj(5,5) // upon return from Demo // Delete dynamic data // Delete dynamic data // C = Demo (...) assignment operator // Destructor on exit // Destructor on exit // Destructor on exit // Destructor on exit 687286748 - 11 - Overloading the Index Operator T& operator[] (int i); // provides general access to elements using an index. // Precondition: 0 <= i < vSize. if the index is out // of range, throws the indexRangeError exception // provides general access to array elements. constant version template <typename T> const T& miniVector<T>::operator[] (int i) const { if (i < 0 || i >= vSize) throw indexRangeError( "miniVector: index range error", i, vSize); return vArr[i]; } 687286748 - 12 - // File: prg5_3.cpp // the program illustrates the use of exceptions with the miniVector // class. during execution, the underflowError and indexRangeError // exceptions each occur once #include <iostream> #include "d_vector.h" using namespace std; int main() { miniVector<int> v; // try block attempts to erase from empty vector; catch block // catches underflowError exception from pop_back() try { v.pop_back(); } catch (const underflowError& e) { cout << e.what() << endl; // store element in v[0] v.push_back(99); } cout << "The size of v = " << v.size() << endl; // try block enables index bound checking ; catch block catches // indexRangeError exception from operator[] try { cout << "v[0] = " << v[0] << endl; cout << "v[1] = " << v[1] << endl; } catch (const indexRangeError& e) { cout << e.what() << endl; } return 0; } /* Run: miniVector pop_back(): vector is empty The size of v = 1 v[0] = 99 miniVector: index range error index 1 size = 1 */ 687286748 - 13 - Part of Exception Class #ifndef EXCEPTION_CLASSES #define EXCEPTION_CLASSES #include <strstream> #include <string> using namespace std; class baseException { public: baseException(const string& str = ""): msgString(str) { if (msgString == "") msgString = "Unspecified exception"; } string what() const { return msgString; } // protected allows a derived class to access msgString. // chapter 13 discusses protected in detail protected: string msgString; }; ....... // index out of range class indexRangeError: public baseException { public: indexRangeError(const string& msg, int i, int size): baseException() { char indexString[80]; ostrstream indexErr(indexString, 80); indexErr << msg << " index " << i << " size = " << size << ends; // indexRangeError can modify msgString, since it is in // the protected section of baseException msgString = indexString; } }; // attempt to erase from an empty container class underflowError: public baseException { public: underflowError(const string& msg = ""): baseException(msg) {} }; . . . . . . . }; #endif // EXCEPTION_CLASSES 687286748 - 14 - Class miniVector #ifndef MINI_VECTOR #define MINI_VECTOR #include "d_except.h" // include exception classes using namespace std; template <typename T> class miniVector { public: miniVector(int size = 0); // constructor. // Postconditions: allocates array with size number of elements // and capacity. elements are initialized to T(), the default // value for type T miniVector(const miniVector<T>& obj); // copy constructor // Postcondition: creates current vector as a copy of obj ~miniVector(); // destructor // Postcondition: the dynamic array is destroyed miniVector& operator= (const miniVector<T>& rhs); // assignment operator. // Postcondition: current vector holds the same data // as rhs T& back(); // return the element at the rear of the vector. // Precondition: the vector is not empty. if vector // is empty, throws the underflowError exception const T& back() const; // const version used when miniVector object is a constant T& operator[] (int i); // provides general access to elements using an index. // Precondition: 0 <= i < vSize. if the index is out // of range, throws the indexRangeError exception const T& operator[] (int i) const; // const version used when miniVector object is a constant void push_back(const T& item); // insert item at the rear of the vector. // Postcondition: the vector size is increased by 1 687286748 - 15 - Class miniVector (continued) void pop_back(); // remove element at the rear of the vector. // Precondition: vector is not empty. if the vector is // empty, throws the underflowError exception int size() const; // return current list size bool empty() const; // return true if vector is empty and false otherwise int capacity() const; // return the current capacity of the vector private: int vCapacity; int vSize; T *vArr; // amount of available space // number of elements in the list // the dynamic array void reserve(int n, bool copy); // called by public functions only if n > vCapacity. expands // the vector capacity to n elements, copies the existing // elements to the new space if copy == true, and deletes // the old dynamic array. throws the memoryAllocationError // exception if memory allocation fails }; 687286748 - 16 - Implementation miniVector // set the capacity to n elements template <typename T> void miniVector<T>::reserve(int n, bool copy) { T *newArr; int i; // allocate a new dynamic array with n elements newArr = new T[n]; if (newArr == NULL) throw memoryAllocationError( "miniVector reserve(): memory allocation failure"); // if copy is true, copy elements from the old list to the new list if (copy) for(i = 0; i < vSize; i++) newArr[i] = vArr[i]; // delete original dynamic array. if vArr is NULL, the vector was // originally empty and there is no memory to delete if (vArr != NULL) delete [] vArr; // set vArr to the value newArr. update vCapacity vArr = newArr; vCapacity = n; } // constructor. initialize vSize and vCapacity. // allocate a dynamic array of vSize integers // and initialize the array with T() template <typename T> miniVector<T>::miniVector(int size): vSize(0), vCapacity(0), vArr(NULL) { int i; // if size is 0, vSize/vCapacity are 0 and vArr is NULL. // just return if (size == 0) return; // set capacity to size. since we are building the vector, // copy is false reserve(size, false); // assign size to vSize vSize = size; // copy T() into each vector element for (i=0;i < vSize;i++) vArr[i] = T(); } 687286748 - 17 - Implementation miniVector (continued) // copy constructor. make the current object a copy of obj. // for starters, use initialization list to create an empty vector template <typename T> miniVector<T>::miniVector (const miniVector<T>& obj): vSize(0), vCapacity(0), vArr(NULL) { int i; // if size is 0, vSize/vCapacity are 0 and vArr is NULL. // just return if (obj.vSize == 0) return; // set capacity to obj.vSize. since we are building the vector, // copy is false reserve(obj.vSize, false); // assign size to obj.vSize vSize = obj.vSize; // copy items from the obj.vArr to the newly allocated array for (i = 0; i < vSize; i++) vArr[i] = obj.vArr[i]; } // destructor. deallocate the dynamic array template <typename T> miniVector<T>::~miniVector() { if (vArr != NULL) // de-allocate memory for the array delete [] vArr; } // replace existing object (left-hand operand) by rhs (right-hand operand) template <typename T> miniVector<T>& miniVector<T>::operator= (const miniVector<T>& rhs) { int i; // check vCapacity to see if a new array must be allocated if (vCapacity < rhs.vSize) // make capacity of current object the size of rhs. don't // do a copy, since we will replace the old values reserve(rhs.vSize, false); // assign current object to have same size as rhs vSize = rhs.vSize; // copy items from rhs.vArr to vArr for (i = 0; i < vSize; i++) vArr[i] = rhs.vArr[i]; return *this; } 687286748 - 18 - Implementation miniVector (continued) // check vSize and throw an underflowError exception if the // value is 0; otherwise return the element vArr[vSize-1] template <typename T> T& miniVector<T>::back() { if (vSize == 0) throw underflowError( "miniVector back(): vector empty"); return vArr[vSize-1]; } template <typename T> const T& miniVector<T>::back() const { if (vSize == 0) throw underflowError( "miniVector back(): vector empty"); return vArr[vSize-1]; } // provides general access to array elements template <typename T> T& miniVector<T>::operator[] (int i) { if (i < 0 || i >= vSize) throw indexRangeError( "miniVector: index range error", i, vSize); return vArr[i]; } // provides general access to array elements. constant version template <typename T> const T& miniVector<T>::operator[] (int i) const { if (i < 0 || i >= vSize) throw indexRangeError( "miniVector: index range error", i, vSize); return vArr[i]; } 687286748 - 19 - Implementation miniVector (continued ) // insure that list has sufficient capacity, add the new item to the list, and increment vSize template <typename T> void miniVector<T>::push_back(const T& item) { // if space is full, allocate more capacity if (vSize == vCapacity) { if (vCapacity == 0) // if capacity is 0, set capacity to 1. // set copy to false because there are // no existing elements reserve(1,false); else // double the capacity reserve(2 * vCapacity, true); } // add item to the list, update vSize vArr[vSize] = item; vSize++; } // if not empty, just decrement the size template <typename T> void miniVector<T>::pop_back() { if (vSize == 0) throw underflowError( "miniVector pop_back(): vector is empty"); vSize--; } template <typename T> int miniVector<T>::size() const { return vSize; } template <typename T> bool miniVector<T>::empty() const { return vSize == 0; } template <typename T> int miniVector<T>:: capacity() const { return vCapacity; } #endif // MINI_VECTOR