Headlines News :
Home » , , » Learn C++ in 12 Days - Day 7

Learn C++ in 12 Days - Day 7

Written By Ente Malayalam on Friday, May 24, 2013 | 5/24/2013

DAY – 7
            In our car dealer ship system, we have already implemented dealer and a customer right. in today’s class lets take another new entity “stereo”. Lets consider the class diagram for it.









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





































Share this article :

0 comments:

Post a Comment

 
Support : Creating Website | Johny Template | Maskolis | Johny Portal | Johny Magazine | Johny News | Johny Demosite
Copyright © 2011. Education World - All Rights Reserved
Template Modify by Creating Website Inspired Wordpress Hack
Proudly powered by Blogger