Classes
Contents
Copy Constructors
Sometimes an object is created using another object. In this case a special kind of constructor called a "Copy Constructor" gets called. There are 3 cases where the copy constructor is called. One is when an object is defined and initialized with an object. The other is when an object is created and initialized with a function on the right hand side that returns an object and the third case is when we call a function that has an object as the argument. A default copy constructor is created if we don't define our own. In some cases this can create problems.File cc1.cpp
#include <iostream> using namespace std ; class Demo { public: int x1 ; int x2 ; int* ptr ; Demo() { cout << "Inside the constructor method." << endl ; ptr = new int[10] ; } ~Demo() { cout << "Inside the destructor method." << endl ; delete[] ptr ; } }; int main() { Demo demo1 ; Demo demo2 = demo1 ; } $ g++ cc1.cpp ; ./a.exe Inside the constructor method. Inside the destructor method. Inside the destructor method. Aborted (core dumped)We create an object called "demo1" . The constructor allocates some memory that gets assigned to the variable "ptr" . We then use this object to initialize another object "demo2". The default copy constructor gets called and that copies all the data values bit by bit to the variables of "demo1" . This is called bitwise copy. Thus the "ptr" of "demo2" will take on the value of "ptr" of "demo1" The object "demo2" gets destructed and releases the memory for "ptr" . The object "demo1" gets destructed and also deletes "ptr" that points to the same address and we get an exception in the program. We should define our own copy constructor to handle this case.
File cc2.cpp
#include <iostream> using namespace std ; class Demo { public: int x1 ; int x2 ; int* ptr ; Demo() { cout << "Inside the construtor method." << endl ; ptr = new int[10] ; //Initialize with some values for( int i1=0; i1<10 ; i1++ ) { ptr[i1] = i1+ 1; } } Demo(const Demo& demoObj ) { cout << "Inside the copy construtor method." << endl ; ptr = new int[10] ; //Copy the values in the array from demoObj for( int i1=0; i1<10 ; i1++ ) { ptr[i1] = demoObj.ptr[i1] ; } } ~Demo() { cout << "Inside the destrutor method." << endl ; delete[] ptr ; } void print() { for( int i1=0; i1<10 ; i1++ ) { cout << ptr[i1] << " " ; } cout << endl ; } }; int main() { Demo demo1 ; Demo demo2 = demo1 ; demo2.print() ; } $ g++ cc2.cpp ; ./a.exe Inside the construtor method. Inside the copy construtor method. 1 2 3 4 5 6 7 8 9 10 Inside the destrutor method. Inside the destrutor methodThe signature for a copy constructor is of the form:
Demo(const Demo& demoObj ) We function name is the name of the class and the parameter is a reference. It must be a reference and not a normal object such as "Demo demoObj". Otherwise the parameter "demoObj" will try to get created through the calling method and we have the copy constructor method getting called in a loop. The 2 other cases in which he copy constructor will get called is when passing the object to the arguments of a function and when a function returns an object. The function returning an object doesn't always end up calling the copy constructor due to compiler optimization.
File cc3.cpp
#include <iostream> using namespace std ; class Demo { private: int z1 ; public: int x1 ; int x2 ; string name ; int* ptr ; Demo(string nameP ) { name = nameP ; cout << "Inside the construtor method."<< name << endl ; ptr = new int[10] ; } Demo(const Demo& demoObj ) { cout << "Inside the copy constructor method." << demoObj.name << endl ; ptr = new int[10] ; name = demoObj.name ; //We have access to another object of the same class's //private data. x1 = demoObj.z1 ; } ~Demo() { cout << "Inside the destructor method:" << name << endl ; delete[] ptr ; } }; void function1( Demo obj1 ) { cout << "Inside function1" << endl ; } int main() { { Demo demo1("Local1" ) ; cout << "Step1:" << endl ; function1( demo1 ) ; cout << "Step2" << endl ; } } $ g++ cc3.cpp ; ./a.exe Inside the construtor method.Local1 Step1: Inside the copy constructor method.Local1 Inside function1 Inside the destructor method:Local1 Step2 Inside the destructor method:Local1The above shows the copy constructor getting called when we call the function "function1".
File cc4.cpp
#include <iostream> using namespace std ; class Demo { private: int z1 ; public: int x1 ; int x2 ; string name ; int* ptr ; Demo(string nameP ) { name = nameP ; cout << "Inside the construtor method."<< name << endl ; ptr = new int[10] ; } Demo(const Demo& demoObj ) { cout << "Inside the copy constructor method." << demoObj.name << endl ; ptr = new int[10] ; name = demoObj.name ; //We have access to another object of the same class's //private data. x1 = demoObj.z1 ; } ~Demo() { cout << "Inside the destructor method:" << name << endl ; delete[] ptr ; } }; Demo function1( ) { Demo obj2("function1") ; obj2.x1 = 100 ; cout << "Inside function1" << endl ; return obj2 ; } int main() { { Demo demo1("Local1" ) ; cout << "Step1:" << endl ; Demo demo2 = function1( ) ; cout << "Step2" << endl ; } } $ g++ -fno-elide-constructors cc4.cpp ; ./a.exe Inside the construtor method.Local1 Step1: Inside the construtor method.function1 Inside function1 Inside the copy constructor method.function1 Inside the destructor method:function1 Step2 Inside the destructor method:function1 Inside the destructor method:Local1We use the "-fno-elide-constructors" option to prevent optimization and the copy constructor gets called when the function returns. Let us understand how the optimization is working.
File cc4a.cpp
#include <iostream> using namespace std ; class Demo { private: int z1 ; public: int x1 ; int x2 ; string name ; int* ptr ; Demo(string nameP ) { name = nameP ; cout << "Inside the construtor method."<< name << endl ; ptr = new int[10] ; } Demo(const Demo& demoObj ) { cout << "Inside the copy constructor method." << demoObj.name << endl ; ptr = new int[10] ; name = demoObj.name ; //We have access to another object of the same class's //private data. x1 = demoObj.z1 ; } ~Demo() { cout << "Inside the destructor method:" << name << endl ; delete[] ptr ; } }; Demo function1( ) { Demo obj2("function1") ; obj2.x1 = 100 ; cout << "Inside function1" << endl ; printf( "Address of obj2: %p\n" , &obj2 ) ; return obj2 ; } int main() { { Demo demo1("Local1" ) ; cout << "Step1:" << endl ; //Demo demo2 = function1( ) ; function1() ; cout << "Step2" << endl ; //printf( "Address of demo2: %p\n" , &demo2 ) ; } } Inside the construtor method.Local1 Step1: Inside the construtor method.function1 Inside function1 Address of obj2: 0x7ffffcc00 Inside the destructor method:function1 Step2 Inside the destructor method:Local1 The first constructor get called from when "demo1" object is created. Then the "function1" is called. Now the object "obj2" is created. Once the function ends it goes out of scope and it gets destroyed. Now let's take the case of the function return an object.
File cc4b.cpp
#include <iostream> using namespace std ; class Demo { private: int z1 ; public: int x1 ; int x2 ; string name ; int* ptr ; Demo(string nameP ) { name = nameP ; cout << "Inside the construtor method."<< name << endl ; ptr = new int[10] ; } Demo(const Demo& demoObj ) { cout << "Inside the copy constructor method." << demoObj.name << endl ; ptr = new int[10] ; name = demoObj.name ; //We have access to another object of the same class's //private data. x1 = demoObj.z1 ; } ~Demo() { cout << "Inside the destructor method:" << name << endl ; delete[] ptr ; } }; Demo function1( ) { Demo obj2("function1") ; obj2.x1 = 100 ; cout << "Inside function1" << endl ; printf( "Address of obj2: %p\n" , &obj2 ) ; return obj2 ; } int main() { { Demo demo1("Local1" ) ; cout << "Step1:" << endl ; Demo demo2 = function1( ) ; //function1() ; cout << "Step2" << endl ; printf( "Address of demo2: %p\n" , &demo2 ) ; } } $ g++ cc4b.cpp ; ./a.exe Inside the construtor method.Local1 Step1: Inside the construtor method.function1 Inside function1 Address of obj2: 0x7ffffcbf0 Step2 Address of demo2: 0x7ffffcbf0 Inside the destructor method:function1 Inside the destructor method:Local1 The compiler does optimization. We reach "Step 2". The object "obj2" does not go out of scope. Rather the compiler puts "demo2" at the place where "obj2" is. The "obj2" and "obj1" are destroyed when main exits.Now let's study the case when compiler optimization is switch off.
$ g++ -fno-elide-constructors cc4b.cpp ; ./a.exe Inside the construtor method.Local1 Step1: Inside the construtor method.function1 Inside function1 Address of obj2: 0x7ffffcb60 Inside the copy constructor method.function1 Inside the destructor method:function1 Step2 Address of demo2: 0x7ffffcbf0 Inside the destructor method:function1 Inside the destructor method:Local1The function returns and "demo2" is assigned the value of "obj2" via the copy constructor. Then the "obj2" is destroyed.
Exercise