Copy semantics is the term in C++ which describes how to create
copies of an object that belongs to the certain class.
Before we implemented the copy constructor,
our object class would do a shallow copy,
which means that it would just literally take the value of a
pointer and ignore the resource that it points to.
By introducing the copy constructor, we forced the object
to make a deep copy instead. But copy constructor is just
one part of the semantics.
What if instead of initializing this sheep clone at the declaration,
I just declared it as an empty subject and then decided to later assign
this original sheep to the clone object? This statement looks really
similar to the one that invoked the copy constructor.
However, if I compile this,
you can see that we are again introduced with the same problems
we had before defining the copy constructor.
This is because our new statement does not invoke the copy constructor,
and that actually makes sense, because as you can see,
I already declared the sheep clone object on this line,
which means that the default constructor was called.
So when we get to this statement,
this clone object is already instantiated, and copy constructor,
just like any other constructor, is used only when we need to create
a new object. Because of that, this statement will actually use the
assignment operator to make a copy.
Just like a copy constructor,
assignment operator is automatically defined by the compiler, and
its default behavior is to do a shallow copy.
So, this statement is actually equal to this one.
The assignment operator works like a function of the clone, which takes in the
original sheep object as an argument. To fix this problem,
we again have to redefine the copy behavior to make a deep copy.
Since the assignment operator basically makes a copy of an object, in this
context it is also known as a copy assignment operator.
We need both the copy constructor and the copy assignment
operator to fully implement copy semantics.
The default overloaded operator looks just like a copy constructor.
It takes in a reference and it makes a copy of all the members.
Let's change it to make it work more like our own redefined copy constructor.
This code alone will cause a memory leak, because we are reassigning
this sample pointer to a new object on the heap.
It's okay to do this inside of the copy constructor, but in this case, the
object that calls this operator already exists, which means that its
sample pointer is already pointing to something.
If we just reassign this pointer,
the old DNA object that the pointer was pointing to will still stay in
memory and we won't be able to release it because we don't have its
memory address. To prevent this, we can first delete the current
object from the heap before reassigning the pointer, and this would
work, but we can do better.
Let's see what's actually happening here.
We are first deleting the old DNA from memory and then allocating space for
the new one on the heap, and then we pass the DNA object from the other
subject to invoke the DNA's copy constructor,
which will just copy all of the elements from the code array.
So, why don't we just skip this deleting and allocating new memory, and
instead just do what this copy constructor does? I will first include the
algorithm library to get access to the copy function.
We'll need it for making a copy of an array.
I will also use the define preprocessor directive to create
a macro for the size of the DNA code.
This is because we'll need this number to make a copy of an array,
and since we need to use it in more than one place,
I like to define a macro to make the code more maintainable. In case you
don't know, preprocessor directive works kind of like a search and replace
command. Before the actual program gets compiled,
it will search for all of the places where this DNA code size is written,
and literally replace it with the number we specified.
This is another way to define the size of static arrays, because pre
processor directives execute before compilation,
hence the size is known at compile time.
I can now use the standard copy function from the
algorithm library to make a copy of the array.
The first two arguments represent the beginning and the end of the array,
which we want to copy. As we know, the name of the array itself
is the memory address of the first element.
The last memory address can be accessed by adding the
number of elements to the starting address.
Note that by doing this,
we are not actually getting the address of the last element, but the address of
the first free byte in memory after the end of the array.
Finally, the last argument is the start of the array
which will accept the copied elements.
This code will improve the performance of the assignment
operator, because we don't have to allocate new space on the
heap, we are overwriting the existing DNA code.
Before we wrap this up,
there is still one thing that we need to add to this operator definition.
A common practice of assignment operators is to return the
reference to the current object itself.
This is because of the possibility that someone might try to do a
multiple assignment, which could look something like this.
We expect both of these subjects to contain the same value taken
from the object on the right side of the statement.
But this would not happen with our current implementation,
because this statement is equal to this one.
The first assignment operator would work, but since the operator returns void,
this subject would be assigned to nothing. To fix this,
I will return the modified object itself, the object
that makes a call to this operator.
We didn't talk about "this" keyword yet, because I don't like to use it
unless I have to, and I think that this operator is the perfect use case
where using "this" is not redundant. When you use the this" keyword inside
of the class's non‑static member function definition, you are actually
getting a pointer to the current instantiated object which makes a call to
this function.
So in our example, the sheep clone object is calling
this assignment operator function.
So, the keyword "this" inside of the scope of this function will be a pointer
to the clone sheep itself. To access data members of this object, we can just
use their identifier, and the compiler will know that we want to access the
sample pointer of the current object.
But if we want to be explicit, we can also access the sample pointer like this.
Since "this" is a pointer to a current object,
then we can use the arrow operator to get the sample pointer of that object.
Some people prefer this practice, but I find it redundant and
only use "this" keyword if it makes sense in the specific
context, and in this context, it does make sense. Since we want
to return the object itself,
I can use the dereference operator to access it from the "this" pointer.
Finally, we also have to account for the case when someone
tries to assign the same object to itself.
We can check this by verifying if the pointer to the current
object has the same address as the other object.
If it does,
I will immediately return the object itself, because executing the
rest of the function would be a waste of performance.
The operator does not return void anymore, instead, it will
return a reference to a current object.
So, in other words, a reference to a subject. And that's it.
Now,
let's recompile the program to see if it works. No more
segmentation faults, so it seems that we did it, we fully
implemented copy semantics for the subject class.