DAY
– 7
Lets see the
implementation of the above class.
//Stereo.h
//illustrating
deep copy and shallow copy
#include
<iostream>
#include
<exception>
using
namespace std;
class
Stereo {
private:
char *Model_Name;
char *Accessories;
int Volume;
public:
Stereo() {}
Stereo(char *b, int v, char *a)
{
try {
Model_Name =
new char[strlen(b) + 1];
}
catch(bad_alloc obj) {
cout <<
obj.what() << endl;
}
strcpy(Model_Name, b);
try {
Accessories =
new char[strlen(a) + 1];
}
catch(bad_alloc
obj) {
cout <<
obj.what() << endl;
}
strcpy(Accessories, a);
Volume = v;
}
~Stereo() {
delete [] Model_Name;
delete [] Accessories;
}
void Change_Model_Name(char*);
void Change_Accessories(char
*a);
char* Return_Accessories();
char* Return_Model_Name();
};
//stereo_lib.cpp
#include
"stereo.h"
void
Stereo :: Change_Accessories(char *a)
{
strcpy(Accessories, a);
}
char*
Stereo :: Return_Accessories() {
return Accessories;
}
void
Stereo :: Change_Model_Name(char *n)
{
strcpy(Model_Name, n);
}
char*
Stereo :: Return_Model_Name() {
return Model_Name;
}
//stereo_application.cpp
#include
"stereo.h"
Stereo
Change_Features(Stereo);
int
main() {
Stereo Sony_Basic("SonyM",
11, "Casette player");
Stereo Sony_Advanced = Sony_Basic;
Sony_Advanced.Change_Model_Name("SonyA");
cout <<
Sony_Basic.Return_Model_Name() << endl;
cout <<
Sony_Advanced.Return_Model_Name() << endl;
}
Just explaining briefly what exactly I am
doing here. First I created a stereo named Sony_Basic with model name as SonyM,
volume as 11 and accessories as Casette player. Now my idea is to create a new
advanced stereo in which the accessories will be a CD player instead of
cassette player. As part of this, in the program I am creating a new stereo
object Sony_Advanced. Since only few modification are there when compared to
Sony_Basic, I am first copying the entire contents of old stereo to new one.
Then I am invoking the function Change_Model_Name() just to change the model
name. Then I am trying to print the model names of both the mobiles.
Can you
guess what will be the output here. The most expected answer is SonyB
and SonyA. But surprisingly u wont get the output you
expected. It will be printing you SonyA and SonyA instead of your expected output. What is the
reason behind this unexpected behaviour ? To answer this you should first
analyze the memory picture of the above program.
In the first step, I am creating an object Sony_Basic. Internally a block of memory will be
allocated which is equivalent to the size of the class Stereo. ie) 12 bytes. Once the memory allocation is over, it
invokes the parameterized constructor for the initialization of the memory. In
the constructor we are allocating few bytes of memory each for Model_Name
and Accessories. After
this, the memory will be initialized with the values specified by the user. The
memory picture can be diagrammatically shown as below.
Now in the second line of main, a second object
named Sony_Advanced is created. So as a
first step 12 bytes of memory
will be allocated in stack. Now the next step is initialization of the memory.
So the question arises. Which constructor will be called? The constructor which
is getting called here should be capable of initializing the new object Sony_Advanced with the already existing object Sony_Basic.
But have we written any constructor in our
class which is capable of taking Sony_Basic as an argument and copying it to Sony_Advanced. No. As you can see, the default argument or
parameterized constructors are not capable of doing this job. So what your
compiler will do is, it will provide you with a default copy constructor which
can copy the contents of the already existing object to the new object.
But the copy constructor provided by the
compiler knows only bit by bit copy. Ie) what ever is there in the previous
object , it will be copied to the new object. As a result, the pointers Model_Name and Accessories of both the objects will be holding the same
address. So the memory picture can be re-drawn as follows
So now we can guess the output right. When you
tried to change the Model_Name of Sony_Advanced, since both the object are sharing the same
heap memory locations, the changes made by one object will be directly
reflected in the other. So both the cout statements are going to print you SonyA only. This type of bit by bit copying is
technically termed as shallow copy. So the copy
constructor provided by the compiler is going to do a shallow copy.
There are two more situations where the above
type of shallow copy can take place. Consider the same example. Lets say I am
invoking a global member function which is taking care of changing the features
Accessories.
After changing it, it returns the result back to main. lets modify our
application layer of the above program.
//stereo_applcation.cpp
#include
"stereo.h"
Stereo
Change_Features(Stereo);
int
main() {
Stereo Sony_Basic("SonyM",
11, "Casette player");
Stereo Sony_Advanced = Sony_Basic;
Sony_Advanced.Change_Model_Name("SonyA");
cout <<
Sony_Basic.Return_Model_Name() << endl;
cout <<
Sony_Advanced.Return_Model_Name() << endl;
Sony_Advanced =
Change_Features(Sony_Advanced);
cout <<
Sony_Advanced.Return_Accessories() << endl;
return 0;
}
Stereo
Change_Features(Stereo Sony) {
Sony.Change_Accessories("CD
Player");
return Sony;
}
In
the above program, since the object is passed by value to the global function,
again a local object will be created and due to the shallow copy done by the
copy constructor, the local object Sony will also be pointing to the same
memory location. At the end of the function, we are returning Sony back to main
by value. Returning by value creates a temporary local object into which
contents of Sony will be getting copied. Here also a shallow copy occurs and it
is this temporary object which is coming to back to main. so before seeing the
rest of the explanation lets see the memory picture till now.
Back to the explanation, the temporary
object comes back to main, copies its contents back to Sony_Advanced. Since its
life time is very short, once the copying is done, it calls its constructor and
gets destroyed. When it gets destroyed, the heap memory associated with it also
gets destroyed. In the sense, the pointers, Model_Name and Accessories of all
the other 3 objects becomes dangling pointers. Ultimately the last cout
statement will simply print you a garbage or a blank space.
This is not a desirable situation at
all. In order to avoid this, the only solution is , the user have to write his
own copy constructor. The syntax of the copy constructor is
Class_Name(const Class_Name &);
The
body of the copy constructor should be modified in such a manner that it do a
deep copy. Lets try to modify our header with a copy constructor
//stereo.h
//illustrating
deep copy and shallow copy
#include
<iostream>
#include
<exception>
using
namespace std;
class
Stereo {
private:
char *Model_Name;
char *Accessories;
int Volume;
public:
Stereo() {}
Stereo(char *b, int v, char *a)
{
try {
Model_Name = new char[strlen(b) + 1];
}
catch(bad_alloc
obj) {
cout <<
obj.what() << endl;
}
strcpy(Model_Name, b);
try {
Accessories =
new char[strlen(a) + 1];
}
catch(bad_alloc
obj) {
cout <<
obj.what() << endl;
}
strcpy(Accessories, a);
Volume = v;
}
Stereo(const Stereo
&Sony) {
try {
Model_Name =
new char[strlen(Sony.Model_Name) + 1];
}
catch(bad_alloc
obj) {
cout <<
obj.what() << endl;
}
try {
Accessories =
new char[strlen(Sony.Accessories) + 1];
}
catch(bad_alloc
obj) {
cout <<
obj.what() << endl;
}
strcpy(Model_Name,
Sony.Model_Name);
strcpy(Accessories,
Sony.Accessories);
Volume = Sony.Volume;
}
~Stereo() {
delete [] Model_Name;
delete [] Accessories;
}
void Change_Model_Name(char*);
void Change_Accessories(char
*a);
char* Return_Accessories();
char* Return_Model_Name();
};
So ultimately after writing the memory picture
the memory picture becomes
If you notice
the syntax of copy constructors, the argument is taken by reference. It is a
must. This is because, removing the reference may make the object passing pass
by value which may result in calling the copy constructor again and again. Ie)
ultimately in an infinite loop.
This is just a theoretical error. But
practically compiler is going to throw you a conflict error. This is because,
when reference is removed, that constructor will be simply considered as a
normal parameterized constructor by the compiler. Now there will be two constructors
in our class : one user defined parameterized which takes the object by value
and one compiler provided which takes object by reference. So when we invoke
the constructor by passing the object, compiler gets confused regarding which
one to call. This is the reason for the conflict error which will be shown by
our compiler.
The const
keyword along with the argument assures that the object taken by reference is
not getting modified inside the copy constructor.
Now compile your
program and see the output. You can see that the last cout statement is still
not perfect. It is simply showing some garbage value. What can be the reason ?
Lets go back to
the story which we stopped. I told you that it is the temporary object which is
coming back to main. now back in main, the contents of the temporary object
will be getting copied to Sony_Advanced. Ie_
Sony_Advanced =
internal temporary object.
Who is doing the
copying here ? it is not the copy constructor. Instead it will be our same old
assignment operator provided by the compiler since the copying is between two
object which are already constructed. This is the only difference between copy
constructor and assignment operator. The compiler provided assignment and copy
constructor is similar in one way in that both of them do a bit by bit copy or
shallow copy. So ultimately memory picture will become,
Since bit bit copy occurs, the
Model_Name and Accessories of Sony_Advanced will also point to the Model_Name
and Accessories of the internal temporary object. After the copying, as I told
you since the life time of temporary object is till the semicolon, it gets
destroyed .This results in two major problems here
1.
Sony_Advanced’s
pointers becomes dangling
2.
Memory
pointer by pointers of Sony_Advanced leaks
So ultimately it prints us garbage only.
If we have to avoid it, we need to
know how to redefine our assignment operator. Any out of scope for us as far as
this class is concerned. We will be dealing with this very shortly in our
class.
So by this time, u will be aware of
4 member functions provided to you by default by your compiler.
1.
Default
constructor
2.
Copy
constructor
3.
Destructor
4.
Assignment
operator
0 comments:
Post a Comment