They are to do with what happens when we initialise an object from another of the same type ... and a bit more... We will leave Move operations until later!
In C++ copying occurs in two forms:
Implicit : The compiler makes a copy of the contents of an object into a new memory location of the same type.
Explicit : The programmer specifies that a particular object's values are to be transferred to the memory of another object, this is copy assignment.
This is defined whether we specify other constructors or not, if it’s needed by the compiler.
It’s invoked by the compiler if we ...
Declare an object and use it to initialize another object of the same class.
Point pointA(pointB);
Point pointA = pointB;
Pass an object by value to a function.
float getDistance(point pointA);
Return objects by value from functions.
point getPosition();
At least roughly . . . some compilers behave a bit differently.
class Point {
int x;
int y;
};
The default/synthesised copy constructor makes a member-by-member copy of the non-static data members of the objects.
The copying implied by ...
Point pointA(pointB);
... will be
pointA.x(pointB.x);
pointA.y(pointB.y);
This might be okay, and it is for Point, but we run into problems if we have links to content outside the object.
Effectively an instance of Point is stored as two contiguous integers, and we are just copying a chunk of memory to somewhere else.
But if we have pointers or dynamic memory, this type of copying may not work correctly, it may be too shallow.
You may have come across the idea of shallow and deep copying in Java in CSIT121.
What happens with this more complex class, with a pointer in it?
It may as well have been a struct.
#include <iostream>
using namespace std;
class Pointier {
public:
int *x;
Pointier(){x = new int(5);}
void display(){cout << *x << endl;}
};
int main()
{
Pointier PointA;
Pointier PointB(PointA);
PointA.display();
PointB.display();
*(PointB.x)=3;
PointA.display();
PointB.display();
}
In deep copying we make sure we create new resources to cover the resources outside of the original object.
Pointier(const Pointier& point) {
x = new int(*(point.x));
}
If we have an array we want to make sure we copy all the elements of the array, and assign resources for them, rather than just copying a pointer to a location.
If we don’t carry out this type of deep copy we can be left with a dangling pointer.
A dangling pointer is one that refers to memory that no longer holds an object.
It’s pretty much the same as not having a pointer initialised.
X& operator=(const X&);
Similar but this is an overloaded operator now, and the copying implied by:
point pointA,pointB;...;
pointA = pointB;
... will be
pointA.x = pointB.x;
pointA.y = pointB.y;
We have the same problem as before and the solution is the same, define the appropriate copy assignment operator.
Note the difference between using the copy constructor in
Point pointB;
Point pointA = pointB;
... and the copy assignment in ...
point pointA,pointB;...;
pointA = pointB;
Assignment operators typically return a reference to their left-hand operand, as this one does.
This is consistent with the built in types.
Note that the default operators are being used in the default/synthesised copy assignment.
We will get to general overloaded operators soon.
The textbook notes it’s important to treat self-assignment carefully.
Make sure that you don’t accidentally remove all copies.
Way back we mentioned that we can tell a C++11 compliant compiler to generate the synthesized default constructor for us as follows ...
energyBill() = default;
A syntactically similar C++11 concept is that of deleted functions...
... which we can make use of if copying and copy-assigning don’t make sense.
Deleted functions are declared but otherwise cannot be used, but they block a synthesised version being generated by the compiler.
In the example below, from the textbook, we explicitly state that we want to use the default constructor and destructor, and that we don’t want to allowing copy or copy assignments to be made.
Struct NoCopy {
NoCopy() = default;
NoCopy(const NoCopy&) = delete;
NoCopy &operator=(const NoCopy&) = delete;
~NoCopy() = default;
// other members
};