>> Great so now we want to talk
about vectors, so we want to go through on size, talk
about them little bit. So, a vector, what we would like is for
a vector to be the most useable version of array that we can possibly think of. Don't have to worry about memory
allocation, we can expand it when necessary with simple commands,
but on the other hand it works like an array, it's got a nice bracket
operator and those kinds of things. So, that's essentially our list, wish
list and we would like it to be able to make vectors of any reasonably nice
type which we're calling proper type. That's a type you know with a full
set of constructor, destructor and assign the operators
and copy constructor. So we set a size at runtime,
reset the size at runtime, a nice bracket operator, and we might
like to have a safe bracket operator so that if you happen to go out of
bounds it'll give you an error message; assignment operator, constructors,
copy constructor, destructor and automatic memory
management that's a big one. So, it's gone be in our fsu name
space, it's going to be called Vector with a capital V and we'll put in
template parameter for T. It's going to be very much like the Q or stack
things you did towards the end of your auditory and programming class. So, what's the implementation plan? The idea is to maintain a protected
array and we'll call it content where the elements are stored and
maintained two protected parameters, a size parameter will tell you the
official number of items in the Vector and the capacity parameter which tells
you how many items you could actually store in this Vector before having
to reallocate memory for it. So we'll define also our protected
method to safely allocate memory when memory has to be reallocated. And we're going to manage the memory and
data for the user so that when this gets in our library and we use it as a client
of the Vector container we don't have to worry about memory management. So, the public interface of Vector or
the API will look something like this. They have constructors, a default
constructor, a one argument constructor, we put that keyword explicit there because one argument constructors
are quite dangerous to programmers. A one argument constructor
without the keyword explicit, well if it's not explicit
it's implicit, right? And that means that the compiler can
use it to do automatic type conversions. And automatic type conversions
can get you in trouble in a hurry on a big software project and
they're really difficult to debug, but if that were not explicit then this
would convert an integer into a Vector and in your code somewhere is the client
code you accidentally use an integer variable and conform some Vector
operation on it, the compiler would say, okay fine I'll just convert
it to a Vector and go with it and you'd never even know the
mistake you had made there, but with an explicit it will convert
only if you make a specific call and so you will get a compiler. So, anyway we got a copy constructor
and we got a destructor that's kind of the big 4 and I guess the big adding
to the assignment operator is the big 5 and those together make Vector a
very nice type in C plus, plus. And we're going to throw in some other
goodies here, we did a plus equal which says expand the current
Vector to include all the stuff in the incoming Vector things like
that, but those are bells and whistles, we want a bracket operator, we need
a bracket operator that can be used on the left hand side of
assignment statements, but we also need a bracket operator
that can be used on the right hand side of things in a constant environment
and so we have two versions of the bracket operator, a
nonconst and a const version. We need to be able to set the
size of Vector, set the capacity of the Vector inquire to the
size, inquire to the capacity, and then we got some container class
protocols that we're going to introduce, empty, pushback, popback, clear, front
two versions, and back two versions. [ Background Sounds ] We have generic display stuff, we have
a display function and a dump function and remember display is for clients to
use to show data, dump is for developers to use to debug their
application, in other words we use that in developing Vector so we
want to make sure that what's in our Vector is what we think is in our
Vector, so that's our developer thing which we would disable when we
delivered it to the library. And we have iterator support
which I will come back to later. We're going to do some nonmember
overloads, the output operator, the equals operator, and the not
equals operator so that we can tell when two Vectors are equal or not
and we can send a Vector's contents to an output stream, of
course that's implemented by just a call to the display function. So, the way this file would work
is, I just want to remind you of what our standard expectations
are, we want a header for the file which names actually
the name of the file and you typically want an
author name and a creation date and if you modify it the dates on
which those modifications occurred and maybe a sight explanation
of what goes where at the time and then a general documentation
for the code and in this particular
case a general discussion of well what is a Vector
which is a Beox. And then we do protection against
multiple reads for this kind of mechanism then we put
everything in the name space of fsu and finally we can start
putting code and this is where we put our class definition. And here's something you
may not have seen before, but we can also include other files
here inside of this name space which would give us the option of
implementing Vector in a separate file but have that file appear
logically to be part of Vector.H and in fact we're going to do this
and you see it quite commonly. Remember in template code you can't
precompile the implantation and so on template code both the
definitions and prototypes and the implementations
are in the header file. What this is showing you is
that they can be in it logically without being in it physically. So, the second level they're
logically in the header file, but not necessarily physically. [ Background Sounds ] So, this file Vector.CP3 is generally
called a slave file in the sense that it is not functional by itself. It must be used through Vector.H. [ Pause ] And what we'll do is, and so
this will not have a name space, it won't have an include
Vector.H statement or anything, it's just a storage place
for the implementations of the functions; just to remind you. Here it gets logically
included into the header file. Okay, so we're going to have this
private method called new array, I guess that ends up
being static as well. So, what this is going to
do is you get new array, you give it a new capacity value. It's a new capacity value as positive
and you allocate with operator new space for exactly that amount of data,
notice that I'm using the no throw flag on operator new here and that said
that it won't throw exceptions in the standard exception and on system
because I want to handle the exceptions in my library separately and
for now, I'll handle it this way but I can come back later and elaborate
on how that exception is handled. And by the way, a new capacity is
zero then I'll T pointer to zero and at the end of this I just
return that pointer back. So what this does is give me a way
of deciding how I want operator new to behave in one place instead of many
places scattered throughout the code. So, for example let's look at the
default constructor for Vector. Notice that we used an initialization
list which is totally mandatory. You always initialize
everything you can in the list, so the default size would be zero, the
capacity will be this constant which is in the d file called default capacity
and I think we said it, I can't remember but maybe 10, there will
be some default capacity and the content pointer will be
initially zero but then it gets changed to point to allocated memory
through this called to new array. [ Pause ] Okay, here's different constructor
it has two arguments, a size argument and an initial value argument. It goes along just as the previous
one content equals new array capacity, but then it executes a loop that
sets every element from zero through the size parameter to be
equal to that initializing value. I want to go back to the default
constructor here for a moment. This right here. [ Pause ] Let me go back one more step. Notice that this call to
operator new is the call to the array version of that operator. Excuse me I think I'm going to
sneeze here in just a second, maybe. Pardon me when that happens. So, what's the point to this? The point is that when you call operator
new with the bracket operator version, what happens is two things; one
on a memory block is allocated, but then secondly the
type T constructor, default constructor is executed for every type T block in
that memory allocation. So there is implicitly a loop that runs
from zero through the new capacity here that executes the type T constructor
for every element of that array. So just the simple call to new is a
theta of new with a bracket operator, is a theta of n operation, or
n is the size celled thing. So anyway back to, so this is
already a theta of n operation and this loop is just executing
first the theta of n operation and then another theta of n operation, so this doesn't really change
the asymptotics the constructor, the second constructor here
has the asymptotic runtime as the first constructor which is
namely theta of the size that you're, or the capacity that
you're calling it on. This is the copying constructor, right? The copy constructor always looks
like x, colon x const x and, right? And it's a constructor so we're
using initialization list. The size is initialized
to source to that size, source is just the name
chosen for that argument. The capacity is initialized
to source that capacity and then we create content
equal new array of capacity and then we simply copy the content
over from the source to this Vector, this loop happens to run through size. Some people might say, oh I'd rather
have it run through capacity just to make the footprints identical either
one of those would be a valid choice. And finally here is a destructor
and of course we're joined to the bracket version of
delete on the content array and because I'm a little bit compulsive,
we will set the content pointer to zero and the size of capacity to zero even
though presumably this thing is going to disappear as soon as this call is
over it's sort of ingrained and made not to allow pointers to have undefined or
illegal values ever when I can help it and so that's the purpose of
this here, so that I can say, no I didn't leave a pointer
with an undefined value. And it's a good thing to review
assignment operators here, remember the assignment
operator is similar to the copy constructor,
but there's more going on. The first thing going on is that
self-assignment is typically a disaster and so you want to make
sure you don't do that. So you first check to make sure this,
which is the address of me the object, is not equal to the address
of the object being assigned; if they have the same address then
they're not just equal objects, they are the same object and
you don't want to do anything. And then you go through the cases. If the source capacity is zero then if
your capacity is not zero then you got to delete your content
instead of being out zero. Or, if the capacity is not equal
to source capacity, but is not, but of course is not zero
because we've got past that, then we go through the process
of reallocating capacity; first deleting the old content
and reallocating content. Okay, if you get to this point
it means that the capacities of the two Vectors are the same and
we've gotten rid of any assigned memory that might have been, any memory
that might have been assigned to the receiving Vector and allocating
new and set everything up correcting. So all we've got to do now is
set size to be source type size and copy the content from the
source over to the receiving Vector. And when it's all said and done,
we retire to self-reference. Notice that we don't have initialization
list for assignment operator and the critical difference between copy
constructor and assignment operator is that copy constructor is creating a new
object in the likeness of the argument. The assignment operator has an existing
object, a pre-existing object that needs to be rebuilt in the
likeness of the argument and that rebuilding process is
fraught with danger first of all as a self-assignment, the
first if you call delete on yourself you've pretty much killed
off your information, so you don't want to go there, but the point is you have
to very carefully manage getting rid of the old memory footprint of the existing object
creating a new memory footprint for the existing object
and then copying the data. Because the existing object already
exists, it doesn't even make sense if you think about it to have
an initialization list cause that object's already been initialized. [ Pause ] Okay, so bracket operator. We had kind of said in
the wish list that we'd like a nice safe bracket
operator, by the way, that's not always the choice people
make for developing libraries, I'm pretty sure that
it is not implemented in the standard library, the std Vector. We've chosen to do it for fsu Vector, after all we're mostly
code developers here and this will help us
in code development. So, if the bracket operator says, okay
I've got this argument that's the thing that goes in the brackets, right? If I being a regular size of the
Vector that's illegal argument, right? And so we send an error message the
index is out of range and we leave it at that unless the i is bigger
than or equal to the capacity in which case we exit because
there's nothing even to return. On the other hand if i is less than
your capacity, but bigger than or equal to size we can return the i element of the footprint even though
it's technically out of bounds. So, just to understand that,
if you see error messages in other applications it
might be using Vector; this is where that is coming from. Of course, you always want to
double check the actual code in the actual library in case there
might have been some minor upgrade to something like this since
this slide was created. [ Pause ] So, this is implementing the
nonmember operators, for example, the double equals operator
we've got two Vectors; first of all if they have different
sizes and they're not equal so return faults, if they'd
have the same size and we go through the elements one at a
time and if we find the place where they're not equal, again, we can
return faults, but if we get all the way through all of those checks
then we can say that they, well are equal meaning they have
the same size and the same elements and so we can return a true
and you pretty much always want to define not equals as not equals
rather than try to reverse logic, this negate the logic
of that implementation. This will guarantee you that if you
go tweak this it'll still be logically compatible with this. [ Pause ] Somebody's downloading movies
or something on my network. [ Pause ] So, we're almost to the
end of Vector here. I believe the next slide is,
no, no I've quite a few left. [ Inaudible ] [ Pause ] I'm going to have to pause. Okay, so we had a delay there. Display methods you've been through
an exercise like this, but I just want to remind you that there's a strange
little beast called the output formatting character that's
an argument for display, the first argument is the stream, the
output stream through which you want to send the data and the out ofc works, I mean it of course could
be any character of the useful characters are
blank; null blank tab and new line, those are pretty much
the 4 you want to use. If you put in a null character backslash
zero it means you want the data displayed with nothing in between,
and so that what that does, if output formatting character is
null you just output all the data from zero up to size. If it's any other character then
you output the data and then follow that with the output formatting
character and it's a mistake to just try to save typing and allow the output that
the null character to be output here because that produces
unpredictable behavior in your terms. So you don't want to be
outputting the null character. If it's a tab or a blank or a new line
of course then you do want output. Dump is really just make
a picture of a Vector so if the size is zero you just output
paren-paren otherwise you have to bran and then an element and then a
coma and another element and a coma and another element and so on until you
run out and then you put a close paren, so just kept paren element coma, element
coma, element coma, element close paren, just to give you a picture of a Vector. [ Background Sounds ] Now set capacity is something that's
important to get your arms around. So, set capacity has this
new capacity variable. If the new capacity is zero they're
going to delete all the content and set everything to null. If the new capacity is not the
same as capacity then we've got to allocate a new array and copy the
data from the content to new content, set capacity to new capacity,
delete the old content and then set content at new content. So this is just exchanging
footprints and it can get bigger or smaller depending
on the values that add. You can set the capacity
to anything you want if the capacity currently is 100,000 and
you know now halfway through the program that you only need 50 then it would make
sense to call set capacity and reduce from a 100,000 size footprint to a
50 size footprint and keep going. Set capacity is most often used
though to increase the capacity and that we will take up here in the
next slide I believe, so set size. If we want a new size then we first
test to see if that new size is bigger than the capacity we currently
have and then if it is we have to reset the capacity otherwise we just
set size to new size, and by the way, clear is just setting size to zero. [ Pause ] I'm not sure if the slide
changer is going work. So, size just returns the size argument, capacity just returns the capacity
argument returns the front element which is the index zero element,
back returns the back element which is the index size minus 1 element
with suitable checks to make sure that element exists and then the popback
and pushback and I'm going to look at popback next, so popping the
back of a Vector means this comes from your stack terminology, it means
that I want the Vector to shrink by 1 slot and so if the size is
already zero that's not possible so I do a rudimentary check to size
equals zero, I'm going to return a zero which is telling me, hey was not
able to popback; otherwise I'm going to decrement the size
argument and return true. So, again just like in
implementing stack with an underlying array popping is
really just the same as ignoring, cause we have the fixed size
footprint or a static size footprint so it would not be efficient to keep changing footprints every
time we wanted a Vector to get smaller by 1 notch, that would
cause a lot of runtime; you could look at this algorithm here
and see that it is 2 or 3 things to do and that's it, so it is a
constant runtime operation. If we set capacity down 1, that
would be a theta of n operation because that would involve
allocation of memory copying of data from the old footprint to the
new and deleting [inaudible], each one of those steps
being a theta of n operation, you definitely would
not want to do that. [ Pause ] Okay. I'm not sure why. [ Background Sounds ] Now a pushback is something probably
the most subtle concept that's in this entire chapter,
something that we will revisit on occasion in future courses. Here's the problem with pushback,
if the size of the Vector is less than the capacity and you can push
something to the back of that Vector by just making the size 1
bigger and copying it only, but what if the capacity
is the same as the size? You can't, there's no
more place to put stuff. What you have to do then is a
very expensive timeout operation to reallocate the footprint. So you have to expand the content
array and copy the data from the old to the new and delete the other. Then you can now increment
size and copy the footprint in. So, this is really a call to
set capacity and what you want to avoid though is having to do this
too often, so when you get to this point where you got to expand the capacity
of a Vector to put a new element at the back what you want
to do is expand it a lot. What we do in our library is double
it, so we double the capacity so that and then go back and
execute our push operation and now we can execute a
whole lot more push operations in constant time before you
run into that barrier of having to call set capacity again and
when we do we double the size and so pretty soon you're not
going to have to do that anymore. Now, there is something I want you
read about and you can wear, you know, filter glasses that allow you to not be
intimidated by mathematical notation. When it gets to be a blizzard
just get the gist and go on, but there is an assertion here
Vector pushback has amortized runtime complexity O if 1. Let's talk a little bit
about that means. What that means is that if we
carry, if we call pushback n times and measure the cost of each one
of those pushbacks added together so the total cost of all of those calls
to pushback and divide by the number of times we called pushback, that
would be what the average cost of those pushback operations, right? What amortized constant runtime says
is, that average cost is constant and this is remarkable because
think back, pushback is most of the time cheap content time, every
now and then it's horribly expensive, when we run into that capacity issue we
have to call set capacity and a timeout and that is going to allocate a
new double size memory footprint, copy data from the old
footprint with a new footprint and delete the old memory footprint, each step of that being
a theta n operation. So, terribly expensive
occasionally; cheap most of the time and what this theorem basically says is that the average cost is constant not
linear and that's really good news because pushback is a very, very often
used Vector operation and you don't want to build cost into your library, so
client programmers will use pushback. They will assume it's efficient
and they will use it a lot, so if you make it inefficient what
you have done is take time away from every program that is
a client of your library, in other words you've built
inefficiency into all the future coders that use your library and that's a
burden you don't want to put on them and so we have to be very careful. [ Background Sounds ] So, our next talk is going to
be about lists which is coming.