13 The Important Trio
Jyoti Pareek
In this module we will discuss about important trio namely
- User Defined Destructor
- User Defined Copy Constructor
- User Defined Overloaded = Operator
But before we understand the need of this important trio, let us recall where these functions are used one by one.
Destructors
Destructors are called automatically when object goes out of scope.
Matrix operator+ (Matrix m)
{
Matrix resultant(row,col);
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
resultant.matrix[i][j] = matrix[i][j] + m.matrix[i][j]; return resultant;
}
As we know the scope of the object is the block in which it is declared and it’s lifetime is the duration for which the block is executing. The Matrix object resultant is declared inside the operator+ function therefore when execution of the function ends the object will go out of scope and destructor will be called for its destruction. Also the formal argument m, which is object of class Matrix will come into existence when control enters function operator+ and will be destroyed once the function ends, by means of invocation of destructor.
Copy Constructor
Copy Constructor is called:
- When we make copy of an object. Time t1(2,30,45);
Time t2(t1);
Here object t1 is constructed with the help of parameterized constructor initializing its data members namely hrs, minutes and seconds with values 2, 30 and 45 respectively. Object t2 is created as a copy of t1 and its construction is done by copy constructor.
- When we pass an object as an argument by value to a method.
Matrix operator+ (Matrix m)
{
Matrix resultant(row,col); for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
resultant.matrix[i][j] = matrix[i][j] + m.matrix[i][j]; return resultant;
}
Objects in C++ are passed by value Here object m will be created as a copy of actual argument and will be constructed by copy constructor.
- When we return an object from a method by value.
Matrix operator+ (Matrix m)
{
Matrix resultant(row,col);
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
resultant.matrix[i][j] = matrix[i][j] + m.matrix[i][j]; return resultant;
}
As we know when value is returned from function its temporary copy is created before the returning object goes out of scope and destroyed. This temporary copy is created as copy of returning object using copy constructor.
Overloaded Assignment Operator
Overloaded Assignment Operator is called when one object of the class is assigned to another object of the same class.
void main()
{
Matrix m1(2,2),m2(2,2), result(2,2);
m1.readMatrix();
m2.readMatrix();
result = m1+m2;
cout << endl << “The resultant matrix is ” << endl;
result.displayMatrix();
}
Here in the program the matrix object holding sum of matrix objects m1 and m2 is returned and assigned to matrix object result by means of overloaded assignment operator.
Let us define the complete Matrix class to understand this.
Class Matrix
/* Program : C++ program to add two matrices*/
#include<iostream>
#include<cmath>
using namespace std;
class Matrix
{
int row,col;
int matrix[10][10];
public :
Matrix()
{ row = 0; col = 0; } Matrix(int r, int c)
{
row = r; col = c;
for (int i=0; i<row; i++) for (int j=0; j<col; j++)
matrix[i][j] =0;
}
void readMatrix()
{
cout << endl << “Enter the matrix” << endl;
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
cin >> matrix[i][j];
}
void displayMatrix()
{
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
cout << matrix[i][j] << ” “;
cout << endl;
}
}
Matrix operator+ (Matrix m)
{
Matrix resultant(row,col);
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
resultant.matrix[i][j] = matrix[i][j] + m.matrix[i][j];
return resultant;
}
}; // end of class
void main()
{
Matrix m1(2,2),m2(2,2), result(2,2);
m1.readMatrix();
m2.readMatrix();
result = m1+m2;
cout << endl << “The resultant matrix is ” << endl;
result.displayMatrix();
}
In this program we have not defined destructor or copy constructor or overloaded assignment operator. Then
- How objects are destroyed when objects go out of scope ?
- How copy of objects is created when objects are passed as value in functions or when objects are returned from the function?
- How objects are assigned to object?
Well all this is achieved by means of default implementation of destructor, copy constructor and overloaded assignment operator provided by compiler. Now the question is when we have compiler supplied Destructor, Copy constructor and overloaded assignment operator, what is the need of defining them ourselves?
Need of user defined Important Trio
Compiler supplied Destructor, Copy constructor and overloaded assignment operator are good enough as long as the memory of data members are not constructed dynamically. But if we create a class which constructs memory for some of its data members dynamically, it becomes important/necessary to define all the three ourselves. Let us understand it with the help of matrix class constructed dynamically.
Matrix Class with Dynamic Construction
/* Program : C++ program to add two matrices*/
#include<iostream>
#include<cmath>
using namespace std;
class Matrix
{
int row,col;
int **matrix;
public :
Matrix(int r, int c)
{
row = r; col = c;
matrix = new int *[row];
for (int i=0; i<col; i++)
matrix[i] = new int[col];
}
void readMatrix()
{
cout << endl << “Enter the matrix” << endl;
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
cin >> matrix[i][j];
}
void displayMatrix()
{
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
cout << matrix[i][j] << ” “;
cout << endl;
}
}
Matrix operator+ (Matrix m)
{
Matrix resultant(row,col);
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
resultant.matrix[i][j] = matrix[i][j] + m.matrix[i][j]; return resultant;
}
}; // End of Class
void main()
{
int r,c;
cout << “Enter the no of rows of input Matrices : “; cin >> r;
cout << “Enter the no of columns of input Matrices : “; cin >> c;
Matrix m1(r,c),m2(r,c), result(r,c);
m1.readMatrix();
m2.readMatrix();
result = m1+m2;
cout << endl << “The resultant matrix is ” << endl;
result.displayMatrix();
}
This program will also produce the same result as that of the program with matrix class without dynamic construction of data member.
But the advantage of dynamic construction of matrix is that we can get only that much memory allocated as much as is needed. In earlier program no matter how small or big matrix we wish to store in object of Matrix class, the memory for 10 by 10 matrix will be allocated to the object. If we wish to store 2 by 2 matrix, we will be requiring 8 bytes but 100 bytes will be allocated for in the object for the matrix. And if we wish to store 15 by 15 matrix, we will not be able to do so as the dimension of array for storing the matrix is 10 by 10. In above program, with dynamic memory allocation for data member matrix, we can get only that much memory allocated as much is needed, removing memory wastage. But this will lead to other kind of memory leak. Let us understand this with following figure
Matrix m;
When we declare object m, 6 bytes (assuming integers and pointers occupy 2 bytes in our machine) will be allocate to m. The memory allocated for data member matrix dynamically, will be allocated outside the object in heap.
When the object goes out of scope memory for object will be released by default destructor, but memory allocated in heap will not be released, generating garbage
This problem can be overcome by creating destructor which releases dynamically constructed memory in heap before destroying the object. We can define destructor in our matrix class as follows
~Matrix()
{
for (int i=0; i<row; i++)
delete matrix[i];
delete matrix;
cout << endl << “I am Destructor” << endl;
}
Defining this destructor in our matrix class will solve the problem of garbage , as when object goes out of scope this destructor will be called which will release the dynamically created memory in heap before destroying the object.
Following is the program to add two matrices with dynamic constructor and user defined destructor.
/* Program : C++ program to add two matrices with dynamic constructor and user defined destructor*/
#include<iostream>
#include<cmath>
using namespace std;
class Matrix
{
int row,col;
int **matrix;
public :
Matrix(int r, int c)
{
row = r; col = c;
matrix = new int *[row];
for (int i=0; i<col; i++)
matrix[i] = new int[col];
}
void readMatrix()
{
cout << endl << “Enter the matrix” << endl;
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
cin >> matrix[i][j];
}
void displayMatrix()
{
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
cout << matrix[i][j] << ” “;
cout << endl;
}
}
Matrix operator+ (Matrix m)
{
Matrix resultant(row,col);
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
resultant.matrix[i][j] = matrix[i][j] + m.matrix[i][j]; return resultant;
}
~Matrix()
{
for (int i=0; i<row; i++)
delete matrix[i];
delete matrix;
cout << endl << “I am Destructor” << endl;
}
}; // End of Class
void main()
{
int r,c;
cout << “Enter the no of rows of input Matrices : “; cin >> r;
cout << “Enter the no of columns of input Matrices : “; cin >> c;
Matrix m1(r,c),m2(r,c), result(r,c);
m1.readMatrix();
m2.readMatrix();
result = m1+m2;
cout << endl << “The resultant matrix is ” << endl;
result.displayMatrix();
}
Here destructor will release dynamically constructed memory in heap before destroying the object. But as shown above, defining appropriate destructor solves the problem of memory leak, but the above code results in runtime error – access violation while reading some memory. What is the cause of this error? Let us try to understand the same.
Well the cause of the error is the compiler provided copy constructor. Let us understand it with the help of an example.
Have a look at the following declaration
Matrix m;
Matrix m2(m1);
This will create object m with default constructor, and m2 will be constructed from m1. Since we have not defined copy constructor, m2 will be constructed with compiler provided copy constructor. Compiler provided constructor performs shallow copy, which will creates copy of object byte by byte, therefore copy of object is made but copy of memory allocated for matrix in heap will not be made. Only the copy of address stored in the object will be made, which will cause both m1 and m2 to point to same memory in heap.
In our program the error arises when compiler provided copy constructor creates shallow copy of function argument and shallow copy of temporary copy of return object.
Matrix operator+ (Matrix m)
{
Matrix resultant(row,col);
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
resultant.matrix[i][j] = matrix[i][j] + m.matrix[i][j]; return resultant;
}
When these two objects go out of scope they will destroy the memory allocated in heap for matrix, which is also being pointed by actual argument and the object receiving the returned object respectively.
So what is the solution for this problem?
Well to solve this problem we can define our own copy constructor, which does deep copy as follows
Matrix(Matrix &m)
{
row = m.row;
col = m.col;
matrix = new int *[row];
for (int i=0; i<col; i++)
matrix[i] = new int[col];
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
matrix[i][j] = m.matrix[i][j];
}
}
Matrix Class with Dynamic Construction , Destructor and Copy Constructor
/* Program : C++ program to add two matrices*/
#include<iostream>
#include<cmath>
using namespace std;
class Matrix
{
int row,col;
int **matrix;
public :
Matrix(int r, int c)
{
row = r; col = c;
matrix = new int *[row];
for (int i=0; i<col; i++)
matrix[i] = new int[col];
}
void readMatrix()
{
cout << endl << “Enter the matrix” << endl;
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
cin >> matrix[i][j];
}
void displayMatrix()
{
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
cout << matrix[i][j] << ” “;
cout << endl;
}
}
Matrix operator+ (Matrix m)
{
Matrix resultant(row,col);
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
resultant.matrix[i][j] = matrix[i][j] + m.matrix[i][j]; return resultant;
}
~Matrix()
{
for (int i=0; i<row; i++)
delete matrix[i];
delete matrix;
cout << endl << “I am Destructor” << endl;
}
Matrix(Matrix &m)
{
row = m.row;
col = m.col;
matrix = new int *[row];
for (int i=0; i<col; i++)
matrix[i] = new int[col];
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
matrix[i][j] = m.matrix[i][j];
}
}
}; // End of Class
void main()
{
int r,c;
cout << “Enter the no of rows of input Matrices : “; cin >> r;
cout << “Enter the no of columns of input Matrices : “; cin >> c;
Matrix m1(r,c),m2(r,c), result(r,c);
m1.readMatrix();
m2.readMatrix();
result = m1+m2;
cout << endl << “The resultant matrix is ” << endl;
result.displayMatrix();
}
Well this time it is default implementation of overloaded = operator function provided by the compiler. If we do not overload = operator in our class, compiler provides overloaded = operator. The default implementation of overloaded = operator provided by compiler again does shallow copy. So how to overcome this problem. To overcome this problem we can define our overloaded assignment operator which does deep copy as follows
void operator= (Matrix m)
{
row = m.row;
col = m.col;
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
matrix[i][j] = m.matrix[i][j];
}
Now in our matrix class we have defined important trio namely destructor, user defined copy constructor amd user defined overloed =operator function, our program will work fine.
Matrix Class with Dynamic Construction, Derstructor, Copy Constructor and Overloaded = Operator
/* Program : C++ program to add two matrices*/
#include<iostream>
#include<cmath>
using namespace std;
class Matrix
{
int row,col;
int **matrix;
public :
Matrix(int r, int c)
{
row = r; col = c;
matrix = new int *[row];
for (int i=0; i<col; i++)
matrix[i] = new int[col];
}
void readMatrix()
{
cout << endl << “Enter the matrix” << endl;
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
cin >> matrix[i][j];
}
void displayMatrix()
{
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
cout << matrix[i][j] << ” “;
cout << endl;
}
}
Matrix operator+ (Matrix m)
{
Matrix resultant(row,col);
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
resultant.matrix[i][j] = matrix[i][j] + m.matrix[i][j];
return resultant;
}
~Matrix()
{
for (int i=0; i<row; i++)
delete matrix[i];
delete matrix;
cout << endl << “I am Destructor” << endl;
}
Matrix(Matrix &m)
{
row = m.row;
col = m.col;
matrix = new int *[row];
for (int i=0; i<col; i++)
matrix[i] = new int[col];
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
matrix[i][j] = m.matrix[i][j];
}
}
void operator= (Matrix m)
{
row = m.row;
col = m.col;
for (int i=0; i<row; i++)
for (int j=0; j<col; j++)
matrix[i][j] = m.matrix[i][j];
}
}; // End of Class
void main()
{
int r,c;
cout << “Enter the no of rows of input Matrices : “; cin >> r;
cout << “Enter the no of columns of input Matrices : “; cin >> c;
Matrix m1(r,c),m2(r,c), result(r,c);
m1.readMatrix();
m2.readMatrix();
result = m1+m2;
cout << endl << “The resultant matrix is ” << endl;
result.displayMatrix();
}
Well, appropriate implementation of important trio in our class makes our program work !!!
Summary
Compiler supplied Destructor, Copy constructor and overloaded assignment operator are good enough as long as the memory of data members are not constructed dynamically. But if we create a class which constructs memory for some of its data members dynamically, it becomes important/necessary to define this trio ourselves appropriately in our class.
you can view video on The Important Trio |
Additional References
1) Stanley Lippmann, “C++ Primer”, Pearson Education.
2) Bjarne Stroustrup, “The C++ Programming Language”, Pearson Education.
3) Scott Mayer, “Effective C++”, Addison Wesley.
4) Bhushan Trivedi , Programming with ANSI C++, 2/e , Oxford University Press.
5) Yashavant P. Kanetkar “Let Us C++” , Bpb Publications.
6) Abhiram G. Ranade, “An Introduction to Programming through C++” , McGraw Hill
7) Ellis and B. Stroustrup “Annotated C++ Reference Manual”, http://www.stroustrup.com/arm.html
8) Herbert Schildt, “Complete Reference C++”, McGraw Hill Publications.
9) Ashok Kamthane, “Object Oriented Programming with ANSI and Turbo C++”, Pearson Education
10) E Balaguruswami, “Object Oriented Programming With C++”, Tata McGraw Hill
11) “C++ FAQs”, Pearson Education.