Hi, and welcome back to the course about pointers and arrays in C++. My name is
Mateo, and in this final module, we will go over standard library abstraction
mechanisms provided by the modern C++ language.
Now that we know how row pointers and arrays work, we will be able to choose
the correct standard library class for a specific purpose.
Let's start by talking about static arrays. I created a wrapper class
for fixed‑sized arrays, and I named myArray. Before talking about the
Array class from the standard library, I wanted to show you the idea
behind its implementation.
The real standard Array class is a lot more sophisticated.
I just defined some of the most important
functionality in the simplest way possible.
As you can see, this class is templated. And that is, in my
opinion, the best reason to always use it instead of our row
c‑style array. In case you're not really familiar with
templates, let me show you what I mean.
This templated class accepts two parameters,
the data type of array elements and the size of the allocated array. By
size, I, of course, mean the number of elements, and this should
actually be size_t instead of int, but as I said,
let's keep it simple.
So, what happens when I try to instantiate an object from this class?
I will create three arrays by providing the data type and size to
the template instead of using square brackets.
Now let's imagine that I started the compilation of this program.
The compiler will see that we have three different versions of this array class,
one with five integers, one with three integers, and one with four doubles.
Any combination of different data type and size will be treated as a different
class from the perspective of the compiler. This is because the compiler will
actually use the template to create a version of this class in the code for
each combination of template arguments.
For example, a new definition of this class will be created by
replacing this T typename with int and size with 5.
This will allow us to make the first array object, which contains five integers.
But if we did this by hand, we would have to write a version of this
class for every possible template argument combination.
This way, the compiler will do it for us.
It will create two more versions of this class in code,
one with three integers and one with four doubles.
I wanted to make sure that you're familiar with what templates actually
do because now you can see that the size of each array is stored in this
template argument. And since template arguments are just injected into
another version of this class,
that means that this size value will only exist in the code.
In other words, it doesn't need to be stored in memory.
That means that the array instantiated by this class and a regular
array need the same amount of space in memory.
They just need to store the array itself.
However, by using templates,
we can also remember the size of the array without storing it in memory, and
also, we can attach a lot of functionality to the class itself.
For example,
you can see that I defined the size function, which just
returns the template argument itself. And since we know the
size, now I can do something like this.
The add function works like a square bracket operator.
It takes in an index and returns a reference to an element
from the array at that specific position.
However, unlike this operator,
the at function will first check if the index is out of bounds, and
we can do that since now we know the array size.
The array class from the standard library has a similar
function, but instead of doing this, it will throw an
exception and do something more sophisticated.
I also defined two constructors.
The default one will just create an empty array.
The other one will accept the initializer list as a
list of elements of the data type T.
I included the initializer_list library to achieve this functionality.
Once I have this list, I can look through it and assign its
values to the elements of our internal array.
We will talk about this kind of for loop in the next lesson. And
finally, I created a simple fill function, which will just
initialize the whole array with a single value.
Now that you're familiar with the class, let's use it to see if it works.
I will only leave this array of three integers and
initialize it to some random values.
This will work because of the initializer_list constructor.
I will then use the overloaded square brackets operator to
change the value of the first element.
This operator will pass the index to the internal array from the object
and return a reference to the element at that position.
Now let's print out all of the elements by using the
size function in the for loop.
We don't need to define some external constant
expression to store the size of the array.
After this, I will change the value of all elements with the fill function.
Let's set them all to 3.
I will print out all of the elements again to see if
the fill function did the job.
And now, just to see if it works, I will try to access the
element at the index five, but I will use the at function,
which has balance checking.
Okay, let's compile this, and it seems that everything works.
We managed to use the size function to print the array,
square brackets to update this value, and fill function
to reinitialize the elements.
And we also got this warning message because index 5 is out of bounds.
This is my simple implementation of an array class.
Now let's use the real standard array container from the standard library.
We first need to include the array header. And now, we can replace the
myArray class from the main function with the std::array class.
This class takes in the same amount of template arguments, so this
declaration is fine. Since myArray class was inspired by this standard
array, everything should still work in the same way, and it does. The
output is equivalent to the previous one. This program termination was
caused by the out_of_range exception because we tried to access the element
at index 5. And like I mentioned,
the at function in the real array class is more sophisticated than
mine. As you can see, the array class has a lot more functionality than
a simple c‑style array, so you should use it in almost all situations
unless you have a special reason not to because maybe you're working
with an old library or legacy code, which returns a simple row array,
like this one.
If something like this happens, fortunately you can easily convert
this array into a standard array. In C++20, to_array function was
added just for this purpose, and that's it.
That's all we have to do.
Now we have a standard array with elements from this row array. I would
like to pass this new standard array to a function,
the printerArr function, which will output all of the elements to the terminal.
Of course, we need to define this function, so let's do that.
We know that row arrays will be decayed into a pointer when we
use them as a function argument.
This is not the case with standard arrays. We can pass
them by reference like any other object.
However, there is still a catch. When you pass arrays by value or reference,
you need to also specify the template arguments.
So this function will only be able to work with
arrays, which contain three integers.
Fortunately, there is a better way to do this.
We can create a function template. Just like the array class,
the template arguments will contain the data type of elements
and the number of elements.
Since this size will be passed down to the arrays template argument, we need
to turn it into a standard size_t type. Std::array class is more
sophisticated than my implementation where I used a simple integer to store
the size. Don't forget to include the cstddef library. We need it for the
size_t data type. And now we have a templated function, which can receive an
array of any size and data type.
When we pass the array from the main function,
compiler will know that it needs to create a version of this
function that receives an array of three integers.
I can easily loop through all of the elements
because the size function will work.
We don't need an additional parameter for the array length anymore.
Okay,
now is the time to implement this standard array in our own DNA
class. First, let's include the array library.
I want to turn this legacy code array into a standard array.
We first need to provide the data type, which is char, and
then the DNA_CODE_SIZE, and that's it.
No major changes to the code.
I want to leave the rest of the DNA class as it is.
However, we are required to modify the copy assignment operator.
The standard copy function takes in a pointer to the beginning of
the array, but the code member from each one of these samples is now
a standard array class, and the name of the array class is not a
pointer to the first element.
This is actually great because there is a better and simpler way to do
this with standard arrays. We can just assign the array from the other
subject to the code array of the current object.
Std::array class has a copy assignment operator, which will
copy each element from one array to another.
So basically, it's doing what we expect it to do.
That would not be possible with c‑style arrays because this statement would
try to reassign the array name to point to something else,
which as we know, is not allowed.
This is one of the perks of using std::array class, and we will
learn about more of them in the next lesson.