Com S 228 meeting -*- Outline -*- * dynamic data (HR 7.5-8, DD 7.6) ** what it is and why it's used (HR 7.5) Recall the first slide of the introduction, which describes the problem. ----------------------------- POINTERS AND DYNAMIC DATA (HR 7) Problem: Amount of input data varies between uses Some solutions: - Allocate the maximum amount - Ask user how much to allocate - Allocate space at run-time (dynamically) ----------------------------- allocating the max may lead to poor virtual memory performance, (page faults) Our solution involves allocating space at run-time pointers are used to name this space ------------------------- DYNAMIC STORAGE ALLOCATION AND DEALLOCATION OVERVIEW In Scheme: (let ((ls ; allocate (cons 1 '()))) ; compute ... ls ...) ; storage deallocated when not used In C++: // allocate List *ls = new Cons(1, EmptyList()); // compute ... *ls ... // deallocate delete ls; -------------------------- in Pascal this is like new and free, in C this is like malloc and free Note the extra burden of doing your own storage management, introduces new possibilities for errors... ** operator new ------------------------------ OPERATOR new Syntax examples: int *ip = new int; char *cp = new char; int *ia = new int [i+20]; char *s = new char[size]; IntVec *ivp = new IntVec(i+20); FlexIntVec *fia = new FlexIntVec [size2]; Semantics: returns a pointer to new cell (or array of new cells) "new" means no other pointers to it if not enough space, returns null pointer (0). the cell is unitialized, unless the type is a class, then the constructor is called ------------------------------ there is also a constant amount of space overhead to remember size, which is used in deallocation. ----------------------------- PROBLEM Implement the following spec. // strdup.h #include extern char* strdup(const char *s); // PRE: s in null-terminated // MODIFIES: cerr // POST: if space can't be allocated // prints an error to cerr // and returns with FCTVAL == 0, // otherwise return a pointer to new // space containg a copy of s. ----------------------------- // strdup.C #include "strdup.h" #include char* strdup(const char *s) { char *t = new char[strlen(s)+1]; if (t == NULL) { cerr << "out of space" << endl; } else { strcpy(t,s); } return (t); } Note: there are better ways to handle printing error messages from new. To avoid forgetting it, you can do something like the following... or in main() (See Effective C++ by Meyers, points 7-9.) // strdup.C #include "strdup.h" #include #include void NoMoreMemory() { cerr << "out of space" << endl; abort(); } typedef void (*PEHF)(); // the type of NoMoreMemory char* strdup(const char *s) { // give error if new can't find space PEHF old_new_handler = set_new_handler(&NoMoreMemory); char *t = new char[strlen(s)+1]; set_new_handler(old_new_handler); // restore old handler strcpy(t,s); return (t); } Of course, someone has to remember to delete this space... ** operator delete ---------------------------- OPERATOR delete Syntax examples: delete ip; delete cp; delete [] ia; delete [] s; delete ivp; delete [] fia; Semantics: space pointed at is returned to C++ argument must be: either 0 or result of previous call to new if argument is 0, nothing happens. form with [] must be used for arrays form without [] must be used otherwise class objects have destructor called the object pointed to may be modified ---------------------------- Watch out: don't delete same pointer twice ------------------------------- PROBLEM Implement the following specification: // Deallocate.h extern void Deallocate(int* &intPtr); // PRE: value of intPtr was returned // a call to new, and hasn't yet been // given to delete // MODIFIES: free store, intPtr // *intPtr is deallocated && intPtr == 0 ------------------------------- Explain the type of the arg... // Deallocate.C #include "Deallocate.h" void Deallocate(int* &intPtr) { delete intPtr; intPtr = 0; } ** errors (HR 7.7) *** dangling pointers ----------------------------------- DANGLING POINTERS (1) // deleting an alias int main() { int *intPtrA = new int; *intPtrA = 77; int *intPtrB = intPtrA; delete intPtrA; // ... cout << *intPtrB << endl; // error! } ---------------------------------- There's no easy way to get around this if you create aliases... The trouble is, the problem won't usually be so obvious... ---------------------------------- DANGLING POINTERS (2) struct complex {int re; int im}; complex* cmul(complex *a, complex *b) { complex result; result.re = (*a).re * (*b).re; result.im = a->im * b->im; return &result; } ----------------------------------- Q: can you make the same error with refrerences? you bet! ----------------------------------- // fix: complex* cmul(complex *a, complex *b) { complex *result = new complex; result->re = a->re * b->re; result->im = a->im * b->im; return &result; } ---------------------------------- of course this may also cause a problem... *** memory leaks ------------------------------------ MEMORY LEAKS Complex & operator *(const Complex & a, const Complex & b) { Complex *result = new Complex(a.real() * b.real(), a.imag() * b.imag()); return *result; } // ... int main() { Complex x, y; cout << x * y * z << endl; // ... } ------------------------------------ Q: Who deletes the objects created by operator *? In a program that isn't intended to run long, ok to not worry about this (memory returned when finishes). But need to for general purpose classes and for long-running programs *** guidelines (p. 320) -------------------------------- THINGS TO BE AWARE OF WHEN USING DYNAMIC DATA Is this pointer valid? - if variable not initialized, no? - if it's null (0), then no? Is there memory allocated at the end? - after passing it to delete, no. - before a call to new or assignment, no Is this going to cause a memory leak? - overwriting the last pointer to object - returning from a function without returning pointer to allocated objects, or without deleting them Could this make other pointers invalid? - deleting one of several aliases ---------------------------------- good luck! Most of these are easy to goof up. ** classes with dynamic memory (HR 7.8) Do something like vector module on p. 321 ---------------------------------- PROBLEM Design and implement a type that is like array, but more flexible and safer. Want: - size determined at run-time - trap invalid subscripts - aggregate assignment - aggregate copying (pass by value) ---------------------------------- Look at spec of IntVec in$PUB/examples/IntVec/vector.h go over constructor Q: how can the dynamically-allocated data be deleted? *** destructors (HR 7.8) ------------------------------------------ DESTRUCTOR OVERVIEW (HR 7.8) Destructor for class IntVec - is a member function nasmed ~IntVec() - called implicitly ------------------------------------------ ... just before space is deallocated ------------------------------------------ - job is to delete ------------------------------------------ ... any storage allocated by constructor so it's opposite of constructor ------------------------------------------ - *not* responsible for deleting self Constructor and destructor example: // IntVec.pri private: int *vec; int size; // IntVec.C // ... IntVec::IntVec( int num ) : vec(new int[num]), size(num) { } IntVec::~IntVec() { delete [] vec; } ------------------------------------------ draw picture before and after invoking destructor note object itself still there ------------------------------------------ WHEN DESTRUCTOR INVOKED At block exit, for variables int& Dangle(int n) { IntVec alpha(n); // ... return alpha[n-1]; } // call Dangle(7) = 3; ------------------------------------------ destructor called at return, because alpha is automatic storage draw picture of call would be ok, if didn't try to return ref. ------------------------------------------ Also: - as first part of deleting dynamic data IntVec * ivPtr = new IntVec(20); // ... delete ivPtr; - other times objects deallocated ------------------------------------------ temporaries going away, deallocation of data members of class type... ------------------------------------------ DESTRUCTOR SUMMARY - no parameters, no return type - called when variable goes out of scope - needed only to deallocate storage allocated by constructor WARNINGS - need a copy constructor! - don't call exit function, as may loop ------------------------------------------ because the exit makes stuff be destroyed *** assignment operators (HR pp. 326-7) ------------------------------------------ PROBLEM DEFAULT (C++-DEFINED) ASSIGNMENT vs. DATA MEMBERS THAT ARE POINTER VARS // kaboom.C #include #include "IntVec.h" int main() { IntVec myVec(4); IntVec yourVec(3); for (int i = 0; i < 3; i++) { myVec[i] = 1; yourVec[i] = myVec[i] + 10; } myVec[3] = 3; yourVec = myVec; // with built-in = cout << yourVec[0] << endl; } ------------------------------------------ draw picture of this!!! this is a "shallow copy", always dangereous if one of the data members is a pointer; only copys the pointer variables, not the pointed-to data ------------------------------------------ SOLUTION OVERLOAD THE ASSIGNMENT OPERATOR ------------------------------------------ whenever you use dynamic data! ------------------------------------------ IntVec myVec(4); IntVec theirVec(4); Before: myVec = theirVec; After: ------------------------------------------ draw pictures (like Fig 7.23) Implement the correct assignment operator, then show what the kaboom looks like with it. *** copy constructors (pp. 328-330) ------------------------------------------ PROBLEM DEFAULT (C++-DEFINED) COPY CONSTRUCTOR vs. DATA MEMBERS THAT ARE POINTER VARS IN CLASS WITH A DESTRUCTOR // kaboom2.C #include #include "IntVec.h" IntVec incAll(IntVec iv) { for (int i = 0; i < 4; i++) { iv[i] += i; } return iv; } int main() { IntVec myVec(4); for (int i = 0; i < 4; i++) { myVec[i] = 1; } IntVec yourVec = incAll(myVec); cout << yourVec[0]; } ------------------------------------------ If use default copy constructor, get a shallow copy when: pass parameter by value, return result by value, initialize in a declaration so this shares memory between iv and myVec draw pictures, show that: incAll actually modifies myVec, even though looks like copy when identity returns, iv is deleted, so destructor for IntVec called, which deletes shared space so myVec becomes undefined... ------------------------------------------ SEQUENCE OF EVENTS In main before call: activation frame for incAll(myVec): When returning: ------------------------------------------ again the problem is the shallow copy ------------------------------------------ DESIRED SEQUENCE OF EVENTS In main before call: activation frame for incAll(myVec): When returning: ------------------------------------------ Implement the correct copy constructor. ------------------------------------------ COPY CONSTRUCTORS IntVec::IntVec( const IntVec & another ) ------------------------------------------ note that the parameter is passed by const refrence also no return value specified: this is a constructor! ------------------------------------------ Used by C++ implicitly during: 1. initializations in declarations: IntVec yourVec = myVec; 2. passing parameters by value IntVec incAll(IntVec iv) { // ... return iv; } incAll(myVec); 3. returning results by value return iv; ------------------------------------------ Q: Why isn't the parameter to the copy constructor passed by value? *** safety guidelines, summary (HR pp. 330-1) ------------------------------------------ GUIDELINES FOR SAFE USE OF DYNAMIC DATA IN C++ CLASSES To prevent unwanted indirect aliasing and dangling refrences: - always overload ------------------------------------------ ... the assignment operator and the copy constructor, to make deep copies prevents sharing of dynamically allocated storage ------------------------------------------ To prevent memory leaks: - always overload ------------------------------------------ ... the destructor to delete the space allocated the above two ensure that each object has at most one reference ------------------------------------------ Never use destructor unless you use a copy constructor ------------------------------------------ ------------------------------------------ A SAFE CLASS OUTLINE WITH DYNAMIC DATA // MyCls.h #include "Foo.h" class MyCls { public: MyCls(); ~MyCls(); MyCls& operator =(const MyCls& oth); MyCls(const MyCls & oth); // ... #include "MyCls.pri" }; // MyCls.pri private: Foo* data; // MyCls.C #include "MyCls.h" MyCls::MyCls() : data(new Foo()) {} ~MyCls::MyCls() { delete data; } MyCls& MyCls::operator =(const MyCls& oth) { *data = *(oth.data); } MyCls::MyCls(const MyCls & oth) : data(new Foo(*oth.data)) {} ------------------------------------------ the assignment operator uses Foo's assignment operator the copy constructor uses Foo's copy constructor *** other alternatives (may omit) Q: What happens if just leave off destructor? memory leaks Q: In this case, can omit deep copies. But what happens then? indirect aliasing and larger problem of memory leakage Q: What was Scheme like? had no destructor, no copying either ------------------------------------------ OTHER SAFE ALTERNATIVES Prohibit pass by value and assignment: - client has to use references ------------------------------------------ or pointers ------------------------------------------ - need to declare the copy constructor and the assignment operator, but ------------------------------------------ ... declare them as private! ------------------------------------------ - destructor does *not* dealloctate - use a garbage collector ------------------------------------------ it is possible to get garbage collection programs for C++, but these have some small memory leakage ------------------------------------------ Reference counting: - copy constructor, assignment operator don't make deep copies but increment a reference count - destructor decrements reference count, only deletes storage when the reference count reaches 0. - has indirect aliasing ------------------------------------------ emphasize that the previous techniques are safer. ** summary and further examples take a final look at the IntVec example look at the IntFlexVec example, and do it's implementation