There comes a time in a life of every genetic engineer when
he needs to create a clone of a sheep.
I assume that's like a hello world of genetic engineering, but I might be wrong.
Creating a clone in our program should be straightforward.
I can declare the new sheepClone variable and initialize it
with the already existing sheep subject.
I didn't want to complicate things by validating unique IDs, so
let's just assign some other ID to the clone.
And finally, let's print the clone's data to see if our
cloning experiment was successful.
Let's compile this.
It seems that the clone was initialized correctly, both of
the sheep have the same genetic sequence.
However,
something also went terribly wrong, since we also got this
Segmentation fault, but let's ignore this error for a second
and continue like nothing's wrong.
Since we already created a clone,
let's modify its genetic sequence by changing the first
two chemical bases to something else.
I will change the first base to guanine and the second one to
cytosine. After the modification, I will again print the data
from both subjects to compare them.
Let's now recompile this program.
It looks like the modification worked, the clone has a
different sequence, but it seems like the original sheep was
also affected. By modifying the clone, we also changed the
genetic code of the original subject.
This is one of the problems we have to face when working with
pointers to dynamically allocated resources.
To examine the root of this issue, let's start from this declaration statement.
When you initialize a new object at the point of declaration by using this
equals character, C++ will apply implicit conversion. It will implicitly
convert this whole statement to a constructor call.
So, this statement is actually completely identical to this one.
We are calling the subject's constructor, which accepts another subject
as a parameter, but we didn't define this constructor,
we only have two constructors and none of them accept an object of this type.
However,
this will still work, because the compiler will
define this constructor by default.
This predefined constructor looks something like this.
It takes in a reference to an object of the same type, and
then it uses that reference to copy all of its members to
our newly instantiated one.
In other words,
this constructor creates a clone of another object by literally
taking all of the values from its data members.
The reason why compiler defines this constructor is because we want to make our
user‑defined classes work similar to primitive data types.
If you declared an integer by initializing it with another integer variable,
you would expect that the value gets copied over from one variable to another.
The same expectation stands for the user‑defined classes, and creating a copy
from the object of the same type is really common practice.
This is why this constructor is known as the copy constructor, because it
literally creates a copy of another object. In the first module, I said that
whenever you pass an argument to the function by value,
that function will create a local copy of the value and
store it inside of the parameter variable.
So the reason why we pass this other object by reference
isn't just for optimization purposes,
it is also because it makes no sense to pass this other object by value.
Imagine if we did that, if this was not a reference,
what would happen? Since this object is passed by value, that means
that this function needs to create a copy of the sheep object and
store it inside of this other parameter.
But the exact process of creating a copy is defined by this copy
constructor, so we are defining a copy constructor by invoking that
same copy constructor, and that makes no sense.
This is also why a copy constructor is important, because it's invoked whenever
you pass the objects by value. We also make this reference constant because we
have no reason to change this original object.
There is also another purpose for doing this, but we'll talk about that later.
This definition of a copy constructor is more than enough for some classes,
but not for this subject class, because we are using a pointer
which points to the DNA object on the heap.
So, first we create the original sheep object, which will allocate a new
DNA object on the heap, and we will store its location inside of the
sample pointer. Then, we declare the clone,
bypassing this original sheep to the copy constructor.
This copy constructor makes a copy of values from all of the original
sheep members, and this is where the problems begin. Copy constructor
will literally copy the value from the sample pointer of the original
sheep to the sample pointer of the clone.
So both of these sheep have the sample pointer which
points to the same place in memory.
The clone didn't allocate another DNA sequence on the heap,
it just points to the same address in memory.
When we modify the DNA sequence through the sample pointer of a clone,
we are actually modifying the DNA object allocated by the original sheep.
And, since both sheep own that same DNA,
the original sequence will be changed too, because
there is only the original sequence.
This is the same reason why we get the segmentation
fault at the end of the program.
Once the scope of the main function is over,
all of the subjects will be destroyed, and as we know, before that
happens, the program will first call their destructors.
So when the program calls the destructor of the original sheep,
this DNA sequence will be deallocated from the heap.
We did that to prevent a memory leak.
However,
the sample pointer from the clone is still pointing to this non‑existent DNA
sequence, so when its destructor gets called, the program will try to deallocate
that DNA again, and since it doesn't exist anymore,
we will get a segmentation fault.
So now you see why implementing RAII in classes is more complicated
than just allocating and deallocating memory.
But now that we know what the problem is, the solution is simple.
We just need to allocate another DNA object for the clone and then copy the
sequence from the original sheep to that new DNA. To do that, I will pass the
DNA object itself by dereferencing the sample pointer.
And, we are allowed to do that because DNA class also has its
own copy constructor, defined by the compiler.
I can hover over this expression to show you. This
constructor does look familiar, right?
It is the DNA's copy constructor which will literally copy the
code array from this DNA object. Since I already defined other
constructors with the member initializer list, let's do the same
for this copy constructor.
You don't have to, but I think that this makes our intention clearer.
Okay,
let's see if this fixed something, and it seems like it did. We no longer
get a segmentation fault, and the sequence of the original sheep is
preserved after the modification of the clone.