Classes
Contents
Copy Constructors id="Copy Constructors"> Copy Constructors
Sometimes an object is created using another object. In this case a special kind of constructor called a "Copy Constructor" gets called. 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" . Thus the "ptr" of "demo2" will take on the value of "ptr" of "demo1" The object "demo1" gets destructed and releases the memory for "ptr" . The object "demo2" 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
We are going to construct our own string class called "mystring" . The below files have been written out for you: "main.cpp" , "mystring.h" , "mystring.cpp" . Add a constructor without any arguments that sets the ptr to null . Add the copy constructor to the mystring class and run the program. To compile: g++ main.cpp mystring.cpp
File: mystring.h
#ifndef MYSTRING_H #define MYSTRING_H #include <string.h> #include <iostream> using namespace std ; class mystring { private: char* ptr ; public: mystring( const char* str1 ) ; ~mystring() ; void print() ; }; #endif
File: mystring.cpp
#include "mystring.h" mystring::mystring( const char* str1 ) { cout << "Inside the constructor." << endl ; if ( str1 == NULL ) ptr = NULL ; else { int len = strlen( str1 ) + 1; ptr = new char[ len ] ; strcpy ( ptr, str1 ) ; } } mystring::~mystring() { cout << "Inside the destructor." << endl ; if ( ptr != NULL ) delete[] ptr ; } void mystring::print() { cout << ptr << endl ; }
File: main.cpp
#include "mystring.h" int main() { mystring str1( "Marvin" ) ; mystring str2 = str1 ; str1.print() ; return 0 ; }