virtual copy constructor

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • Deanm007
    New Member
    • Apr 2010
    • 37

    virtual copy constructor

    I have been plunging my brains trying to figure this out all day today...Its 2 am now. Please, someone explain the following to me: I know the virtual clone method is there in each class to facilitate the use of virtual copy constructors since you cannot directly declare
    *virtual Mammal (const Mammal & rhs);* in this manner. However, I cannot understand what the clone method is doing. Lets say for example, at the prompt, I enter 2. Then the switch statement creates a Cat object on the free store and assigns it to ptr. Then ptr is assigned to theArray[i]. This cycle continues until the for loop ends. Then it executes the next for loop with the addresses stored in theArray[i] accessing the speak method. you see "Meow" in the output. In the next step it access the clone method and the both the Cat copy constructor and the Dog copy constructor is called. How is this possible? And what are the steps taking place here? How does the Cat's clone method call the copy constructors? It is possible that I am not understanding the fundamentals of *this as I suspect that this is responsible for calling the copy constructor.

    Code:
    #include <iostream>
    
    using namespace std;
    
     class Mammal
     {
     public:
     Mammal():itsAge(1) { cout << "Mammal constructor...\n"; }
     ~Mammal() { cout << "Mammal destructor...\n"; }
     Mammal (const Mammal & rhs); 
     virtual void Speak() const { cout << "Mammal speak!\n"; }
     virtual Mammal * Clone() { return new Mammal(*this); } 
     int GetAge()const { return itsAge; }
     protected:
     int itsAge;
     };
    //copy constructor defined
     Mammal::Mammal (const Mammal & rhs):itsAge(rhs.GetAge())
     {
     cout << "Mammal Copy Constructor...\n";
     }
    
     class Dog : public Mammal
     {
     public:
     Dog() { cout << "Dog constructor...\n"; }
     ~Dog() { cout << "Dog destructor...\n"; }
     Dog (const Dog & rhs); 
     void Speak()const { cout << "Woof!\n"; }
     virtual Mammal * Clone() { return new Dog(*this); } 
     };
     
     Dog::Dog(const Dog & rhs):
     Mammal(rhs)
     {
     cout << "Dog copy constructor...\n";
     }
    
     class Cat : public Mammal
     {
     public:
     Cat() { cout << "Cat constructor...\n"; }
     ~Cat() { cout << "Cat destructor...\n"; }
     Cat (const Cat & rhs); 
     void Speak()const { cout << "Meow!\n"; }
     virtual Mammal * Clone() { return new Cat(*this); } 
     };
     
     Cat::Cat(const Cat & rhs):
     Mammal(rhs)
     {
     cout << "Cat copy constructor...\n";
     }
     
     enum ANIMALS { MAMMAL, DOG, CAT}; 
     const int NumAnimalTypes = 3; 
     
     int main()
     {
     Mammal * theArray[NumAnimalTypes]; 
     Mammal * ptr; 
     int choice, i;
     for ( i = 0; i<NumAnimalTypes; i++)
     { 
     cout << "(0)Mammal (1)dog (2)cat: ";
     cin >> choice;
     
     switch (choice)
     {
     case DOG: ptr = new Dog; 
     break;
     case CAT: ptr = new Cat; 
     break;
     default: ptr = new Mammal; 
     break;
     }
     theArray[i] = ptr; 
     }
     Mammal * OtherArray[NumAnimalTypes]; 
     
     
     for (i=0;i<NumAnimalTypes;i++)
     {
     theArray[i]->Speak(); 
     OtherArray[i] + theArray[i]->Clone(); 
     
     }
     
     for (i=0;i<NumAnimalTypes;i++)
     OtherArray[i]->Speak(); 
     
     system("pause");
     return 0;
     }
  • newb16
    Contributor
    • Jul 2008
    • 687

    #2
    constructor - Dog::Dog(const Dog & rhs)
    code - return new Dog(*this);
    'this' variable here is a pointer to the class instance, of the type Dog*, then it is dereferenced to the type Dog& and passed to constructor. It works the same way as if they were
    Code:
    constructor - Dog::Dog(const Dog* rhs)
    code - return new Dog(this);

    Comment

    • weaknessforcats
      Recognized Expert Expert
      • Mar 2007
      • 9214

      #3
      A clone is a copy.

      In C++ you create a clone by using the copy constructor.

      In languages other than C++ where there is notjhing like a copy constrcutor, you need to write a clone method to make the copy.

      Comment

      • Deanm007
        New Member
        • Apr 2010
        • 37

        #4
        Thanks for the responses. But I am sorry, i'm still not getting the complete picture. Let me know if this is correct: in the virtual function:

        virtual Mammal * Clone(){return new Dog(*this)}

        It creates a Dog object on the free store and assigns the calling object's contents to this newly created Dog. Then it returns a pointer to a Mammal. Then this pointer is stored in OtherArray[i] which will iterate to display all the speak methods. A mammal pointer is able to access the correct speak methods in Dog, Cat, etc because the speak method in Mammal is virtual. Am I correct so far? Now, the question is: When in this process is the copy constructor called? And what is the copy constructor doing? I really think I should understand this before moving on to polymorphism.

        Comment

        • newb16
          Contributor
          • Jul 2008
          • 687

          #5
          Yes you are correct.

          This part
          > assigns the calling object's contents to this newly created Dog.

          actually happens within copy constructor, when parent constructor is called -
          Code:
          : Mammal(rhs)
          and copies the only member (age) from rhs into constructing object.
          When is it called? During 'new' operator, after memory is allocated.

          Comment

          • Deanm007
            New Member
            • Apr 2010
            • 37

            #6
            Thanks for the explanation newb16. I now understand that the new Dog gets its age copied to it inside the copy constructor. So when the Dog clone() method is called, I see 2 events take place:

            1. A new Dog object is created on the free store.

            In between these two steps, the Mammal copy constructor is called first with the address in *this and then the Dog copy constructors is called with *this. And inside the copy constructor, the value of age is copied into the new object. So basically *this is calling these copy constructors?

            2. A Mammal pointer to this object is returned to the invoking object which is theArray[i] and the address is assigned to OtherArray[i].

            Comment

            • Deanm007
              New Member
              • Apr 2010
              • 37

              #7
              I think I finally understand this correctly thanks to newb16 and others. Please see my comments in the code below and let me know if I my understanding is right or wrong. Thanks for your patience with my rather long comments.

              Code:
              #include <iostream>
              
              using namespace std;
              
               class Mammal
               {
               public:
               Mammal():itsAge(1) { cout << "Mammal constructor...\n"; }
               ~Mammal() { cout << "Mammal destructor...\n"; }
               Mammal (const Mammal & rhs); 
               virtual void Speak() const { cout << "Mammal speak!\n"; } //virtual function
               virtual Mammal * Clone() { cout << "mammal clone called" << endl; return new Mammal(*this); } //This method returns a pointer to a new Mammal
               //object by calling the copy constructor, passing in itself (*this) as a const reference to a mammal. this virtual method is the core of the 
               //copy constructor. Here is the step by step of what happens here:
               //A Mammal object's ptr calls this method. A new Mammal is created on the heap. Then Mammal(*this) calls Mammal copy constructor. 
               //*this contains a constant reference(address) of the new Mammal object. This address is assigned to rhs because it takes a reference(address).
               //Then rhs gets the existing value in itsAge, and assigns it to the new Mammal object's itsAge. The clone method also returns a
               //pointer to a Mammal back to the invoking object which is theArray[i] and the address is assigned to OtherArray[i]. Using this Mammal pointer,
               //the correct speak method is called for each object in the end.
               
               
               int GetAge()const { return itsAge; }
               protected:
               int itsAge;
               };
              
               Mammal::Mammal (const Mammal & rhs):itsAge(rhs.GetAge())
               {
               cout << "Mammal Copy Constructor...\n";
               }
              
               class Dog : public Mammal
               {
               public:
               Dog() { cout << "Dog constructor...\n"; }
               ~Dog() { cout << "Dog destructor...\n"; }
               Dog (const Dog & rhs); 
               void Speak()const { cout << "Woof!\n"; }
               virtual Mammal * Clone() { cout << "dog clone called" << endl; return new Dog(*this); } //Step by step of what happens here:
               //A Dog object calls the Dog clone method. Then Dog(*this) calls the Dog copy constructor and passes in the address of the new Dog object
               //into rhs. Then this rhs(address of the new Dog object) is passed in as a reference to Mammal, thus Mammal(rhs) calls the 
               //Mammal copy constructor that takes in a reference(address). Control is now passed to the Mammal copy constructor.   
               //Now inside the Mammal copy constructor, rhs gets the existing value for age and assigns it into the new Dog object's itsAge.
               //And then the Mammal body code is executed. Control now returns back to the Dog copy constructor. And then its body is executed.
               //And then a pointer to the mammal is returned to the invoking object theArray[i].
               };
               
               Dog::Dog(const Dog & rhs):
               Mammal(rhs)
               {
               cout << "Dog copy constructor...\n";
               }
              
               class Cat : public Mammal
               {
               public:
               Cat() { cout << "Cat constructor...\n"; }
               ~Cat() { cout << "Cat destructor...\n"; }
               Cat (const Cat & rhs); 
               void Speak()const { cout << "Meow!\n"; }
               virtual Mammal * Clone() {cout << "cat clone called" << endl; return new Cat(*this); }  //same procedure as Dog's clone method.
               };
              
               Cat::Cat(const Cat & rhs):
               Mammal(rhs)
               {
               cout << "Cat copy constructor...\n";
               }
               
               enum ANIMALS { MAMMAL, DOG, CAT}; 
               const int NumAnimalTypes = 3; 
               
               int main()
               {
               Mammal * theArray[NumAnimalTypes];  
               Mammal * ptr; 
               int choice, i;
               
               for ( i = 0; i<NumAnimalTypes; i++)
               { 
               cout << "(0)Mammal (1)dog (2)cat: ";
               cin >> choice;
               
               switch (choice)
               {
               case DOG: ptr = new Dog; 
               break;
               case CAT: ptr = new Cat; 
               break;
               default: ptr = new Mammal; 
               break;
               }
               theArray[i] = ptr; 
               }
               Mammal * OtherArray[NumAnimalTypes]; 
               
               for (i=0;i<NumAnimalTypes;i++)
               {
               theArray[i]->Speak(); 
               OtherArray[i] = theArray[i]->Clone(); 
              
               }
               
               for (i=0;i<NumAnimalTypes;i++)
               OtherArray[i]->Speak(); 
               
               
               system("pause");
               return 0;
               }

              Comment

              • weaknessforcats
                Recognized Expert Expert
                • Mar 2007
                • 9214

                #8
                This code in the Mammal class:

                Code:
                virtual Mammal * Clone() { cout << "mammal clone called" << endl; return new Mammal(*this); }
                should be:

                Code:
                virtual Mammal * Clone() { return 0; }
                This make the method a hook. A hook can be overridden in a derived class but the derived class is not forced to to do this.

                Suppose have the address some Mammal object in ptr:

                Code:
                Mammal* obj = ptr->Clone();
                If the object pointed at by ptr is a Mammal class that did not override Mammal::Clone() , then you haven't got a clone. You have a Mammal created from the Mammal portion of the ptr object. The rest of the ptr object has been sliced off. (Research on slicing).

                By having Mammal::Clone() return a 0, then if a derived class does not implement Clone() then you cannot clone objects of thise classes. The caller just verifies that the Mammal* returned after the call to Clone() is not null.

                Comment

                • Deanm007
                  New Member
                  • Apr 2010
                  • 37

                  #9
                  Thanks for the explanation. But I am trying to understand the code in this book that I am learning with i would like to know if I am understanding this specific code correctly.

                  Comment

                  • weaknessforcats
                    Recognized Expert Expert
                    • Mar 2007
                    • 9214

                    #10
                    Many books have poor examples. In the case of hierarchies using virtual functions the going-in position is that:

                    1) you will create a derived object.
                    2) you will refer to the derived object using only a base class pointer
                    3) when you call a base class method, the call is diverted to the derived class method (if available).

                    Therefore, your Mammal* may not point to a Mammal. Therefore, the Clone() of Mammal returns a bad object if a) the actual object is not a Mammal and b)the actual object does not support Clone().

                    I understand about trying to work through your textbook but I want you to understand that in all design there is a downside. Not seeing the downside in C++ will cause you more trouble than you can imagine. Maybe what I say does not apply in this case but I guarantee that it will apply in a case you encounter later on. Maybe at that time you remember this series of posts and go "Wait a minute...".

                    Comment

                    • Deanm007
                      New Member
                      • Apr 2010
                      • 37

                      #11
                      You're absolutely right about books. I tried to contact the author of the book and his email is no longer valid. I am going to make the changes you've posted and report back. Thanks.

                      Comment

                      Working...