DAY – 5
Consider
the encapsulation done for the dealer. There I have created two objects : john
and smith. As I told you yesterday, the moment I create an object, the first step
which is going to happen internally is memory allocation. A block of memory corresponding
to the attributes will be allocated to you. Now since it is allocated in the
stack, we can say that the initial value to which it is initialized is garbage.
Since it is initialized to garbage I can say that the object john or smith are
in undefined state. So here if ever we forget to invoke the Add_Details() function, we may end up
doing garbage manipulation.
So
if ever our object was capable of invoking a function from our class
automatically which can help us in initialization, it will be always better. Such
a facility is also provided in C++ and it is done using something known as
constructors.
Constructors
are special member functions which helps us in initialization of the objects.
Why it is called as special member functions is, it gets invoked implicitly at
the time of creation of objects. Constructor will always take the name of the
class as the function name and this helps the compiler to distinguish between
constructors and other functions. Even though it is a function, a constructor
doesn’t have any return type not even void. Just like any other function,
constructors will also have argument list. Based on arguments, constructors can
be classified into two as shown below.
Constructors
1. Zero
argument constructor
2. Constructors
with arguments
Based on the type of the
arguments, constructors with arguments can be further classified as follows.
Constructor
with arguments
1. Parameterized
constructors
2. Copy
constructors
Lets try to modify our dealer program
with constructors. We will just see default / zero argument constructor and
parameterized constructor now. We will talk about copy constructor once we are
done with our dynamic memory allocation.
//Header file for Dealer in CDS
#include <iostream>
using namespace std;
class Dealer {
public:
Dealer() {
cout <<
"Enter your name" << endl;
cin >> Name;
No_Cars_Sold = 0;
Price = 200000;
Comm_Rate = 0.16;
Comm_Per_Car = 0.0;
Total_Comm = 0.0;
}
Dealer(char *n) {
strcpy(Name, n);
No_Cars_Sold = 0;
Price = 200000;
Comm_Rate = 0.16;
Comm_Per_Car = 0.0;
Total_Comm = 0.0;
}
void Sell_Car();
void Change_Comm_Rate(float);
float Report_Comm();
private:
char Name[20];
int No_Cars_Sold;
int Price;
float Comm_Rate;
float Comm_Per_Car;
float Total_Comm;
};
//dealer_library.cpp
#include
"dealer_header.h"
void Dealer :: Sell_Car() {
No_Cars_Sold += 1;
Comm_Per_Car = Price * Comm_Rate;
Total_Comm += Comm_Per_Car;
}
void Dealer ::
Change_Comm_Rate(float Comm_Rate)
{
this -> Comm_Rate = Comm_Rate;
}
float Dealer ::
Report_Comm() {
return Total_Comm;
}
//dealer_application.cpp
#include
"dealer_header.h"
int main() {
Dealer John;
John.Sell_Car();
cout << John.Report_Comm()
<< endl;
Dealer Smith("Stephen
Smith");
Smith.Change_Comm_Rate(0.20);
Smith.Sell_Car();
cout << Smith.Report_Comm()
<< endl;
return 0;
}
Lets
start from the first step. When an object is created, first a block of memory
is allocated for the object equivalent to the size of the class. Construction
of the object is not over by this step. After memory allocation it invokes the
constructor for initialization of the memory. Which constructor is invoked is
purely based on whether you are passing arguments along with the object or
not.
Here
the object John will be invoking the default argument constructor and object Smith
will be invoking the parameterized constructor. If you notice here you can see
that I have defined the constructors in the public section of the class. So
naturally question arises : can I write the constructor in the private section
of the class.
Putting
a constructor in the private section of the class restricts the creation of objects
of that class. Consider the case when object anu is created. As we discussed
before, immediately after memory allocation, it tries to invoke the constructor
for initialization. To reach the private constructor, it need to seek the help
of a member function defined in the public section of the class. But with a
partially constructed object, will we be ever able to invoke a member function.
No. So there is no way we can reach the private constructor of the class which
restricts the creation of object.
I am just
modifying the application layer of the above program.
#include
"dealer_header.h"
int main() {
Dealer John = Dealer("Abraham
John");
Dealer Smith;
Smith = Dealer("Stephen
Smith");
return 0;
}
Will this work??
If you notice the syntax, it seems to be like the constructor function is
getting invoked on the RHS and vinu on LHS is waiting for a return type from
this function. Infact some books refers it to as explicit call of constructors.
But not exactly. Syntax for real explicit call is
Dealer John;
John.Dealer(“Abraham
John”);
This is not
allowed at all in our language. The reason / explanation is : constructors are
meant for construction cum initialization of the object. If the object John is
already created why do we have to invoke the constructor again.
The thing which
happens in the first case is : in the first statement compiler forces object
John to invoke the constructor and in the second statement a temporary object
is created which invokes the constructor and the initialized temporary object
will get copied to Smith. So that is not exactly explicit call of constructor.
Explicit call always refers to invoking something with a dot operator.
So summarizing:
Constructors are special member functions of
the class. As a function, it do have a function name and function argument but
no return type. Constructor will take the class name. They should be written in
the public section of the class for successful object creation and it helps us
in initialization of the object.
From the above
discussion, you might have understood that constructors are playing very vital
role in object creation. Let me ask you one question. Till yesterday’s class
when we were writing programs, did we ever bothered about the constructors. No
right. Even then it was working perfectly. Or in other words, object creation
was taking place perfectly right. So what was happening there? The object was
getting created there because when you don’t write any constructors in your
class, by default you compiler provides you a default argument / zero argument
constructor. The only problem with that constructor is it initializes the
members only to garbage. So it is always preferable to write our own
constructors in our programs which initializes the members to user specified
values.
All these time we were talking
about functions which helps us in construction of the objects. So by this time
you might have guessed that there will be some one who is going to help us in
destruction also right. It is none other than another special member function
named as destructor.
Destructor:
Destructors
are special member functions of the class which helps us in cleaning up
activities when object comes out of the scope. Just like constructor,
destructors also share the same name with constructor. To distinguish between
constructors and destructors, destructor function have to be preceded by a ~
symbol. If you are not writing a destructor, compiler by default will give you
a destructor
Modifying
the above program with a destructor
//Header file for Dealer in CDS
#include <iostream>
using namespace std;
class Dealer {
public:
Dealer() {}
Dealer(char *n) {
strcpy(Name, n);
No_Cars_Sold = 0;
Price = 200000;
Comm_Rate = 0.16;
Comm_Per_Car = 0.0;
Total_Comm = 0.0;
}
~Dealer() {}
const Dealer& operator= (const Dealer &);
void Sell_Car();
void Change_Comm_Rate(float);
float Report_Comm();
private:
char Name[20];
int No_Cars_Sold;
int Price;
float Comm_Rate;
float Comm_Per_Car;
float Total_Comm;
};
//dealer_library.cpp
#include
"dealer_header.h"
void Dealer :: Sell_Car() {
No_Cars_Sold += 1;
Comm_Per_Car = Price * Comm_Rate;
Total_Comm += Comm_Per_Car;
}
void Dealer ::
Change_Comm_Rate(float Comm_Rate)
{
this -> Comm_Rate = Comm_Rate;
}
float Dealer ::
Report_Comm() {
return Total_Comm;
}
//dealer_application.cpp
#include
"dealer_header.h"
int main() {
Dealer John;
John.Sell_Car();
cout << John.Report_Comm()
<< endl;
Dealer Smith("Stephen
Smith");
Smith.Change_Comm_Rate(0.20);
Smith.Sell_Car();
cout << Smith.Report_Comm()
<< endl;
return 0;
}
If you notice
you can see that I have written a destructor without anything in the body. Ie)
it is similar to the destructor provided by the compiler. This is because, in
this program, there is only stack memory allocation. All the stack memory is
automatically reclaimed when the object comes out of the scope. Only when
dynamic memory allocation occurs, users have to explicitly de-allocate the
memory. Only in such situations we have to write our own destructors. Till that
point it is perfectly ok to utilize the destructor provided by the compiler. So
we will be talking more about these destructors once we are done with DMA.
Now
in the above program, can you tell me the order of construction and order of
destruction? The order of construction will be first John and then Smith. But
the order of destruction will be first Smith and then John. Y ? It is purely
due to the stack arrangement. Stack is last in first out. So the object which
went inside last is Smith. So it is popped out first. Or concluding, the order
of construction and destruction will always be in the reverse.
Before moving further lets learn
few things about dynamic memory allocation in C++.
Dynamic memory allocation:
Since c++ have backward
compatibility with C language, it completely supports the dynamic memory
allocation techniques supported by C. ie) it supports malloc,realloc, calloc
and free. In addition to this, for better memory management, c++ designers have
provided us with two new operator named as new
and delete.
New and delete
have two different versions, one for normal variable and one for arrays. The
syntax of new and delete for normal variables is
New data-type;
delete data-type;
The syntax of new and delete for
arrays is
new data-type[size]
delete [] data-type
Let’s take an
example. Consider a normal integer pointer :
int *ptr. See the difference in
memory allocation using malloc, free and new, delete.
Malloc() & free() new & delete
For variables for variables
Ptr = (int*) malloc(sizeof(int)); ptr = new int;
Free(ptr); delete ptr;
For arrays for arrays
Ptr = (int*) malloc(20 *
sizeof(int)); ptr = new int[20];
Free(ptr); delete [] ptr;
The major differences between these
two memory allocation techniques are
- malloc and free are functions where as new and delete are operators. Even though they are operators, don’t ever think that it is going to avoid the function overheads. This is because new and delete internally calls two functions, operator new and operator delete respectively.
- In new and delete, explicit type casting and size of operator is not needed. Every thing is taken care of internally.
- Malloc() when fails returns NULL where as new when it fails it returns a runtime error. Since we haven’t learnt how to handle the runtime errors, for the time being we will just modify the syntax of the new to throw NULL when it fails. The syntax you have to use is
new (nothrow)
data-type
&
new (nothrow)
data_type
The nothrow
version forces new to return NULL when it fails. So this will be the version of
new which we are going to use for the next few classes.
- Another major difference between malloc, free and new, delete is while dealing with the memory allocation of objects, new always calls the constructor and delete always calls the destructor where as malloc n free never calls the constructors and destructors.
- to enhance the above feature, there is one more feature added along with new. Ie) memory initialization at the time of allocation. For eg.
Ptr = new
int(10);
is perfectly
legal in new. Here four bytes of memory for integer is allocated and it will be
initialized with 10. This feature is very very essential while allocating
memory for objects. As I told you, while allocating memory for object, it calls
the constructors internally right. So this memory initializer list can be used
to pass the arguments to the parameterized constructor of the class.
Lets try to
apply the above mentioned concepts to our classes and objects and lets c how it
works. We will take the same dealer example here also.
//Header file
for Dealer in CDS
#include
<iostream>
using namespace
std;
class
Dealer {
public:
Dealer() {
Name = new (nothrow)
char[20];
if(Name == NULL) {
cout <<
"Mem allocation failed" << endl;
exit(1);
}
cout <<
"Enter your name" << endl;
cin >> Name;
No_Cars_Sold = 0;
Price = 200000;
Comm_Rate = 0.16;
Comm_Per_Car = 0.0;
Total_Comm = 0.0;
}
Dealer(char *n) {
Name = new (nothrow)
char[strlen(n) + 1];
if(Name == NULL) {
cout <<
"Mem allocation failed" << endl;
exit(1);
}
strcpy(Name, n);
No_Cars_Sold = 0;
Price = 200000;
Comm_Rate = 0.16;
Comm_Per_Car = 0.0;
Total_Comm = 0.0;
}
void Sell_Car();
void Change_Comm_Rate(float);
float Report_Comm();
~Dealer() {
delete [] Name;
}
private:
char *Name;
int No_Cars_Sold;
int Price;
float Comm_Rate;
float Comm_Per_Car;
float Total_Comm;
};
//dealer_library.cpp
#include
"dealer_header.h"
void Dealer ::
Sell_Car() {
No_Cars_Sold += 1;
Comm_Per_Car = Price * Comm_Rate;
Total_Comm += Comm_Per_Car;
}
void Dealer ::
Change_Comm_Rate(float Comm_Rate)
{
this -> Comm_Rate = Comm_Rate;
}
float Dealer ::
Report_Comm() {
return Total_Comm;
}
//dealer
_application.cpp
#include
"dealer_header.h"
int main() {
Dealer John;
Dealer Smith("Stephen
Smith");
Dealer *Jack = new Dealer;
Dealer *Jill = new
Dealer("Jill");
delete Jack;
delete Jill;
return 0;
}
For the above
program, the memory picture will be as shown below.
So
by this time you will be clear with the advantages of new and delete which made
us shift from malloc, free to new and delete. Here onwards we will be sticking
to the memory allocation techniques specific to c++ in our programs.
0 comments:
Post a Comment