I have few confusions about what exactly happens when you pass the object by value, and about working of copy constructor. To practice the concept, I wrote the following code.
#include <iostream>
using namespace std;
class Cat
{
public:
Cat();
Cat(Cat&);
~Cat() { cout << "Destructor called\n";}
int itsage;
};
Cat::Cat()
{
cout << "Constructor called\n";
itsage=2;
}
Cat::Cat(Cat& )
{
cout << "Copy constructor called\n";
itsage=50;
}
Cat myFunction(Cat Frisky)
{
cout << "Frisky's age: " << Frisky.itsage << "\n";
Frisky.itsage=100;
cout << "Reassigned Frisky's age: "<< Frisky.itsage << "\n";
return Frisky;
}
int main()
{
Cat Mani;
cout << "Mani's age: " << Mani.itsage << "\n";
myFunction(Mani);
cout << Mani.itsage;
cout << endl;
return 0;
}
I got output as:
Constructor called
Mani's age: 2
Copy constructor called
Frisky's age: 50
Reassigned Frisky's age: 100
Copy constructor called
Destructor called
Destructor called
2
Destructor called
I have understood the first six lines of output. I have read that when you pass or return the object
by value, a temporary copy of object is made. So, temporary copy of Frisky
got
creared when statement return Frisky
got executed. But it got destroyed immediately as it was not assigned to
anything. This I think produced seventh line of output making first call to destructor. The output on line nine
and ten is also clear to me. I am confused about the eighth line of output which makes the second call to destructor.
Doubt 1: What exactly got destroyed in the second call to destructor? When we made call to myFunction via statement myFunction(Mani)
, local copy of Mani
got created. After copy constructor finished, Frisky
also got created in myFunction
. Now when myFunction
ended, what got destroyed? local copy of Mani
or Frisky
?
Doubt 2: Where this local copy of object is stored? Means when I called myFunction
, where is local copy of Mani
gets stored? Inside myFunction
, inside main
or somewhere else?
Doubt 3: Copy constructor is also one kind of function. Then in the header, in the parameter list, we only mentioned parameter type Cat &
but didn't write parameter name. I have read that it is ok not to write parameter name in function prototype but when we are writing function definition, we should write type and name both. Is Copy constructor exception to this rule?
The main problem is, the "copy" constructor Cat::Cat(Cat &)
doesn't copy the value of itsage
but sets it to an arbitrary value. Thus, passing a Cat
object by value and returning it will yield strange results since the received objects don't resemble the original object.
Details: The parameter Frisky
of the function myFunction
is passed by value. This means calling myFunction
will copy the passed object "into" Frisky
. The copy will be constructed by the copy constructor Cat::Cat(Cat &)
. Since this constructor sets itsage
to 50
, myFunction
always reports an age of 50
regardless of the original passed value.
The same applies here to the return values: Returning an object from a myFunction
constructs a copy for the caller by using the copy constructor Cat::Cat(Cat &)
. The returned copy always has a value itsage=50
.
Solution: Write a proper copy constructor:
Cat::Cat(const Cat &other)
: itsage(other.itsage)
{ }
Or even better: Use the default one by simply omitting the copy constructor. This default constructor essentially does the same as the above mentioned and comes for free.
If you really wish to modify the value passed to myFunction
and to see these modification outside of myFunction
, you need to pass the parameter Frisky
by reference:
Cat myFunction(Cat &Frisky)
{
// ...
}
Answers to your doubts:
The first destructor call is from the parameter Frisky
which ends its lifetime when the function myFunction
returns. The second destructor call is from the returned object. Since it never get used it is destructed right after the call of myFunction
. The third destructor call is the end of the lifetime of Mani
at the end of main
.
Right before entering myFunction
a local copy of Mani
is stored on the stack and can be referenced inside myFunction
by Frisky
. It will be destroyed at the end of myFunction
.
Yes, you can omit the name of a parameter but in the case of a copy constructor this is nonsense. A copy constructor should construct an object similar to the passed object. Since that it needs to access the state inside the passed object.
Lifetime: The following diagram shows the lifetime of the various objects of class Cat
:
int main()
{ Mani
Cat Mani; Frisky -+-
myFunction(Mani) -+- |
Cat myFunction(Cat Frisky) | |
{ | |
Frisky.itsage=100; Return | |
return Frisky; -+- | |
} | | |
-(*)----------------------------------------------+- | |
;-(*)--------------------------------------------------------+- |
return 0; |
}-(*)---------------------------------------------------------------------+-
The (*)
denotes roughly the moment, when their destructors are called.
You asked, why there is no destructor call for the local copy of Mani
. The only local copy (I'm not entirely sure, what you mean by that) of Mani
is created in order to get an object called Frisky
inside the function myFunction
. You don't need any further copies of Mani
.