Standard array class is a thin wrapper over a regular static array.
However,
the replacement for dynamic arrays from the standard
library is a lot more powerful and flexible.
I am, of course, talking about the famous Vector class.
So let's first include the vector library.
The next few lessons will be dedicated to working with vectors,
and I want to do these demonstrations by using our own Subject class.
The last modification to this program was the
introduction of the standard array.
So let's pick up where we left off.
I will add a special message to all of the subject constructors,
because I want to show you what vector does with the objects in the background.
I only need to add a message for the default and parameterized constructor.
Other constructors already have a message from the previous demonstrations.
Now, let's go down to the main function.
Here, I created the dynamically allocated array of subjects on the heap.
The size of the array is taken from the variable at runtime.
However, in modern C++,
there is a better solution for allocating arrays on the heap.
We can use the std::vector class.
This class is templated,
so we need to pass the data type of the array elements as a template argument,
and we will also pass the size of the array to the constructor.
Let's compile this now.
As you can see, the default subject constructor was invoked three times,
which makes sense, because the size of the array is three.
So now we have a dynamic array on the heap with three empty subjects.
The first big benefit is the memory management.
Vector will de‑allocate memory from the heap when it goes out of scope.
So memory leaks are not a problem anymore.
We don't have to use new and delete operators.
The name for this class is not inspired by a concept of a
mathematical vector; it is just the name that library
developers chose to avoid using the word array,
because until the introduction of standard arrays,
array would usually mean a raw C‑style array.
Now let's initialize these three subjects with some values
by using the square brackets operator.
Vectors can be used just like standard arrays.
You can use the square brackets operator or the at function to
access the elements from the internal array.
Let's keep this as simple as possible.
I don't care about these DNA sequences anymore.
So you can pass any sequence you want in here.
We will only focus on working with subject IDs; they will help
us in identifying one subject from another.
Now I want to check if these subjects were initialized successfully.
So I will pass the whole vector to the printSubjectIds function.
Of course, we need to define this function first, so let's do that.
I will pass the vector as a constant reference,
because I don't want to make a local copy.
Notice that we don't have to template this function,
because the size of the vector is not important here.
We don't have to provide it as a template argument.
Vectors will remember their size, even in the scope of another function.
So the size function, which gives us the number of elements,
is also available here.
But we won't need it here,
because I will use a range‑based for loop to print the ID of each element.
Notice that I'm using this iterator variable as a reference.
We learned in the previous lesson that this is more efficient.
Okay, it is time to compile this to see what we get.
We already know what these first three lines do.
After that, we have a parameterized constructor,
followed by a move assignment operator,
and this actually happens three times in a row.
So what's going on?
Well,
these parameterized constructors are actually called when we
instantiate a new subject in this main function.
Once the subject is created,
we assign it to the specific subject from the vector.
This is an assignment operator, and since this expression is a prvalue,
the move assignment will be used instead of a copy assignment.
And that's great.
Imagine if we didn't define move semantics; then each one of these
subjects would make a deep copy to the object on the vector.
So once all of these subjects are moved from the
main function scope to the heap,
we can see that objects on the vector are now correctly initialized,
but vectors are much more powerful than simple,
dynamically allocated arrays.
Let's say that in the middle of the program,
we allow the user to pick a new size for the array,
or maybe we just have a need to store more subjects.
Vectors can solve this problem, because they are resizeable.
We can use the resize function to make this vector longer.
Once I do this,
the vectors should be able to store five elements instead of three.
But you have to be careful with this,
because the internal array managed by the vector
did not magically become bigger.
Let me compile this to show you.
We are interested in this part of the output, after the list of subject IDs.
You can see that after we resize the vector,
five different constructors were used in the process.
As I said, this is not magic.
The vector will allocate a new array on the heap with the bigger size,
in this case, the array with five elements.
Since this array has two more elements than the previous one,
it will use a default constructor to create two new
subjects at the back of the array.
After that,
it will move all of the subjects from the previous array to
the same position inside of the new array,
and then the old array will be de‑allocated from the heap.
We are only left with this new one.
This is why this list has two new subjects with the ID of 0.
It's great that we defined move semantics for the Subject class,
because if we didn't,
this would make a deep copy of three subjects from the previous array.
Each subject has 60MB of memory allocated for the DNA sequence,
which means that this resizing alone would cost us the
performance of allocating 180MB of new memory and making a copy
from the old memory to fill that space.
You can now see how the move constructor is a really important
part of defining classes with dynamic resources.