ZeePedia

Dynamic Arrays of Objects, Overloading new and delete Operators

<< Assignment Operator, Self Assignmentm, Pointer, Conversions
Source and Destination of streams, Formatted Input and Output, Buffered Input/Output >>
img
CS201 ­ Introduction to Programming
Lecture Handout
Introduction to Programming
Lecture No. 34
Reading Material
Deitel & Deitel - C++ How to Program
Chapter 8
Summary
·
Arrays of Objects
·
Dynamic Arrays of Objects
·
Overloading new and delete Operators
·
Example of Overloading new and delete as Non-members
·
Example of Overloading new and delete as Members
·
Overloading [] Operator to Create Arrays
Arrays of Objects
A class is a user-defined data type. Objects are instances of classes the way int variables
are instances of ints. Previously, we have worked with arrays of ints. Now, we are going
to work with arrays of objects.
The declaration of arrays of user-defined data types is identical to the array of primitive
data types.
Following is a snapshot of our veteran Date class:
/* Snapshot of Date class discussed in previous lectures */
class Date
{
private:
int day, month, year;
public:
/* Parameterless constructor, it is created by the compiler automatically when we
don't write it for any of our class. */
Page 432
img
CS201 ­ Introduction to Programming
Date( )
{
cout << "\n Parameterless constructor called ...";
month = day = year = 0;
}
/* Parameterized constructor; has three ints as parameters. */
Date(int month, int day, int year)
{
cout << "\n Constructor with three int parameters called ...";
this->month = month;
this->day = day;
this->year = year;
}
~Date ( )
{
cout << "\n Destructor called ...";
}
...
...
};
Consider the example of declaring an array of 10 date objects of Date class. In this case,
the declaration of arrays will be as under:
Following is the declaration:
Date myDates [10] ;
With this line (when this line is executed), we are creating 10 new objects of Date class.
We know that a constructor is called whenever an object is created. For every object like
myDate[0], myDate[1],.... myDate[9], the constructor of the Date class is called.
Theimportant thing to know here is that which constructor of Date class is being called to
construct objects of the array myDates. As we are not doing any initialization of the array
objects explicitly, the default constructor (parameterless constructor) of the Date class is
called. Remember, the default constructor is defined by the C++ compiler automatically
for every class that has no parameterless constructor defined already. In our case of Date
class, we have defined a parameterless constructor, therefore, the compiler will not
generate default constructor automatically.
We can also initialize the array elements at the declaration time. This initialization is
similar to that done for native data types. For int array, we used to do initialization in the
following manner:
int  array [10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } ;
Similarly, we initialize Date array while declaring it:
Page 433
img
CS201 ­ Introduction to Programming
Date yourDate [3] = { Date(10, 24, 1980), Date(06, 14, 1985), Date(07, 09,1986) };
The above statement will call the parameterized constructor Date (int month, int day, int
year) of Date class to create three objects of myDate array. This parameterized
constructor carries out initialization of the objects data member (month, day, year) with
the values supplied as arguments to it.
It will be interesting to know, how the following statement works:
Date myDate [10] = { Date(09, 03, 1970), Date(08, 23, 1974) } ;
We are trying to declare an array of 10 Date objects while supplying only initialization
values for the first two elements. At first, we might be doubtful if the statement is
compiled successfully. Not only it compiles successfully but also does the initialization
of the first two objects ( myDate[0], myDate[1] ). What will happen to the remaining
objects in the array? Actually, all the 10 objects are created successfully by the above
statement. The parameterized constructor is called for the first two objects ( myDate[0],
myDate[1] ) and parameterless constructor is called for the remaining objects
(myDate[2], myDate[3], ..., myDate[9]).
You might have noticed that at the array initialization stage, we have explicitly called
parameterized constructor of Date for every object. We may specify only the argument
when a constructor with only one parameter is called.
/* A snapshot of String class discussed in previous lectures */
class String
{
private :
char *buf ;
public:
// Constructors
String ();
String( const char *s )
{
buf = new char [ 30 ];
strcpy (buf,s);
}
...
...
};
Page 434
img
CS201 ­ Introduction to Programming
For example, in the above-mentioned case of String class, we have a constructor that is
accepting one argument of type char *. While writing our code, we can declare and
initialize an array of Strings as follows:
String message [10] = {
"First line of message\n",
"Second line of message\n",
String( "Third line of message\n"),
String( )
};
See the initializing arguments for first two objects i.e, (message[0], message[1]) in the
array. Here only one string is being passed. Therefore, for the first two objects,
constructor with one parameter of type char * of String class is called automatically. That
constructor is String ( char *
str ). For the third object (message[2]), the same
constructor with one char * as parameter is being called explicitly. For fourth object
(message[3]), parameterless constructor i.e., String ( ) is being called explicitly, though,
this was optional as parameterless constructor is called up automatically when no
initialization is made. As there is no explicit initialization for the remaining six objects,
the parameterless constructor is called up automatically.
Can we create arrays of objects dynamically? As usual, the answer is yes. Let's discuss it
in detail.
Dynamic Arrays of Objects
Consider the following statement:
1.
String *text ;
2.
text = new String [5] ;
In line 1, we have declared a pointer text of String type.
In line 2, we are creating an array of 5 objects of String type. This statement allocates
space for each object of the array, calls the parameterless constructor for each object and
starting address of the first object is assigned to the pointer text.
The important point to be noted here is that in line 2, we can't initialize objects because
there is no way to provide initializers for the elements of an array allocated with new.
The default constructor (parameterless constructor) is called for each element in the array
allocated with new. Remember, the default constructor for a class is generated by C++
compiler automatically if it is not defined already in the class definition.
To deallocate these arrays of objects, the delete operator is used in the same way as it is
used for the native data types.
There are few cautions that should be taken care of while performing these operations of
allocation and deallocation with arrays of objects.
Firstly, while deallocating an array allocated with new operator, it is important to tell the
compiler that an array of objects is being deleted. The brackets ( [] ) are written in our
delete statement after the delete keyword to inform the delete operator that it is going to
Page 435
img
CS201 ­ Introduction to Programming
delete an array. The consequences of using the wrong syntax are serious. For example, if
we want to delete previously created array of five String objects using the following
statement:
delete text;  // Incorrect syntax of deleting an array
The delete operator in this case will not be aware of deleting (deallocating) an array of
objects. This statement will call the destructor only for the object pointed by the text
pointer i.e. String[0] and deallocate the space allocated to this object. The requirement is
to call the destructor for all the objects inside the array and deallocate the space allocated
to all of these objects. But on account of the wrong syntax, only the first object is deleted
and the remaining four objects ( String[1], String[2], String[3], String[4] pointed by
text[1], text[2], text[3], text[4] respectively ) remain in the memory intact. The memory
space occupied by these four objects results in memory leak as the same program or any
other program on the same computer cannot use it unless it is deallocated.
Calling the destructor while destroying an object becomes essential when we have
allocated some memory in free store from inside the object (usually from within the
constructor).
To destroy an array of objects allocated on free store using the new operator, an array
equivalent of delete operator is used. The array equivalent of delete operator is to write
empty square brackets after the delete keyword (delete [] ). So the correct statement is:
delete [] text ;
This statement destroys the whole array properly. It calls destructor for each object inside
the array and deallocates the space allotted to each object. Actually, by looking at the
brackets ( [] ) after delete, the compiler generates code to determine the size of the array
at runtime and deallocate the whole array properly. Here, it will generate code to
deallocate an array of 5 objects of String type.
If we create an array of Date objects and want to delete them without specifying array
operator: It will look as under:
// Bad Technique: deleting an array of objects without []
// for a class that is not doing dynamic memory allocation internally
Date * ppointments;
appointments = new Date[10];
...
delete appointments; // Same as delete [] appointments;
Although, this is good to deallocate an array of objects without specifying array operator
([]) as there is no dynamic memory allocation occurring from inside the Date class. But
this is a bad practice. In future, the implementation of this class may change. It may
contain some dynamic memory allocation code. So it is always safer to use array operator
( [] ) to delete arrays.
Can we overload new and delete operators? Yes, it is possible to overload new and delete
operators to customize memory management. These operators can be overloaded in
global (non-member) scope and in class scope as member operators.
Page 436
img
CS201 ­ Introduction to Programming
Overloading of new and delete Operators
Firstly, we should know what happens when we use new operator to create objects. The
memory space is allocated for the object and then its constructor is called. Similarly,
when we use delete operator with our objects, the destructor is called for the object before
deallocating the storage to the object.
When we overload new or delete operators, it can only lead to a change in the allocation
and deallocation part. The call to the constructor after allocating memory while using new
operator and call to the destructor before deallocating memory while using delete
operator will be there. These calls to constructors and destructors are controlled by the
language itself and these cannot be altered by the programmer.
One of the reasons of overloading new and delete operators can be their limited current
functionality. For example, we allocate space on free store using the new operator for
1000 ints. It will fail and return 0 if there is no contiguous space for 1000 ints in free
store. Rather the free store has become fragmented. The total available memory is much
more than 1000 ints , but in fragments. There is no contiguous segment of at least 1000
ints. The built-in new operator will fail to get the required space but we can overload our
own new operator to de-fragment the memory to get at least 1000 ints space. Similarly,
we can overload delete operator to deallocate memory.
In the embedded and real-time systems, a program may have to run for a very long time
with restricted resources. Such a system may also require that memory allocation always
takes the same amount of time. There is no allowance for heap exhaustion or
fragmentation. A custom memory allocator is the solution. Otherwise, programmers will
avoid using new and delete altogether in such cases and miss out on a valuable C++ asset.
There are also downsides of this overloading. If we overload new or delete operator at
global level, the corresponding built-in new or delete operator will not be visible to whole
of the program. Instead our globally written overloaded operator takes over its place all
over. Every call to new operator will use our provided new operator's implementation.
Even in the implementation of new operator, we cannot use the built-in new operator.
Nonetheless,when we overload new operator at a class level then this implementation of
new operator will be visible to only objects of this class. For all other types (excluding
this class) will still use the built-in new operator. For example, if we overload new
operator for our class Date then whenever we use new with Date, our overloaded
implementation is called.
Date* datePtr = new Date;
This statement will cause to call our overloaded new operator. However, when we use
new with any other type anywhere in our program as under:
int* intPtr = new int [10];
The built-in new operator is called. Therefore, it is safer to overload new and delete
operators for specific types instead of overloading it globally.
Page 437
img
CS201 ­ Introduction to Programming
An important point to consider while overloading new operator is the return value when
the new operator fails to fulfill the request. Whether the operator function will return 0 or
throw an exception.
Following are the prototypes of the new and delete operators:
void * operator new ( size_t size ) ;
void operator delete ( void * ptr) ;
The new operator returns a void * besides accepting a parameter of whole numbers size_t.
This prototype will remain as it is while overloading new operator. In the implementation
of overloaded new operator, we may use calloc( ) or malloc( ) for memory allocation and
write some memory block's initialization code.
The delete operator returns nothing (void) and accepts a pointer of void * to the memory
block. So the same pointer that is returned by the new operator, is passed as an argument
to the delete operator. Remember, these rules apply to both if operators (new and delete)
are overloaded as member or non-member operators (as global operators). Importantly,
whenever we use these operators with classes, we must know their sequence of events
that is always there with these operators. For new operator, memory block is allocated
first before calling the constructor. For delete operator, destructor for the object is called
first and then the memory block is deallocated. Importantly, our overloaded operators of
new and delete only takes the part of allocation and deallocation respectively and calls to
constructors and destructors remain intact in the same sequence.
Because of this sequence of events, the behavior of these new and delete operators is
different from the built-in operators of new and delete. The overloaded new operator
returns void * when it is overloaded as non-member (global). However, it returns an
object pointer like the built-in new operator, when overloaded as a member function.
It is important to understand that these operator functions behave like static functions
when overloaded as member functions despite not being declared with static keyword.
static functions can access only the static data members that are available to the class
even before an object is created. As we already know that new operator is called to
construct objects, it has to be available before the object is constructed. Similarly, the
delete operator is called when the object has already been destructed by calling destructor
of the object.
Example of Overloading new and delete as Non-members
Suppose we want new to initialize the contents of a memory block to zero before
returning it. We can achieve this by writing the operator functions as follows:
/* The following program explains the customized new and delete operators */
#include <iostream.h>
#include <stdlib.h>
#include <stddef.h>
Page 438
img
CS201 ­ Introduction to Programming
// ------------- Overloaded new operator
void * operator new ( size_t size )
{
void * rtn = calloc( 1, size ); // Calling calloc() to allocate and initialize memory
return rtn;
}
// ----------- Overloaded delete operator
void operator delete ( void * ptr )
{
free( ptr ); // Calling free() to deallocate memory
}
void main()
{
// Allocate a zero-filled array
int *ip = new int[10];
// Display the array
for ( int i = 0; i < 10; i ++ )
cout << " " << ip[i];
// Release the memory
delete [] ip;
}
The output of the program is as follows.
0000000000
Note that the new operator takes a parameter of type size_t. This parameter holds the size
of the object being allocated, and the compiler automatically sets its value whenever we
use new. Also note that the new operator returns a void pointer. Any new operator we
write must have this parameter and return type.
In this particular example, new calls the standard C function calloc to allocate memory
and initialize it to zero.
The delete operator takes a void pointer as a parameter. This parameter points to the
block to be deallocated. Also note that the delete operator has a void return type. Any
delete operator we write, must have this parameter and return type.
In this example, delete simply calls the standard C function free to deallocate the
memory.
Example of Overloading new and delete as Members
// Class-specific new and delete operators
Page 439
img
CS201 ­ Introduction to Programming
#include <iostream.h>
#include <string.h>
#include <stddef.h>
const int MAXNAMES = 100;
class Name
{
public:
Name( const char *s ) { strncpy( name, s, 25 ); }
void display() const { cout << '\n' << name; }
void * operator  new ( size_t size );
void operator delete( void * ptr );
~Name() {};  // do-nothing destructor
private:
char name[25];
};
// -------- Simple memory pool to handle fixed number of Names
char pool[MAXNAMES] [sizeof( Name )];
int inuse[MAXNAMES];
// -------- Overloaded new operator for the Name class
void * Name :: operator new( size_t size )
{
for( int p = 0; p < MAXNAMES; p++ )
if( !inuse[p] )
{
inuse[p] = 1;
return pool + p;
}
return 0;
}
// --------- Overloaded delete operator for the Names class
void Name :: operator delete( void *ptr )
{
inuse[((char *)ptr - pool[0]) / sizeof( Name )] = 0;
}
void main()
{
Name * directory[MAXNAMES];
char name[25];
for( int i = 0; i < MAXNAMES; i++ )
{
Page 440
img
CS201 ­ Introduction to Programming
cout << "Enter name # " << i+1 << ": ";
cin >> name;
directory[i] = new Name( name );
}
for( i = 0; i < MAXNAMES; i++ )
{
directory[i]->display();
delete directory[i];
}
}
The output of the above program is given below.
Enter name # 1: ahmed
Enter name # 2: ali
Enter name # 3: jamil
Enter name # 4: huzaifa
Enter name # 5: arshad
Enter name # 6: umar
Enter name # 7: saleem
Enter name # 8: kamran
Enter name # 9: babar
Enter name # 10: wasim
ahmed
ali
jamil
huzaifa
arshad
umar
saleem
kamran
babar
wasim
This program declares a global array called pool that can store all the Name objects
expected. There is also an associated integer array called inuse, which contains true/false
flags that indicate whether the corresponding entry in the pool is in use.
When the statement directory[i] = new Name( name ) is executed, the compiler calls
the class's new operator. The new operator finds an unused entry in pool, marks it as used,
and returns its address. Then the compiler calls Name's constructor, which uses that
memory and initializes it with a character string. Finally, a pointer to the resulting object
is assigned to an entry in directory.
When the statement delete directory[i] is executed, the compiler calls Name 's destructor.
In this example, the destructor does nothing; it is defined only as a placeholder. Then the
Page 441
img
CS201 ­ Introduction to Programming
compiler calls the class's delete operator. The delete operator finds the specified object's
location in the array and marks it as unused, so the space is available for subsequent
allocations.
Note that new is called before the constructor, and that delete is called after the
destructor.
Overloading [ ] Operator to Create Arrays
We know that if we overload operators new and delete for a class, those overloaded
operators are called whenever we create an object of that class. However, when we create
an array of those class objects, the global operator new( ) is called to allocate enough
storage for the array all at once, and the global operator delete( ) is called to release that
storage.
We can control the allocation of arrays of objects by overloading the special array
versions of operator new[ ] and operator delete[ ] for the class.
Previously, while employing global new operator to create an array of objects, we used to
tell the delete operator by using the array operator( [] ) to deallocate memory for an
array. But it is our responsibility to provide or to overload different type of new and
different type of delete.
There is a common problem when working with arrays. While traversing elements from
the array, we might run off the end of the array. This problem might not be caught by the
compiler. However, some latest compilers might be able to detect this.
int iarray [10] ;
for ( int i = 0; i < 100; i++ )
{
// Some code to manipulate array elements
}
If a variable is used in the condition of the loop instead of the constant value, the
probability of that error increases. Variable might have an entirely different value than
that anticipated by the programmer.
We can overcome this problem of array bound by overloading array operator `[]'. As
usual before overloading, we should be clear about the functionality or semantics of the
array operator. We use array operator to access an element of array. For example, when
we write iarray[5], we are accessing the 6th element inside array iarray. As we want to
check for validity of index every time, an array element is accessed. We can do this by
declaring the size of the array using #define and checking the index against the size every
time the array is accessed.
#define MAXNUM 1000
int iarray [MAXNUM];
Page 442
img
CS201 ­ Introduction to Programming
Below is the syntax of declaration line of overloaded array operator:
int& operator [] ( int index ) ;
In the body of this operator, we can check whether the index is greater or equal to the
MAXNUM constant. If this is the case, the function may throw an exception. At the
moment, the function only displays an error message. If index is less than MAXNUM and
greater than or equal to zero, a reference to the value at the index location is returned.
Let's write a class IntArray and see the array manipulation.
/*
The following example defines the IntArray class, where each object contains
an array of integers. This class overloads the [] operator to perform
range checking.
*/
#include <iostream.h>
#include <string.h>
class IntArray
{
public:
IntArray( int len );
int getLength( ) const;
int & operator[] ( int index );
~IntArray( );
private:
int length;
int *aray;
};
// ------------ Constructor
IntArray :: IntArray( int len )
{
if( len > 0 )
{
length = len;
aray = new int[len];
// initialize contents of array to zero
memset( aray, 0, sizeof( int ) * len );
}
else
{
length = 0;
aray = 0;
}
Page 443
img
CS201 ­ Introduction to Programming
}
// ------------ Function to return length
inline int IntArray :: getLength() const
{
return length;
}
// ------------ Overloaded subscript operator
// Returns a reference
int & IntArray :: operator []( int index )
{
static int dummy = 0;
if( (index = 0) &&
(index < length) )
return aray[index];
else
{
cout << "Error: index out of range.\n";
return dummy;
}
}
// ------------ Destructor
IntArray :: ~IntArray()
{
delete aray;
}
void main()
{
IntArray numbers( 10 );
int i;
for( i = 0; i < 10; i ++ )
numbers[i] = i;
// Use numbers[i] as lvalue
for( i = 0; i < 10; i++ )
cout << numbers[i] << '\n';
}
This program first declares numbers of type IntArray object that can hold ten integers.
Later, it assigns a value to each element in the array. Note that the array expression
appears on the left side of the assignment. This is legal as the operator[] function returns
a reference to an integer. This means the expression numbers[i] acts as an alias for an
element in the private array and it can be the recipient of an assignment statement. In this
situation, returning a reference is not simply more efficient but also necessary.
Page 444
img
CS201 ­ Introduction to Programming
The operator[] function checks whether the specified index value is within range or not.
If it is within the range, the function returns a reference to the corresponding element in
the private array. If it is not, the function prints out an error message and returns a
reference to a static integer. This prevents out-of-range array references from overwriting
other regions of memory while causing unexpected program behavior.
Tips
·
The default constructor is defined by the C++ compiler automatically for every
class that has no default constructor (parameterless constructor) defined already.
·
The default constructor (parameterless constructor) is called for each element in
the array allocated with new.
·
The new operator returns a void *, accepts a parameter of type size_t.
·
The delete operator returns nothing (void) and accepts a pointer of void * to the
memory block.
·
With new operator function, a block of memory is allocated first and then
constructor is called.
·
With delete operator, destructor of the object is called first and then memory
block is deallocated.
·
By overloading new and delete operators, only allocation and deallocation part
can be overridden.
·
The same pointer that is returned by the new operator, is passed as an argument to
the delete operator. These rules apply to both, if operators (new and delete) are
overloaded as member or non-member operators (as global operators).
·
By overloading the array operator ( [] ), one can implement mechanism to check
for array bound.
Page 445
Table of Contents:
  1. What is programming
  2. System Software, Application Software, C language
  3. C language: Variables, Data Types, Arithmetic Operators, Precedence of Operators
  4. C++: Examples of Expressions, Use of Operators
  5. Flow Charting, if/else structure, Logical Operators
  6. Repetition Structure (Loop), Overflow Condition, Infinite Loop, Properties of While loop, Flow Chart
  7. Do-While Statement, for Statement, Increment/decrement Operators
  8. Switch Statement, Break Statement, Continue Statement, Rules for structured Programming/Flow Charting
  9. Functions in C: Structure of a Function, Declaration and Definition of a Function
  10. Header Files, Scope of Identifiers, Functions, Call by Value, Call by Reference
  11. Arrays: Initialization of Arrays, Copying Arrays, Linear Search
  12. Character Arrays: Arrays Comparisonm, Sorting Arrays Searching arrays, Functions arrays, Multidimensional Arrays
  13. Array Manipulation, Real World Problem and Design Recipe
  14. Pointers: Declaration of Pointers, Bubble Sort Example, Pointers and Call By Reference
  15. Introduction, Relationship between Pointers and Arrays, Pointer Expressions and Arithmetic, Pointers Comparison, Pointer, String and Arrays
  16. Multi-dimensional Arrays, Pointers to Pointers, Command-line Arguments
  17. String Handling, String Manipulation Functions, Character Handling Functions, String Conversion Functions
  18. Files: Text File Handling, Output File Handling
  19. Sequential Access Files, Random Access Files, Setting the Position in a File, seekg() and tellg() Functions
  20. Structures, Declaration of a Structure, Initializing Structures, Functions and structures, Arrays of structures, sizeof operator
  21. Bit Manipulation Operators, AND Operator, OR Operator, Exclusive OR Operator, NOT Operator Bit Flags Masking Unsigned Integers
  22. Bitwise Manipulation and Assignment Operator, Programming Constructs
  23. Pre-processor, include directive, define directive, Other Preprocessor Directives, Macros
  24. Dynamic Memory Allocation, calloc, malloc, realloc Function, Dangling Pointers
  25. History of C/C++, Structured Programming, Default Function Arguments
  26. Classes and Objects, Structure of a class, Constructor
  27. Classes And Objects, Types of Constructors, Utility Functions, Destructors
  28. Memory Allocation in C++, Operator and Classes, Structures, Function in C++,
  29. Declaration of Friend Functions, Friend Classes
  30. Difference Between References and Pointers, Dangling References
  31. Operator Overloading, Non-member Operator Functions
  32. Overloading Minus Operator, Operators with Date Class, Unary Operators
  33. Assignment Operator, Self Assignmentm, Pointer, Conversions
  34. Dynamic Arrays of Objects, Overloading new and delete Operators
  35. Source and Destination of streams, Formatted Input and Output, Buffered Input/Output
  36. Stream Manipulations, Manipulators, Non Parameterized Manipulators, Formatting Manipulation
  37. Overloading Insertion and Extraction Operators
  38. User Defined Manipulator, Static keyword, Static Objects
  39. Pointers, References, Call by Value, Call by Reference, Dynamic Memory Allocation
  40. Advantages of Objects as Class Members, Structures as Class Members
  41. Overloading Template Functions, Template Functions and Objects
  42. Class Templates and Nontype Parameters, Templates and Static Members
  43. Matrices, Design Recipe, Problem Analysis, Design Issues and Class Interface
  44. Matrix Constructor, Matrix Class, Utility Functions of Matrix, Input, Transpose Function
  45. Operator Functions: Assignment, Addition, Plus-equal, Overloaded Plus, Minus, Multiplication, Insertion and Extraction