Dynamic Memory Allocation in Classes
If you haven't seen the basics of dynamic memory allocation before, then
see this page:
Recap of DMA Basics
- Remember that memory allocation comes in two varieties:
- Static (compile time): Sizes and types of memory (including
arrays) must be known at compile time, allocated space given
variable names, etc.
- Dynamic (run-time): Memory allocated at run time. Exact
sizes (like the size of an array) can be variable. Dynamic memory
doesn't have a name (names known by compiler), so pointers used to
link to this memory
- Allocate dynamic space with operator new, which returns
address of the allocated item. Store in a pointer:
int * ptr = new int; // one dynamic integer
double * nums = new double[size]; // array of doubles, called "nums"
- Clean up memory with operator delete. Apply to the pointer.
Use delete [] form for arrays:
delete ptr; // deallocates the integer allocated above
delete [] nums; // deallocates the double array above
- Remember that to access a single dynamic item, dereference is needed:
cout << ptr; // prints the pointer contents
cout << *ptr; // prints the target
- For a dynamically created array, the pointer attaches to the starting
position of the array, so can act as the array name:
nums[5] = 10.6;
cout << nums[3];
Dynamic Allocation of Objects
- Just like basic types, objects can be allocated dynamically, as
well.
- But remember, when an object is created, the constructor runs. Default
constructor is invoked unless parameters are added:
Fraction * fp1, * fp2, * flist;
fp1 = new Fraction; // uses default constructor
fp2 = new Fraction(3,5); // uses constructor with two parameters
flist = new Fraction[20]; // dynamic array of 20 Fraction objects
// default constructor used on each
- Deallocation with delete works the same as for basic types:
delete fp1;
delete fp2;
delete [] flist;
Notation: dot-operator vs. arrow-operator
- dot-operator requires an object name (or effective name) on the
left side
objectName.memberName // member can be data or function
- The arrow operator works similarly as with structures.
pointerToObject->memberName
- Remember that if you have a pointer to an object, the pointer name
would have to be dereferenced first, to use the dot-operator:
(*fp1).Show();
- Arrow operator is a nice shortcut, avoiding the use or parintheses to
force order of operations:
fp1->Show(); // equivalent to (*fp1).Show();
- When using dynamic allocation of objects, we use pointers, both to
single object and to arrays of objects. Here's a good rule of thumb:
For pointers to single objects, arrow operator is easiest:
fp1->Show();
fp2->GetNumerator();
fp2->Input();
For dynamically allocated arrays of objects, the pointer acts as the array
name, but the object "names" can be reached with the bracket operator.
Arrow operator usually not needed:
flist[3].Show();
flist[5].GetNumerator();
// note that this would be INCORRECT, flist[2] is an object, not a pointer
flist[2]->Show();
Using dynamic allocation inside classes
A motivating example
Suppose we want an array as member data of a class, but we don't want to
be stuck with a fixed upper bound on the size. How do we accomplish this?
The solution would be to use dynamic allocation on the array. But how
to arrange it? Can a dynamic array be physically embedded inside a class?
What if an object of that class type is created statically? Then the
compiler has to know the size. But the internal contents are to be
dynamic?!
Solution: Only the pointer will be in the member data
section. Dynamic memory will not be physically in the object, but only
linked by the member data pointer.
Setting it up, using good design principles!
- To set up dynamic memory as contents of an object, declare one or more
pointers as member data
- Always initialize pointers in the constructor!
- Constructor might go ahead and dynamically allocate space right away,
assigning addresses to pointers.
- If not allocating space right away, best to initialize to null
pointer until ready for use
- Use new inside class member functions to allocate space,
attaching space to pointers (could be in constructors, or other member
functions)
- Make sure to use delete to clean up dynamically allocated
space whenever finished using it
- This could happen in regular member functions, wherever space is
cleared
- This should happen in the destructor, because this is
guaranteed to be the last function that runs for an
object!
- Suggestion: Separate memory management tasks from the
functionality/algorithmic tasks wherever possible:
- Write a set of member functions just for dealing with memory
management issues -- like creation of space, deallocation, resizing,
etc
- Your algorithmic functions can call the memory-handling
functions, when needed
- The more uses of new and delete there are in a
class, the more complicated it gets, and the more chances of a memory
leak. Separation of tasks helps minimize this.
Application Example: Dynamically resizing an array
(This was discussed in the pre-requisite course. See COP 3014 notes for
full details. Just a summary is listed here)
To change the size of a dynamically allocated array (perhaps to add
space), you cannot just append the next consecutive slots. Must find free
space for entire array, in one consecutive set of memory. Summary of the
basic process:
- Dynamically create a new array of desired size. (This step will
require a second pointer for temporary use).
- Copy the old array's contents into the new one
- Deallocate the memory of the old array (avoid memory leak)
- Adjust pointers so that the new array has the desired name
This process is used in the following code example.
The above link is to an example that involves two classes and uses dynamic
memory allocation. The classes are Entry and Directory.
Entry -- An object of this class type represents a single entry
in a phone book. The data members stored in an entry object are name,
address, and phone number. Strings (i.e. null-terminated character
arrays) are used to store these items.
Directory -- An object of type Directory stores a list of Entry
objects, using a dynamic array. The Directory class also provides
services (public member functions) for adding new entries, deleting entries,
modifying entries, searching for entries, and displaying all entries in
the phone book. The Directory class also has a function for dynamically
resizing the array of Entries when more memory space is needed.
Note that in this class, the destructor is also implemented for
the Directory class, with a needed definition inside. Since the member
data of an object of type Directory includes a pointer, which is being
used to point to dynamically allocated space (i.e. the array of entries),
it is our job in the code to deallocate that space. When the
object is deallocated, the compiler only automatically gives up the space
inside the object. The pointer entryList is pointing to data that
is physically outside the object, so it doesn't get automatically
"cleaned up". But, we can clean up this space (before the object goes
away) by doing it in the last function that runs for an object (which is
always the destructor). Note that the definition of this destructor is:
delete [] entryList;
This simply deallocates the dynamic array attached to entryList, before we
let this pointer be deallocated along with the object.
This one contains overloads of operator<< and
operator>> in class Entry, instead of Show()
and Load()