>> 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.