|
|
Summary
• Lecture Overview
• Memory Allocation in C
• Memory Allocation in C++
• new Operator and Classes
• Example Program 1
• Classes and Structures in C++
• new Operator and Constructors
• delete Operator and Classes
• Example Program 2
• new, delete outside Constructors and Destructors
• main() Function and Classes
• Class Abstraction
• Messages and Methods
• Classes to Extend the Language
• Tips
Lecture Overview
In the previous lectures, we have been discussing about Classes, Objects, Constructors
and Destructors. In this lecture we will take them further while discussing Memory
Allocation.
- We’ll see how the memory allocation is done in C++, while discussing memory
allocation in C?
Page 347
- How C++ style is different from the C-style of allocation discussed earlier?
- What are the advantages of C++ approach as compared to that of C?
Memory Allocation in C
Before further proceeding with the concept of memory, it is better to know what
else we
can create with classes besides objects.
Recapturing of the concept of ‘structures’ can help us to move forward. Consider
the
following statement.
struct abc
{
int integer;
float floatingpoint;
};
We could have declared a structure object as:
struct abc xyz; // Declared an object of structure type
and access data members inside structure by using dot operator (“.”) as:
xyz.integer = 2134;
xyz.floatingpoint = 234.34;
Similarly, we could have a pointer to a structure object as:
struct abc* abcPtr; // Declared a pointer of a structure type
abcPtr = xyz; // Pointer is pointing to xyz object now
We can access the individual data member as:
abcPtr->integer = 2134;
abcPtr->floatingpoint = 234.34;
We can have pointers to different data structures, similarly, pointer to a class
object. Here
we are going to discuss about Pointers, Classes and Objects.
Let’s start by talking about memory allocation. We introduced few functions of memory
allocation in C: malloc(), calloc() and
realloc(). Using these functions, memory is
allocated while the program is running. This means while writing your program or
at
compile time, you don’t need to know the size of the memory required. You can allocate
memory at runtime (dynamically) that has many benefits. The classic example will
be of
an array declared to store a string. If the length of the actual string is lesser
than the size
of the array, then the part that remains unoccupied will be wasted. Suppose we declare
an
array of length 6 to contain student name. It is alright if
the student name is let’s say
Jamil but what will happen for the student named Abdul
Razzaq. This is a case where
dynamic memory allocation is required.
Page 348
In C language, the region of memory allocated at runtime is called heap.
However, in
C++, the region of available memory is called free store. We
have different functions to
manipulate memory in both C and C++.
You know that while using malloc(), we have to tell the number
of bytes required from
memory like:
malloc(number of bytes required to be allocated);
Sometimes, we also do a little manipulation while calculating the number of bytes
required to be allocated: i.e.
malloc( 10 * ( sizeof(int) ) );
The malloc() returns a void pointer (void *).
A pointer that points to a void type of
memory. So in order to use this memory, we have to cast it to our required type.
Suppose,
we want to use it for ints. For this purpose, you will cast
this returned void pointer to int
* and then assign it to an int * before making its
further use. The following code is an
example of malloc() usage.
class Date
{
public:
Date( ) ;
Date(int month, int day, int year);
~Date ( ) ;
setMonth( int month ) ;
setDay( int day ) ;
setYear( int year ) ;
int getDay ( ) ;
int getMonth ( ) ;
int getYear ( ) ;
setDate(int day, int month, int year);
private:
int month, day, year;
};
Date *datePtr; // Declared a pointer of Date
type.
int i;
datePtr = (Date *) malloc( sizeof( Date ) ); // Used malloc() to allocate
memory
i = datePtr->getMonth(); // Returns undefined month value
So there is some house-keeping involved during the use of this function. We have
to
determine the number of bytes required to be allocated and cast the returned void
pointer
to our required type and then assign it to a variable pointer. Lastly, the memory
returned
from this function is un-initialized and it may contain garbage.
Page 349
The contrasting function used to free the allocated memory using malloc()
is free()
function. As a programmer, if you have allocated some memory using malloc(),
it is your
responsibility to free it. This responsibility of de-allocation will be there while
using C++
functions. But these new functions are far easier to use and more self-explanatory.
Memory Allocation in C++
The memory allocation in C++ is carried out with the use of an operator called
new.
Notice that new is an operator while the malloc() was a function.
Let’s see the syntax of
new operator through the following example.
new int;
In the above statement, the new operator is allocating memory for an int
and returns a
pointer of int type pointing to this region of memory. So this
operator not only allocated
required memory but also spontaneously returned a pointer of required type without
applying a cast.
In our program,
we can write it as:
int *iptr;
iptr = new int;
So while using new operator, we don’t need to supply the number
of bytes allocated.
There is no need to use the sizeof operator and cast the pointer
to the required type.
Everything is done by the new operator for us. Similarly, new
operator can be used for
other data types like char, float and
double etc.
The operator to free the allocated memory using new operator
is delete. So whenever, we
use new to allocate memory, it will be necessary to make use
of ‘delete’ to de-allocate
the allocated memory.
delete iptr;
The delete operator frees the allocated memory that is returned
back to free store for
usage ahead.
What if we want to allocate space for any array? It is very simple. Following is
the
syntax:
new data_type [number_of_locations];
For example, we want to allocate an array of 10 ints dynamically.
Then the statement
will be like this:
int *iptr;
iptr = new int[10];
What it does is, it tries to occupy memory space for 10 ints
in memory. If the memory is
occupied successfully, it returns int * that is assigned to
iptr.
Page 350
Whenever we allocate memory dynamically, it is allocated from free store. Now we
will
see what happens if the memory in the free store is not sufficient enough to fulfill
the
request. malloc() function returns NULL
pointer if the memory is not enough. In C++, 0
is returned instead of NULL pointer. Therefore, whenever we
use new to allocate
memory, it is good to check the returned value against 0 for
failure of the new operator.
Remember, new is an operator,it is not a function. Whenever
we use new, we don’t use
parenthesis with it, no number of bytes or sizeof operator
is required and no cast is
applied to convert the pointer to the required type.
delete operator is used to free the memory when the allocation is
done by using new as
shown below:
int *iptr;
iptr = new int [10]; // Memory for 10 ints is allocated dynamically.
delete iptr; // Allocated is freed and returned to the free store.
Can we apply the concept of dynamic memory allocation/deallocation while using
new/delete with classes and objects? The answer is obviously yes.
new Operator and Classes
As we declare a pointer to a primitive datatype, similarly, we can have a pointer
to a
class object.
Date *dptr; // dptr is a pointer to an object of type Date.
Now, we create the object using the
new
operator. Remember, the basic definition
of a class
i.e. it is a user-defined data type. In other words, the language has been extended
to a
programmer to have user defined data types. When we use them in our programs, these
are used in the same manner as the primitive data types.
dptr = new Date;
Process
(Program in memory)
Date class
main ( )
{
Date* = new Date();
. . .
. . .
}
BasicData class
dptr
Free Store
0
0
0
Page 351
Whatever amount of memory is required for a Date object, is allocated from the free
store. A pointer to of Date type is returned back and assigned to the
dptr pointer variable.
Is this all what new is doing? If it is so, can we use malloc()
function by providing
number of bytes required for Date object with the help of
sizeof operator. The answer to
this question lies in the further discussion.
Date mydate;
cout << sizeof (mydate);
As discussed in the last lecture, whenever we instantiate an object of a class,
the data
members are allocated for each object. However, the member functions occupy a
common region in memory for all objects of a class. Therefore, sizeof
operator returns
the size of the data-members storage excluding the member functions part. In the
above
statement, the sizeof operator returns the sum of the sizes
of three integers day, month
and year, declared in the Date class.
The amount of memory allocated in the above statement using new
(dptr = new
Date;) is same as reflected in the following statement:
dptr = (Date *) malloc( sizeof(Date) );
The new operator in the above statement ( dptr = new Date;)
has automatically
determined the size of the Date object and allocated memory
before returning a pointer of
Date * type. Is this all what new is doing? Actually, it is doing
more than this. It is also
creating an object of type Date. C functions like
malloc() do nothing for object creation.
Rather these C functions allocate the required number of bytes and return a
void *
pointing to the allocated memory where the memory might contain garbage. But the
new
operator not only allocates the memory after automatically determining the size
of the
object but also creates an object before returning a pointer of object’s class type.
Additionally, within the call to the new operator, the memory
assigned to the created
object with the use of new operator can be initialized with
meaningful values instead of
garbage (think of C functions like malloc() ).
How the data members are initialized with meaningful values? Actually, a
constructor is
called whenever an object is created. Inside the constructor, individual data members
can
be initialized. The C++ compiler generates a default constructor for a class if
the
programmer does not provide it. But the default constructor does not perform any
data
members initialization. Therefore, it is good practice that whenever you write a
class, use
a constructor function to initialize the data members to some meaningful values.
Whenever new operator is used to create an object, following
actions are performed by it:
- It automatically determines the size of the memory required to store that
object,
leaving no need for the use of sizeof operator.
- Calls the constructor of the Class, where the programmers normally write
initialization code.
Page 352
- Returns pointer of the class type that means no casting is required.
Hence, new operator is extremely useful, powerful and a good
way of allocating memory.
Let’s suppose, we want to allocate space for 10 ints as under:
int * iptr;
iptr = new int [10];
This new statement allocates contiguous space for an array
of 10 ints and returns back
pointer to the first int. Can we do this operation for objects
of a class? The answer to this
question is yes. The syntax in this case will be identical. To create an array of
10 objects
of Date type, following code is written:
Date * dptr;
dptr = new Date [10];
int day = dptr->getDay();
Here the new operator allocates memory for 10 Date
objects. It calls the default or
parameter-less constructors of the Date class and returns the
pointer to the first object,
assigned to the dptr variable. Arrow operators (->) is used
while accessing functions or
data members from the pointer variable.
Example Program 1
/* Following program demonstrates the new operator. This program has the problem
of
memory leak because delete operator is not called for the allocated memory. */
#include <iostream.h>
class MyDate
{
public: // public members are below
/* Parameterless constructor of MyDate class */
MyDate( )
{
cout << "\n Parameterless constructor called ...";
month = day = year = 0; // all data member initialized to 0
}
/* Parameterized constructor of MyDate class. It assigns the parameter values to
the
……..data members of the class */
MyDate(int month, int day, int year)
{
cout << "\n Constructor with three int parameters called ...";
Page 353
this->month = month; // Notice the use of arrow operator ( -> )
this->day = day;
this->year = year;
}
/* Destructor of the MyDate class */
~MyDate ( )
{
cout << "\n Destructor called ...";
}
/* Setter function for the month data member. It assigns the
parameter value to
the month data member */
void setMonth ( int month )
{
this->month = month;
}
/* Setter function for the day data member. It assigns the
parameter value to the
day data member */
void setDay ( int day )
{
this->day = day;
}
/* Setter function for the year data member. It assigns the
parameter value to the
year data member */
void setYear ( int year )
{
this->year = year;
}
/* Getter function for the day data member. It returns the
value of the day data
member */
int getDay ( )
{
return this->day;
}
/* Getter function for the month data member. It returns the
value of the
month data member */
int getMonth ( )
{
Page 354
return this->month;
}
/* Getter function for the year data member. It returns the
value of the year data
member */
int getYear ( )
{
return this->year;
}
/* A function to set all the attributes (data members) of the Date object */
void setDate ( int day, int month, int year )
{
this->day = day;
this->month = month;
this->year = year;
}
private: // private members are below
int month, day, year;
};
main(void)
{
MyDate *dptr; // Declared a pointer dptr to MyPointer class
object
dptr = new MyDate [10]; // Created 10 objects of MyDate and assigned the
// pointer to the first object to dptr pointer variable.
// delete should have been called here before the program terminates.
}
The output of this example program is as follows:
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Notice that the constructor is called 10 times with 10 new
calls but there is no call to
destructor. What is the reason? The objects are created with the new
operator on free
Page 355
store, they will not be destroyed and memory will not be de-allocated unless we
call
delete operator to destroy the objects and de-allocate memory. So
memory allocated on
free store is not de-allocated in this program and that results in memory leak.
There is
another point to be noted in this example program, which is not relevant to our
topics of
discussion today that all the functions are requested to be inline
automatically as the
functions are defined within the class body.
Classes and Structures in C++
Structures and classes in C++ are quite similar. C++ structure is declared with
the same
keyword struct as in C. Unlike C structure, C++ structure can
have data and member
functions. The difference between class and structure is of visibility. Every data
member
or function written inside the structure is public (visible
from outside) by default unless
declared otherwise. Similarly, everything declared inside a class is private
(not visible
from outside) by default unless declared as public.
While writing classes, good programming practice is to write private
keyword explicitly,
despite the fact that this is the default behavior. Similarly, while writing structures,
it is
good to write the public keyword explicitly. This averts confusion
and increases
readability.
Another good practice is to write public or private
keywords only once in the class or
structure declaration, though there is no syntactical or logical problem in writing
them
multiple times.
Also remember while writing a class or a structure that once a keyword is written,
say
public, the declarations falling below this keyword will be
public until the private
keyword is mentioned.
There is another keyword protected. We are not using this keyword in this course
because
that deals with inheritance that is a part of Object Oriented Programming, a separate
course.
new Operator and Constructors
It is clear that whenever new operator is called to create
an object, the constructor is also
called for that object. What will happen if we have to call new from inside a constructor
function. Can we do that? The answer is definitely yes. There are times when we
have to
do dynamic memory allocation or create new objects from inside a constructor. For
example, we have a Student class with attributes i.e.
roll number, age, height
and
Student class
{
public:
Student(char* name)
{
= new char (strlen(name)+1) ;
srcpy( this->name, name) ;
}
. . .
}
main ( )
{
. . .
}
BasicData class
Process
(Program in memory)
Free Store
J
m
a
i
l
\0
this->name
Page 356
name. The attributes like roll number,
age and height can be contained in
ints or floats
but the name attribute will require a string.
Because of the nature of this attribute (as it
can have different lengths for different students), it is better to use dynamic
memory
allocation for this. So we will use new operator from within
the constructor of Student
class to allocate memory for the name of the student.
We know whenever we use new to allocate memory, it is our responsibility
to de-allocate
the memory using the delete operator. Failing which, a
memory leak will happen.
Remember, the memory allocated from free store or
heap is a system resource and is not
returned back to the system ( even if the allocating program terminates ) unless
explicitly
freed using delete or free operators.
Now, we will see how the delete works for objects and what
is the syntax.
delete Operator and Classes
As in our Student class, as we will be allocating memory from
within the constructor of
it. Therefore, there is a need to call delete to de-allocate
memory. What is the appropriate
location inside the class Student to call delete
operator to de-allocate memory? In
normal circumstances, the location is the destructor of a class (Student
class’s destructor
in this case). The destructor is used to de-allocate memory because it is called
when the
object is no more needed or going to be destroyed from the program’s memory. So
this is
the real usefulness of destructors that these are used to release the system resources
including memory occupied by the objects.
As a thumb rule , whenever there is a pointer data member inside our class and pointer
is
being used by allocating memory at runtime. It is required to provide a destructor
for that
class to release the allocated memory. A constructor can be overloaded but not a
destructor. So there is only one destructor for a class. That one destructor of
a class must
do house keeping before the object is destroyed. Normal data members int,
char, float
and double, not allocated using malloc()
or new operator, don’t need to be de-allocated
using free() or delete. These are automatically
destroyed.
Let’s be sure that free() is used with malloc()
function while delete operator with new
operator. Normally, new will be called in a constructor. However,
delete will be called in
the destructor.
Example Program 2
/* Following program demonstrates the new and delete operators. It deallocates the
memory properly before terminating. */
#include <iostream.h>
class MyDate
Page 357
{
public: //public members are below
/* Parameterless constructor of MyDate class */
MyDate( )
{
cout << "\n Parameterless constructor called ...";
month = day = year = 0; // all data member initialized to 0
}
/* Parameterized constructor of MyDate class. It assigns the parameter values to
the
……..data members of the class */
MyDate(int month, int day, int year)
{
cout << "\n Constructor with three int parameters called ...";
this->month = month; // Notice the use of arrow operator ( -> )
this->day = day;
this->year = year;
}
/* Destructor of the MyDate class */
~MyDate ( )
{
cout << "\n Destructor called ...";
}
/* Setter function for the month data member. It assigns the
parameter value to
the month data member */
void setMonth ( int month )
{
this->month = month;
}
/* Setter function for the day data member. It assigns the
parameter value to the
day data member */
void setDay ( int day )
{
this->day = day;
}
/* Setter function for the year data member. It assigns the
parameter value to the
year data member */
void setYear ( int year )
Page 358
{
this->year = year;
}
/* Getter function for the day data member. It returns the
value of the day data
member */
int getDay ( )
{
return this->day;
}
/* Getter function for the month data member. It returns the
value of the
month data member */
int getMonth ( )
{
return this->month;
}
/* Getter function for the year data member. It returns the
value of the year data
member */
int getYear ( )
{
return this->year;
}
/* A function to set all the attributes (data members) of the Date object */
void setDate ( int day, int month, int year )
{
this->day = day;
this->month = month;
this->year = year;
}
private: // private members are below
int month, day, year;
};
main(void)
{
MyDate *dptr; // Declared a pointer dptr to MyPointer class
object
dptr = new MyDate [10]; // Created 10 objects of MyDate and assigned the
// pointer to the first object to dptr pointer variable.
delete [] dptr; // Deleted (freed) the assigned memory to the objects
}
Page 359
The output of this example program is as follows:
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Parameterless constructor called ...
Destructor called ...
Destructor called ...
Destructor called ...
Destructor called ...
Destructor called ...
Destructor called ...
Destructor called ...
Destructor called ...
Destructor called ...
Destructor called ...
It is very clear from the output that the destructor for all the objects is called
to avert any
memory leak. The memory allocated using new operator is being de-allocated using
the
delete operator. Notice the syntax of delete while de-allocating
an array, the brackets ([])
precedes the name of the array after the delete operator.
new, delete outside Constructors and Destructors
Can new be called from some location other than constructor?
The answer is yes and we
usually need to do that. Suppose, we have an object of Student
class. The name of the
student is: Abdul Khaliq. So for the name
attribute, the space is allocated dynamically to
store the string Abdul Khaliq. When our program is running
and we have already
allocated space for the Abdul Khaliq string using the
new operator, after sometime, we
are required to increase the size of the string. Let’s say we want to change the
string to
Abdul Khaliq Khan now.
So what we can do, without destroying this student object:
De-allocate the name previously occupied string using the
delete operator, determine the
size of memory required with the help of strlen() function,
allocate the memory required
for the new string Abdul Khaliq Khan using the new
operator and finally assign the
returned pointer to the name data member.
Hence, we can call new and delete operators,
not only outside the class to create objects
but also within the class. The objects of the same class can have different sizes
of
Page 360
memory space like in case of objects of Student class, student
1 object can be of one size
and student 2 object can be of an another size, primarily varying because of string
name.
But independent of this object size, the destructor of the object remains the same
and deallocates
memory for different objects regardless of their different sizes. delete
operator
is used from within the destructor to deallocate the memory. We call delete
operator to
determine the size of the memory required to be de-allocated and only provide it
a pointer
pointing to it.
Please note that C functions like malloc() and free()
functions can also be used from
within C++ code. But while writing classes inside C++ code, we prefer to use
new and
delete operators as they are designed to work with classes and objects.
main() Function and Classes
We used to discuss about main() function a lot while writing our programs in C.
You
might have noticed that while discussing about classes and objects, we are not talking
about the main() function. This does not mean that
main() function is not there in C++. It
is there but it does not contain as much code in C++ . But as you go along and write
your
own classess, you will realize that almost 90% of your program’s code lies inside
the
class definitions. So firstly we write our classes and main()
function is written after
classes have been defined. That is why the main() function
is very small. Our example
programs clearly depict this fact.
Class Abstraction
Whenever we write a class, we think about its users. Who are the ones going to use
this
class? The users are not only the main() function of the program
but also our colleagues
around us. Remember, we only expose interface to our users and not the class
implementation. All what users need to know is provided in the interface, the methods
signatures and what can be achieved by calling that method. The users do not need
to
know how the functions or interfaces are implemented, what are the variables, how
is the
data inside and how is it being manipulated, it is abstract to the users.
Messages and Methods
When we create an object, we ask that object to do something by calling a function.
This
way of asking objects in Windows operating system is called Messaging or in other
words function calling is sending a message to the object. Sending a message is
a
synonym of calling a method of an object. The word ‘method’ is from the fact that
it is a
way of doing something. So the whole program is sending messages and getting
responses back. It is a different way of looking at things.
Page 361
Notice lot of things have been repeated in this lecture many times, the reason is
that now,
you are required to think differently, more in terms of classes and objects. There
are lots
of exciting things coming up to be covered later.
Classes to Extend the Language
We know that in C, there is no data type for complex numbers. Therefore, we needed
to
define our own class for complex numbers. We might use double
data type for real and
imaginary parts. From basic Mathematics, we also know that whenever
two complex
numbers are added, real part of one complex number is added into the real part of
other
complex number and imaginary part of one complex number is added into the imaginary
part of other complex number. We might write a function for this operation and might
call this as cadd(). We might also write other functions for
multiplication and division. In
C++, the operators like ‘+’, ‘*’ and ‘/’ can be overloaded, therefore, we could
overload
these operators for complex numbers, so that we could easily use these ordinary
addition,
multiplication, and division operators for complex numbers. Actually, we don’t need
to
write this class on our own because this is already been provided in many C++ libraries.
Remember, there is no primitive data type in C++ for complex numbers but a class
has
been written as part of the many C++ libraries. Moral of the above paragraph is;
by using
user defined data types i.e., classes, we can now really extend the language.
Tips
- Classes are one way of extending the C++ language.
- Whenever new operator is used, no number of bytes
or sizeof operator is required and
no cast is applied to convert the pointer to the required type.
- Whenever new operator is called to create an object,
the constructor is also called for
that object. It is a good practice that whenever you write a class, use a constructor
function to initialize the data members to some meaningful values.
- The usual practice is to use constructor to allocate memory or system resources
and
destructors to de-allocate or return the resources back to the system.
- In C language, the region of memory allocated at runtime is called
heap. However, in
C++, the region of available memory is called free store. There
are different
functions in C and C++ to manipulate memory at runtime. However, all C functions
are useable in C++ code.
- The memory allocated from free store or
heap is a system resource and is not
returned back to the system unless explicitly freed using delete
or free operators.
Page 362
- If the memory in the free store is not sufficient enough to fulfill the
request, malloc()
function returns NULL pointer. Similarly, the new function
returns 0 in case the
request could not be fulfilled.
- Whenever we use new operator, the returned value from
the new should be checked
against 0 for any possible failures.
- While writing classes, good programming practice is to write
private keyword
explicitly, despite the fact that this is the default scope. Additionally, the good
practice is to write public or private
keywords only once in the class or structure
definitions, though there is no syntactical or logical problems in writing them
multiple times. |
|
|
|