I think you might have landed after the Constructor & Destructor post.
When you copy a container such as a linked list, you probably want a deep copy, so new nodes need to be created and only the data copied over. The next
and prior
pointers in the nodes of the new list should refer to new nodes you create specifically for that list and not the nodes from the original list. These new nodes would have copies of the corresponding data from the original list, so that the new list can be considered a by value, or deep copy.
Here is a picture depicting the differences between shallow and deep copying:
Notice how in the Deep Copy portion of the diagram, none of the nodes point to nodes in the old list. For more information about the difference between shallow and deep copies, see the Wikipedia article on object copying.
To automate the deep copy, we use copy constructor. Compiler treats this as constructor with special parameter.
Why Copy Constructor?
#include <iostream> using namespace std; // Box Class class box { private: int length; int* breadth; int height; public: // Constructor box() { breadth = new int; } // Function to set the dimensions // of the Box void set_dimension(int len, int brea, int heig) { length = len; *breadth = brea; height = heig; } // Function to show the dimensions // of the Box void show_data() { cout << " Length = " << length << "\n Breadth = " << *breadth << "\n Height = " << height << endl; } // Parameterized Constructors for // for implementing deep copy /* box(box& sample) { length = sample.length; breadth = new int; *breadth = *(sample.breadth); height = sample.height; }*/ // Destructors ~box() { delete breadth; } }; // Driver Code int main() { // Object of class first box first; // Set the dimensions first.set_dimension(12, 14, 16); // Display the dimensions first.show_data(); // When the data will be copied then // all the resources just copied // by shallow copy box second = first; // Display the dimensions std::cout << "\nSecond:\n"; second.show_data(); std::cout << "\nChange the Values(breadth & height) of first object"; first.set_dimension(12, 16, 17); std::cout << "\nFirst:\n"; first.show_data(); std::cout << "\nSecond:\n"; second.show_data(); return 0; }
Click here to debug this.
free(): double free detected in tcache 2
Length = 12
Breadth = 14
Height = 16
Second:
Length = 12
Breadth = 14
Height = 16
Change the Values(breadth & height) of first object
First:
Length = 12
Breadth = 16
Height = 17
Second:
Length = 12
Breadth = 16
Height = 16 |
I don't want the second object's breadth is getting changed.
For each object, destructor is called. So, two.
Note: Compiler provides the default that does shallow copy. If we need deep copy, define your own copy constructor.
Copy Constructor
A copy constructor is used to initialise a previously uninitialised object from some other object's data.
The copy constructor is a constructor which creates an object by initializing it with an object of the same class, which has been created previously. (Must Read *)
The copy constructor is called whenever an object is initialised by another object of the same type. (Must Read *)
The copy constructor is used to − Initialize one object from another of the same type.
Copy an object to pass it as an argument to a function.
class Maths {
Maths();
Maths(const Maths&);
};
A(const A& rhs) : data_(rhs.data_) {}
A aa;
A a = aa; //copy constructor
Example for copy constructor in three ways: (Must Read *)
1. Initialisation
Base obj1(5);
Base obj2 = obj1; //calls copy constructor
2. Function Argument Passing
doNothing(obj2); where obj2 is of type Base.
3. Function Return
return obj3;
Inside function such as Base f() where obj3 is of Base which has no move constructor.
Example 1: (Must Read *)
#include <iostream>
struct A
{
int n;
A(int n = 1) : n(n) { std::cout << "A Constructor\n" << n << std::endl; }
A(const A& a) : n(a.n) { std::cout << "A Copy Constructor\n" << n << std::endl; } // user-defined copy ctor
};
struct B : A
{
// implicit default ctor B::B()
// implicit copy ctor B::B(const B&)
};
struct C : B
{
C() : B() { std::cout << "C Constructor\n"; }
private:
C(const C&); // non-copyable, C++98 style
};
int main()
{
A a = 8; // copy initialisation
A a1(7); // direct initialisation
A a2(a1); // calls the copy ctor
B b;
B b2 = b;
std::cout << "B is done\n";
A a3 = b; // conversion to A& and copy ctor
std::cout << "Polymorphic behavior\n";
volatile A va(10);
/* error: binding reference of type 'const A&' to
* 'volatile A' discards qualifiers
*/
//A a4 = va; // compile error
C c;
/* error: 'C::C(const C&)' is private within this context */
//C c2 = c; // compile error as the copy ctor is private
return 0;
}Output:
Click here to debug the above program.
const ensures that original object is not modified.
It is necessary to pass as reference ( & ) and not by value because if you pass it as
value its copy is constructed using the copy constructor function. This means the copy constructor would call itself to make copy but we are trying to implement the copy. This process will go until the compiler runs out of memory. Check the below program.
#include <iostream>
class Car
{
int m;
public:
Car(int i)
{
m = i; // It calls Car(int i) again
}
Car(Car j)
{
m = j.m; // It calls Car(int i)
}
int getVal() {
return m;
}
};
int main () {
Car a(1);
std::cout << "a's value " << a.getVal() << std::endl;
Car b(a);
std::cout << "b's value " << b.getVal() << std::endl;
return 0;
}
How the compiler make this work? It goes into endless loop. Because there is no function has been established to copy the values. Click here to debug further.
Click here to understand that pass-by-value create a copy. Passing reference doesn't
create any copy in the function rather it passes the same address.
(Must Read *)
Example 2: (Must Read *)
#include<iostream> class Sample { int id; public: Sample(){ id = 1;} //default constructor with empty body Sample(Sample &t) //copy constructor { id=t.id + 1; } void display() { std::cout<< std::endl<<"ID="<<id; } }; static Sample* obj1 = new Sample(); int main() { std::cout << "\nobj1 - heap"; obj1->display(); Sample* obj2 = new Sample(*obj1); std::cout << "\nobj2 - heap"; obj2->display(); Sample obj3(*obj2); std::cout << "\nobj3"; obj3.display(); Sample obj4(obj3); std::cout << "\nobj4"; obj4.display(); obj1= new Sample(obj4); std::cout << "\nobj1 - heap"; obj1->display(); return 0; }
Click here to debug.
Copy Assignment Operator
An assignment operator is used to replace the data of a previously initialized object with some other object's data.
A& operator=(const A& rhs) {data_ = rhs.data_; return *this;} A aa; A a; a = aa; // assignment operator
You could replace copy construction by default construction plus assignment, but that would be less efficient.
Maths& Maths::operator=(const Maths&);
Maths& operator=(const Maths&);
Maths& operator+(const Maths&, const Maths&);
boolean operator ==(const Maths&, const Maths&);
This is also called operator overloading. It is a type of polymorphism in which an operator is overloaded. Operator overloading function can be used as a class or friend function too.
In copy assignment, you need to destroy the existing object before copying into it. When an object points to 1 million copy, and you are assigning to other object, you are consuming more memory.
Example for assignment operator:
Base obj1(5); //calls Base class constructor
Base obj2; //calls Base class default constructor
obj2 = obj1; //calls assignment operator
Technicalities
Assignment 1:
-------------
Maths m1 = m2;
Assignment 2:
-------------
Maths m1;
m1 = m2;
Assignment 1 calls the copy constructor.
In assignment 2, m1 object is created with default constructor, and the next line calls the assignment operator where m2 is copied into the existing object m1.
Example 2:
#include <iostream>
using namespace std;
class X {
private:
int* p_;
public:
X() : p_(new int) {
cout << "Default constructor" << endl;
}
~X() {
cout << "Destructor" << endl;
int* q=p_;
p_ = nullptr;
delete p_;
}
X(const X& other) {
cout<<"copy constructor"<<endl;
};
X& operator = (const X& other) {
cout <<"assignment operator"<<endl;
return *this;
}
};
void doNothing(X x) { // line 30 (when the function is entered)
cout<< " Line 31 After "<<endl;
cout << "I do nothing!"<<endl;
return; // line 33 (when the function exits)
}
int main() {
cout<< " Line 38 Before "<<endl;
X* a = new X(); // line 3
cout<< " Line 38 After "<<endl;
X b; // line 40
cout<< " Line 40 After "<<endl;
b = *a; // line 42
cout<< " Line 42 After "<<endl;
doNothing(b);
cout<< " Line 46 Before "<<endl;
delete a; // line 46
cout<< " Line 46 After "<<endl;
return 0;
} |
| Line 38 Before
Default constructor
Line 38 After
Default constructor
Line 40 After
assignment operator
Line 42 After
copy constructor
Line 31 After
I do nothing!
Destructor
Line 46 Before
Destructor
Line 46 After
Destructor |
Click here to debug
User defined classes (and types) are no different than built-in types. So
Employee emp; // allocated in stack
Employee* emp = new Employee(); // allocated in heap
Example 3:
#include <iostream> using namespace std; class veh { public : int x; veh(int xx) { cout << " Parameterised Constructor : " << xx << endl; x = xx; } veh(const veh& v) : x(v.x) { cout << " Copy Constructor : " << v.x << endl; } }; int main() { int i = 1, j = 2; /* Variable copy will act as value semantics */ j = i; i = 3; cout << "i : " << i << " j : " << j << endl; veh a1(); /* Doesn't take any existing iputs */ veh a2(9); cout << " a2's value " << a2.x << endl; veh a3(a2); cout << " a3's value " << a3.x << endl; return 0; }
Output:
this
It is a pointer to the current object, and can be used only in the member functions. This can also be used like other pointer.
Maths *m1 = this;
if (m1 == this) {
.......
}
The Big Three
Without relying on compiler supplied defaults, If we explicitly define one of the below three, we must define the others as well.
- Destructor
- Copy Constructor
- Copy Assignment Operator
This is predominantly needed when dynamic memory is allocated. Though it may seem cheesy, it will save you from interesting troubles.
The Efficiency Problem (due to copy)
Maths quadraticEq(m2);
Maths m1 = quadraticEq(m2);
When quadraticEq returns, its value is copied into temporary. Then again it is copied into m1 by =.
Here, the temporary is not needed anymore, so moving the temporary to m1 would be much better. Before C++11, we can avoid these kind of unnecessary copying.
Ways to Reduce Copy
Compiler optimisation
Copy elision; return value optimisation
Move semantics
- Classes can specify how objects are moved in move constructors and move assignments.
- Compilers may deduce when moves are preferred over copies.
- Programmers can specify moves using std::move
Reference
SO link2 - Copy ctor reference
Copy initialisation & direct initialisation
copy assignment operator in template
Comments
Post a Comment