Chapter 1 Computer Programs
Chapter 1 Computer Programs
Chapter 1:
Computer Programs
Computers are tools for manipulating and analyzing information. Computer programs are the
means for specifying what actions a computer performs. This chapter will look at a simple
computer program. Some specific elements covered are:
Programming Languages
Computers are among the most complex artifacts made by humans and are comprised of
billions of distinct elements. Direct control of such complexity is beyond our abilities. Instead,
computer programming relies upon the principal of abstraction to allow us to specify what
actions a computer should perform.
Abstraction
Abstraction is the pruning away of complexity resulting in a simplified mental model of a
process. For example, the driver of a car is not required to understand the mechanics of a car
engine in order to drive. As long as there is a mental model that the gas pedal makes the car go
faster, the brake slows the car, and the steering wheel makes the car turn, a driver can be
reasonably effective. Note that this mental model is not really correct – pressing the gas
provides torque to the wheels, which may or may not accelerate the car. This example highlights
one danger of abstraction; in unusual situations, the simplified model may provide incorrect
predictions. For this reason, computer programming practitioners should have some
understanding of how computers operate as well as detailed knowledge of the programming
language.
Page 1
A comic from xkcd (http://xkcd.com/676/) highlights the many levels at which a program can
control a computer.
In this course, the C++ language will be used. C++ grew out of the C language. Both are examples
of compiled languages, where the C++ program is converted into numeric codes the computer
can use directly through another program called a compiler. Once in compiled form, the
program can longer be easily read or understood by a human. This approach is in contrast to
Page 2
interpreted languages, where the high-level language is used inside an execution environment
called an interpreter. The interpreter converts the high-level program as needed into native
actions by the computer. This usually results in slower program execution, but allows the
programmer a greater ability to examine the code as it runs, usually making the development of
programs more efficient.
Every program needs a place to start. In C++, this is a collection of statements called “main”.
int main()
{
int value = 1;
value = value + 1;
return 0;
}
Even this small snippet has some mysterious aspects. First, main is more formally known as a
function, which is a collection of computer statements that produce some kind of value. The line
int main()
specifies that the main function is going to return an integer, or whole number. In this case, this
value gets passed back to the operating system of the computer which interprets the value to
mean whether the program successfully executed or not.
indicates that the next collection of statements belong together, until the matching closing curly
bracket. These are used in place of begin/end pairs that some other languages use, or even using
indentation to collect statements together. While the example program shown uses indentation,
it is not necessary and is just a convenience for human readability.
Page 3
Declaring Variables
The program really starts on the next line,
int value = 1;
which declares a variable named value and puts the number 1 in that variable. A variable is
simply a named location in which to store information for later retrieval or modification.
Analogies can be made to something like a post office box – the contents of the box can be
accessed if the address is known. The box address is like the name of the variable, while the
contents of the box are like the value stored in the variable. Note that the contents can be
replaced with something new, just like the value of a variable is not permanently set. C++ is a
strongly-typed language, which means that the programmer must specify what kind of value
goes in the variable. In this case, the keyword int means that the data stored in the variable
must be an integer value. This variable declaration need not initialize the value in the variable, it
often just declares it to exist. A legal statement is
int value;
but using the variable named value, for example in a computation, before setting it to a known
number can lead to undefined behavior.
Naming Variables
Legal variable names in C++ use letters, numbers, or an underscore character, but they start with
a letter. They cannot match any reserved C++ keywords, so a variable named int is not allowed,
for example. There are a number of conventions used in naming variables. The name should be
useful to someone reading the code. In the example code, the name value is used – this is not a
very good variable name. Something like “peopleCount” would be more descriptive. Some
people use underscores to make variable names look like sentences, like number_of_people. I
prefer to use capitalization to break up words, like numberOfPeople, which is a style called
CamelCase (the first character is optionally capitalized).
Another thing to note is that the name of the variable is just for human convenience in reading
code; it does not have any semantic value to the compiler. So if the variable were called
evenNumber , and the odd number 1 was assigned to it, the compiler would not care. When
reading code, it is important not to make assumptions about values stored in variables based on
the name.
Modifying Variables
Simply storing values does not provide much interest. A more complex action is modifying the
value in a program, as in the next line,
value = value + 1;
Page 4
This type of statement can cause a great deal of confusion for beginner programmers. This may
be a result of having an incorrect mental model for what the line means. One interpretation,
although incorrect, is that this statement is related to the mathematical equals symbol, so it is
claiming some equality between the left and right sides of the equal sign in an algebraic sense.
This is clearly impossible as something cannot be equal to itself plus one.
Instead, the equal sign commonly means assignment in computer languages. This is a somewhat
unfortunate historical choice and some languages try to provide some distinction by using things
like := or ←. A correct way to parse the meaning of this line is something like “look at the
number stored in the variable named value, add one to it, and put this new number back into
the value variable”. Note that this line specifies several things and those things happen in their
own internal sequence, not simultaneously. At an even lower level, this line likely gets
transformed into machine instructions something along the lines of “move the data stored in the
variable to a register on the CPU, move a 1 to another register, perform an addition operation,
then move the resulting value back to the variable location”.
return 0;
specifies that the program completed correctly and returns that value to whatever originally
called the program (in this case likely the programming development environment, like
Microsoft Visual Studio, or an operating system). This line has caused students in this class a
great deal of worry – just consider it a needed but essentially unused feature.
Page 5
In this course, Visual Studio is used. Start VS and pick a new project as
shown in the figure. When loading an old project, the Recent area below
is handy for quick access.
The project is now ready to have code added to it. Under the Project menu, choose “Add a
New Item”, select a .cpp file, and call it something like SampleCode1 (.cpp will be added
automatically). Cut and paste the code from the sample into the text box and compile and run
it using F5. The lower window will show some compilation information, and then a console
style window will briefly appear and disappear. Since the program doesn’t do much, that is to
be expected.
Page 6
Data Types
The sample program declared value to be an int, for integer. The true power of a
programming language comes through when a programmer starts to define custom types, but
there are other predefined data types, such as:
In order to truly understand these types, the fundamental workings of a computer must be
examined.
Page 7
Chapter 2:
What Lies Beneath?
Computers, and the computer programs that control computers, are tools for processing
information. The source of this information ultimately is derived from the real world. For
example, a weather prediction program may work from measurements at multiple locations in
region and use those to compute future changes to those values. These real world values are
inherently analog, which means they can smoothly change along a continuous scale –
temperatures need not jump from 90 to 91 degrees; temperature can take on any intermediate
value in-between as well.
Computers, on the other hand, work with discrete values, or ones that have to make a jump
from one value to the next. This is a result of computers being digital devices. Computers are
built up of a vast set of electrical switches called transistors. These switches are now so small
that their size is measured in atomic widths, but they function as macroscopic switches with
on/off states. By properly connecting these switches together into collections called circuits,
values can be stored and mathematical operations performed on the stored values.
Page 8
rare, would be impossible to find and understand without a correct mental model of what is
happening inside a computer while a program is run.
Computers typically work on binary values (on/off, 0/1, true/false), which map to the on/off
values stored in the digital circuits of the computer. A single value is called a bit. A collection of 8
bits is historically called a byte, and is the smallest set of values typically addressed in a
program.
A collection of bits can be used to represent numbers or other values. A single bit has 2 states
and can thus only represent 2 values. Which two values depends on the person or program
doing the interpretation – the two states could mean 0 or 1, a cat or a dog, blue or red, true or
false, open or shut, and so on. The point is that there is no fundamental meaning to those two
states. Similarly, a collection of bits such as a byte, has no intrinsic meaning.
There is, however, an elegant choice for interpreting the meaning of a collection of bits. If each
bit is viewed as a 0 or 1, then an ordered collection of bits becomes a number, like 11010111. This
can be interpreted as a binary, or base-2, number.
Number Bases
Base-10 numbers, such as 138, are so familiar to us that the true meaning of them becomes a bit
fuzzy in our thinking about them. A base-10 number means that there are ten kinds of digits to
work with, 0-9. Each digit’s position in the number has a different value: one’s, ten’s, hundreds
and so on. The unspoken pattern is that each column is worth 10 times the neighboring column
on the right.
Other bases are possible. For a base-n number, there are n digits, 0-(n-1), and each column is
worth n times the neighboring column on the right. So a collection of bits with values of 0 or 1
can be interpreted as a base-2 number. As an example, the base-2 number 1101 can be
interpreted as a 1 in the 1’s column, a 0 in the 2’s column, a 1 in the 4’s column, and a 1 in the 8’s
column, or the value 13 in base-10.
Binary numbers seem strange and unnatural. They only seem unnatural from lack of practice.
Base-10 is so natural after years of practice that it seems there must be something unique about
base-10 that makes arithmetic operations work out more easily or more efficiently or so on.
There is not. Binary numbers can become perfectly comfortable after time.
Integers
Declaring a variable an int is merely specifying that the bits allocated for storage of that
variable should be interpreted as a binary number. This is actually not quite right, as an int has
both positive and negative numbers, so the correct C++ specification for a positive whole
Page 9
number is unsigned int. The C++ specification describes an unsigned int as using 4
bytes worth of storage, or 32 bits. This also means there is a maximum value for such a data
type, with values that range from 0 to 4294967295 (in base-10). If a program keeps adding 1 to a
variable, eventually it will run out of values and the program could become wildly incorrect.
int main()
{
unsigned int value = 4294967295;
value = value + 1;
return 0;
}
Try Code Sample 2 in VS. Set the debugger to stop after the first variable, then step to the next
line. Note that value goes from a very large number to 0 when 1 is added to it.
Adding negatives (as used for i nt), complicates the format quite a bit. One bit could be
reserved for a negative sign, but this approach allows both a positive and negative zero, and
more importantly, requires a great deal of special case handling in the hardware to handle basic
operations like addition and multiplication. A more elegant solution is called 2’s-complement
representation, and this is what computers typically use, but interested readers should consult
other sources for details.
Reals
C++ uses two main built-in data types to handle real numbers. The type float is also 4 bytes,
just like an int, but the bits in this type are allocated differently. A portion of the bits specifies a
whole number, while the rest determines an exponent which shifts the location of the radix
point. The type double uses 8 bytes for far greater precision. The memory savings of a float
should only be used are careful determination of need and double should be the default choice
of a real number data type. Real numbers have three main issues
Page 10
A more detailed description of some of the issues with real numbers can be found at
http://floating-point-gui.de/ or a more formal write-up at
http://download.oracle.com/docs/cd/E19957-01/806-3568/ncg_goldberg.html
Characters
Characters are stored in single bytes of memory. A single byte can have 256 values, and a
standard called ASCII specifies what numerical value corresponds to what letter. C++ uses the
char datatype to store single characters, sequences of characters are called strings. A single char
is shown using single quotes around the character, as in
The Unicode standard uses 2-bytes for many international symbols as well. VS has settings to
move between Unicode and ASCII, and care should be taken when communicating with outside
devices that may be expecting 1 byte or 2 byte character representations.
Casting Types
If one kind of data type is assigned to another, the compiler will attempt to convert one type into
the other, so statements like
actually produces the expected result with the equivalent form of the number stored in the
variable. This process is called type casting and this automatic type casting is the implicit form of
it.
One common issue with integer data types is that mathematical operations using them are
closed – that is, operations involving just integers will result in integers. This is true even if a
person might expect some kind of implicit type casting to occur. For example, think about the
expected result for 5/2. A human should probably answer 2.5. Note that the original numbers
were both integers. C++ would force this into an integer by truncating the decimal portion,
leaving an answer of 2. This is true even if the result is being put into a real data type. So
would still execute leaving realVariable = 2.0 (the right side first evaluates to integer 2, which
then gets cast to 2.0 as it is stored in the double). Since this runs so counter to our own
experience, it is difficult to recognize as a bug. In this case, the programmer may choose to make
one of the original numbers a real, or form an explicit type case, as in
Page 11
The last example performs the division then casts the result, but the answer had already been
finished as an integer operation.
Machine Architecture
Another interesting aspect of the ability to interpret the meaning of stored values is that
programs can also be stored as values that are then interpreted by the processor of a computer
to do tasks.
Modern computing machines all follow the same basic design philosophy, called Von Neumann
architecture. The idea is that variables are stored in a dedicated area of storage, called RAM, for
Random Access Memory. Another dedicated area called the ALU contains circuits that perform
basic logical and arithmetic operations on data stored in
local memory called registers. The particular circuits that
get to operate on registers are controlled by signals from
a control unit. Data travels between these units on a set
of wires called the bus.
This architecture also helps explain some of the traditional data sizes of built-in data types for
high-level languages. The data path between units travels on physical wires and the registers on
the CPU are also a set number of bits. Many early machines used 8 bit data buses and registers.
The next big jump was for 16 bit computing, then 32 bits, and only recently have machines
largely transitioned to 64 bit data paths and instructions.
Page 12
Chapter 3:
Flow of Control
The default behavior for a program is sequential flow of control. Such programs cannot exhibit
very interesting behavior. Two important ideas are selection, where a program can choose
between two different actions, and repetition, where an action is repeated. This chapter covers:
Selection
Allowing a computer to make decisions based on computed information or human input greatly
increases the complexity of tasks a program can achieve. This can be thought of as selecting a
proper action.
The basic means of selection in C++ is the “if-then-else” statement. The basic setup of this is
int main()
{
int number = -5;
Page 13
return 0;
}
The condition is formed from a matched pair of parenthesis with some Boolean expression
inside it. A Boolean expression evaluates to true or false; C++ also recognizes 1 or 0 as true or
false respectively. It is better to use the built-in type bool to hold a conditional value than using
int and depending on the mapping to true/false. Booleans expressions use the following
comparisions
Operator Meaning
== Equal
> Greater than
< Less than
!= Not equal
And more complex conditions can be formed by using logical operators that form compound
conditionals:
Operator Meaning
! Not
&& And
|| Or
Here is some code that works like a band-pass filter (rejecting values outside a range)
int main()
{
double frequency = 100.0;
double signalOut;
return 0;
}
Note that this condition uses two comparisons and the && logical operator. Conditionals of
arbitrary complexity can be built up. Parentheses are used as in math to enforce precedence of
operations or sometimes just to make portions clearer.
Page 14
Sometimes, multiple actions are desired as a result of a conditional. In this case, enclose the
multiple statements in curly braces to make a block.
One way to setup a conditional that is technically correct, but stylistically awkward is to
compare a Boolean statement with true or false. The following chunk of code illustrates this
bool flag;
[some other code]
if ( flag == true )
do something;
The variable flag already evaluates to true or false, so there is no need to compare it to the
built-in values for bool. This commonly indicates that the variable name is not descriptive
enough. Try something like
f ( errorDetected )
i
do something;
where when you read the if statement, it sounds like something sensible.
A related issue comes from assigning a value to a Boolean variable based on some condition.
Code like this is commonly seen
bool oldEnough;
if ( age > 15.0 || grade > 9 )
oldEnough = true;
else
oldEnough = false
Note that the value assigned is the same as the value of the condition, so a more concise
statement might be
This takes some practice to be comfortable reading at first glance. The worst case is when code
looks like the first, longer example, but the true and false assignments are reversed. That
basically shows the programmer messed up the condition and fixed it after the fact.
Page 15
Repetition
The code finds the squares of ever increasing integers. While this is perfectly workable for small
sets of numbers, continuing on to large sizes of value would be impractical. One of the major
advantages of a computer is that it is perfectly fine with doing the same boring activity all day
long, so a means is needed to specify these repeated actions more efficiently.
There are several mechanisms for this in C++. If the number of repetitions is well-defined, the
for-loop is a common choice. It has the form
The for-loop is mostly used when the programmer wants some action to repeat a known
number of times. For example
int square;
for ( int value = 0; value < 1000; value++)
square = value * value;
The first part of the for-loop initializes some kind of counting variable. The second part provides
a condition that determines the end of the loop – the loop continues until the condition is false.
The last part increments the counter (the ++ notation is a short-hand for value = value + 1, and
the inspiration for the ++ in C++).
The for-loop is an easy one to get a little bit wrong. The presented code snippet would work
through values between 0-999, skipping the 1000 value. This end case is often confused. Also,
the use of the semi-colon between each part of the for statement is critical for success.
Semi-colons are used because looking at each part of the loop statement, each component is a
bit like an independent statement – there is an initializer, a comparison, and an increment.
Page 16
1. Use of a less-than is preferred over testing for equality. Two common errors come from
trying to use == in the comparison. One is using assignment = instead. Assignment is
actually a valid operation, so there is no compilation warning and an infinite loop results.
Another common error is to use equality, then later change the increment statement and
have the equals test never hit the target.
2. Very complex for statements are possible, with several counters initialized, and perhaps
the condition depending on actions within the loop body. If the number of desired
iterations are not known in advance, other looping forms are preferred. An example of a
hard to read for loop this might be
See the section following on indeterminate looping for the proper way to do such
actions.
Debugging
The VS debugger provides some nice tools to help examine the workings of loops. Set a
breakpoint rather than “run to cursor”. If the breakpoint is inside a loop, the debugger will
normally stop each time through the loop, but the error may only happen at high loop counts.
Right-click on the line where you desire the breakpoint, and select “Breakpoint->Insert
Breakpoint”. Notice a red circle appears on the left of the text area on that line.
Instead of stopping each time through the loop, bring up window of breakpoints by clicking on
the “Debug” menu, and selecting “Windows->Breakpoints”. The breakpoints windows appears
where compilation information usually is shown. Right-click on the desired breakpoint, and set
the “hits” value to specify after how many times the breakpoint is passed before the program
will start to break. Start debugging with F5 and then either step through the program using F10,
or continue the program with F5 again.
Indeterminate Looping
Loops that run until some prior unknown condition is met are better made with a while loop. The
structure for a while loop is
In this case, some action inside the loop must modify the finished variable or the while loop
will never terminate. A slight variation is the do-while loop, which always performs the desired
action once and then checks the termination condition at the end.
Page 18
Chapter 4:
Functions
An important principle in software engineering is the idea of code reuse. Chunks of useful code
should be made available to other parts of a program or even other programs. This chapter
covers:
✓ Structure of functions
✓ Arguments to functions
Code Reuse
Image a program that finds the better of two scores. The main body of code might look like the
following (although typically the scores would be entered by hand or from a file).
int main()
{
double score1 = 95.0;
double score2 = 87.0;
double bestScore;
if (score1 > score2)
bestScore = score1;
else
bestScore = score2;
return 0;
}
This code is not that complex, but imagine doing the same for ten different sets of scores. Each
test for a best score would look very much like the others.
Page 19
A danger comes from the very real need to go back and change the way the program works.
Perhaps the scoring committee has decided that the first score should be declared the best if it is
greater or equal to the second, instead of the strict greater-than currently used. Each copy of the
test would have to be hunted down in the code and changed to meet the new requirement.
There is also an increased chance of careless errors from replicating the original code to
different locations – perhaps the variable name of the scores are not properly updated for the
new location.
C++ allows functions to be defined that avoid these issues. The main function used to define
most examples up until this point is a specialized version of this capability. Functions can also be
thought of as a way to extend the C++ language – not for all users of the language everywhere,
but within a specific project.
A function is a group of statements that are executed when called by name from some point in a
program. The function can return a result value, although it is optional. The basic structure of a
function is
where type is a built-in or defined type, the name of the function is how it is called
elsewhere, and parameters are information that can be passed into the function in
order to provide data for the computation.
int main()
{
double score1 = 95.0;
double score2 = 85.0;
double best = bestScore( score1, score2 );
return 0;
}
Page 20
One important thing to pay attention to is that the scores in the function are called scoreA and
scoreB. This is because the function is defined for any arbitrary score variable passed into the
function. It is designed to work called with score3 and score4 as well, or any score (or valid
double) variable.
The return statement dictates what value is sent back from the function. If nothing is needed,
then a type void is used.
Functions can also be thought of as modifying the flow of control. Just as if-then-else and
looping statements modify the default sequential flow of control, calling a function passes the
execution sequence to a different portion of the program until the return statement is executed.
When defining a function, the parameters of the function define the type and number of values
passed to the function from another part of the code. When calling a function, these are called
the arguments – the arguments map to the parameters. In the previous example, score1 and
score2 are arguments to the bestScore function. Inside the function, scoreA and scoreB
are the function parameters.
If the value of scoreA were changed in the function, that change would only be temporary for
the duration of execution of the function call and would not change the argument variable. In
other words, if something like scoreA = scoreA +1; were executed in the function, then
score1 would not also change just because it was the calling argument. There are ways to
make the changes permanent and these approaches will be discussed later. Some computer
languages are very strict about only letting information into the function through the parameters
and only letting information out of the function through the return value as this restriction is
thought to help improve program reliability.
Functions must be defined before they are called. This typically means that they are above the
main function. There are tricks to get around this that will be discussed later.
Scope
The ability to define parameters opens up a host of possible issues. Look at the following code.
Page 21
Here, the function has been defined using parameters score1 and score2, which is a somewhat
natural evolution if the function were derived from a single working example. So far, nothing too
odd has happened. What if the function is now called as follows:
int main()
{
double score1 = 95.0;
double score2 = 85.0;
double best = bestScore( score2, score1 );
return 0;
}
Here, the call to bestScore has the arguments reversed from the original example. The result is
actually quite straightforward, as long as the similarity of names doesn’t cause confusion. The
first argument, called score2, maps to the first parameter of the function, named score1. The
second argument, score1, maps to the second parameter of the function, score2. The key is
that once inside the function, the parameters are totally different objects to the program, even
though they share a common name with the variables in the main function.
This idea is called variable scope, and it defines which names match to which data in a program.
Essentially, variables exist within the block of code where they are defined. One consequence of
this is that in the original function example, the parameters scoreA and scoreB are not visible
outside the function. Similarly if any variable had been defined within the function, they can only
be used inside the function block as well. There is an implicit block of code around an entire file,
and variables defined here are called global variables, as they can be seen globally in the
program. Variables can also be defined inside any code block defined by curly-braces; for
example, in the true action part of an if statement. W hen names conflict by being used in more
than one place, the most recently defined variable is used.
The following diagram shows some sample variable definitions and the scope of their visibility.
Words following a // are considered comments and are ignored by the compiler.
Page 22
int count = 0;
ount )
int aMess( int c
{
//There cannot be a local variable count here
if ( count < 0 )
{
Page 23
int main()
{
return 0;
}
The brown count variable is defined in the global scope. If there were no other definitions of
count, it would be visible through the file, even inside functions. Inside the main function, the
red count is local to the main function. However, once passed to a function as an argument, it
maps to the blue count parameter. If that parameter were named something else, the red
count would map to that name. Inside the a Mess function, the count inside the if-statement
condition uses the most recent definition, the blue count parameter. The true action of the
if-statement is a multi-statement block which includes yet another local count variable. This
yellow count is used when the return statement is executed, making for a very messed up
and nonsensical program. However, it is entirely valid for the compiler. If you try this at home,
the -2 value is passed to the function, where it is less than zero. The local yellow count then
supersedes the blue count, so the value of yellow count (or 2) is returned.
A clear sense of scoping rules is critical in larger programs. An appreciation for the kind of
trouble and confusion caused by name overlaps is also key for a programmer to develop the
discipline to avoid many of these issues in the first place through judicious naming of variables.
Page 24
Chapter 5:
Structuring a Project
A software practitioner must have a clear sense of the problem being solved and its solution,
confidence in the programming language being used, and comfort with the programming
environment being used. One important skill is understanding how larger projects, with multiple
files and locations, come together. An understanding of modular code also provides the needed
background for importing the tools for standard input and output. This chapter covers:
Function Prototypes
In the last chapter, functions had to be defined before they were used. Again, this is a
consequence of how programs are compiled. The program text file is searched, starting from the
top, for sequences of characters that match patterns in the C++ language. Once something like a
variable or function is defined, the name is stored so the compiler knows how to find it and what
it is supposed to be. If it hasn’t been defined, the compiler does not know what to do when it is
seen elsewhere in the code.
Sometimes, this can lead to a paradoxical situation when functions need to call each other.
Consider the following situation.
Page 25
int main()
{
bool odd = isOdd( 5 );
return 0;
}
The logic of this example is that we know that 0 is not odd and 0 is even, but for bigger numbers
we only know that a number is odd if one less than that number is even and similar for larger
even numbers. Try stepping into the functions as they are being called to see if you can figure
out the overall logic. While the example is contrived, it is possible to occur even during normal
programming, and the related situation of at least desiring a certain order to functions in the file,
perhaps for grouping similar code together, causes this problem often.
The key to the problem is that the isEven function calls isOdd, but isOdd also calls isEven,
and they cannot both be above the other.
The solution is to let the compiler know the function exists along with the function return type
and parameter types, so that an entry can be created for it, but to hold off on the actual code for
the function until later. These function prototypes are commonly done at the top of the program
file. The top of the now legal example is
The parameter names need not match as the compiler is only interested in the type of the
parameter. For this reason, the names may be omitted completely, as in
These function prototypes are very useful, but can introduce a source of errors and confusion. If
the full function has its parameters or return type modified, the function prototype needs to be
modified as well. Often, the list of function prototypes at the top of a file can aid others trying to
understand the functionality of a file – the prototypes act something like a table of contents in a
book.
Header Files
It is possible to go one step further than placing the function prototypes at the top of a .cpp file.
These prototypes can be placed in a separate file, usually ending in .h or .H. This type of file is
called a header file. To use a header file, the name of the header file is included at the top of the
.cpp file using a special command. Here is an example.
#include “OddOrEven.h”
There is a new file (added to the Project) called OddOrEven.h, with the function prototypes in
them.
#include “OddOrEven.h”
This statement is not a regular programming command. Instead, it is a command to the compiler
known as a preprocessor directive. The #include tells the compiler to grab the file inside the
quotes and to temporarily copy the text from that file into the current .cpp file. The compiler
does not know it is a file of function prototypes, it could be a file with a grocery list on it,
although that would likely cause a compilation failure.
Page 27
Having header files allows one more innovation. Sometimes, a programmer might develop a
nice collection of useful functions for one project. When wanting to use them in another project,
instead of copying all the functions over to the new file, we just want the new main program to
use these functions.
This is an important programming practice, as it allows code reuse from one project to the next.
Code reuse improves reliability as presumably the code was reasonably well debugged while
used in the first project.
To demonstrate this concept, the non-main functions have been split off and put in a separate
file. The example project now has three files: OddOrEven.h, OddOrEven.cpp, and a main.cpp.
They look like
OddOrEven.h
OddOrEven.cpp
#include “OddOrEven.h”
#include “OddOrEven.h”
int main()
{
int number = 3;
return 0;
}
Page 28
The OddOrEven.cpp file needs to include the header file so that it can resolve the ordering issue
of the two functions. The main.cpp file includes the header file because otherwise, it would not
know anything about the functions off in the other file, even though they share a project in Visual
Studio and a folder.
The other thing to know is that the OddOrEven.cpp compiles to what is known as an object file,
usually saved with a .o extension. This is code that is ready to run, but does not have a main
function so it cannot be run directly. Large projects will have dozens or hundreds of files, each
with a header and stand-alone .cpp file which can be reused and combined into new
functionality.
Up until now, the debugger has been the principal means of examining the inner workings of
executing programs. More typically, programs ask for information from a user, compute
something using that data, then provide some textual information back to the user. This
capability can also be used during the debugging process, by printing information about
program state or the value of variables at desired locations in a file.
Somewhat surprisingly, the standard set of input and output tools are not automatically available
inside a program, the programmer must ask for them. The following code shows a simple
example.
#include <iostream>
amespace std;
using n
int main()
{
cout << "Hello";
return 0;
}
The #include statement is a bit different as it uses angle brackets rather than quotes. The
distinction varies a bit from system to system, but in general, #include statements that use
quotes will look in the local directory (or whatever path is specified inside the quotes) before
looking more system-wide, while #include statements that use angle brackets look in known
system locations and ones specified in Visual Studio for that specific project. You might think of
this as using quotes for your own header files, and angle brackets for standard ones, although
your own header files can become more official once they are fully developed.
Page 29
Another difference is that this header does not have a .h in it, it is just iostream, not iostream.h.
This is an artifact of how C evolved into C++ then became standardized. Essentially, many
capabilities of C++ must be included explicitly, and the standardized C++ wanted to include new
functions but retain compatibility with older C++ and C programs. So the new function
prototypes are put into header files without a .h extension.
The iostream file contains code for handling basic input and output. Other standard headers are
available, for example, <cmath>, which includes more advanced functions than the basic
arithmetic shown so far.
Since the basic library is so important, its designers wanted to make sure that other functions
defined with the same names did not manage to cause conflict with the standard ones. In order
to help this process, the standard library functions are defined within a namespace. A
namespace is a special scope for names, and can be used for organizing related functions and
also for allowing popular name reuse. For example, a program may have an input function for a
keyboard, but also for some graphical window element. A keyboard namespace might be
defined as follows
namespace keyboard
{
input()
remap()
}
Then, when calling these functions, the namespace and function can be explicitly typed and
joined by a double ::, as in keyboard::input() , or whole blocks of code can follow using
namespace keyboard; and all functions and variables are checked to see if they exist in that
namespace.
Looking back at the example program, the code includes iostream, then indicates that things
from the std (for standard library) namespace will be used. The line that actually uses all this is
cout << "Hello"; which prints the word Hello to the console. The word Hello is wrapped in
quotes. This tells the compiler that it is not a variable but instead a string, which is a collection of
characters. The double << is called an insertion operator, as it inserts the value on the right of it
into the thing on the left of it. In this case, the string “Hello” is inserted into cout, which is
something called a stream and is essentially the console window. It could have been called by
using std::cout << “Hello”; as it is defined in the standard library. Instead of cout, the
string could have been inserted into a file, or another string, so this approach is very general.
The insertion operator also knows what to do with variables. The following snippet of code
Page 30
would send to the console My age is 25 and shows how multiple insertion operators can
piece together descriptive words along with values from variables. The keyword endl is also part
of std:: and adds a line break to the end of the output. These types of statements are very useful
to inspect variables in a program while a program runs.
int age;
cin >> age;
would wait for a user to type a number followed by the ENTER key and place that value in the
variable age. Cin only processes input after seeing a return key press.
We can now also hold a console open long enough to look at text that was sent to the screen.
The line
std::getchar();
system("pause");
There are reasons not to use this. This command actually calls the system program called pause.
If your system is compromised, and the normal pause is replaced with something malicious, it
would be run as yourself with your permissions and access. For our small, informal examples,
this approach is reasonable.
Page 31
Chapter 6:
References and Pointers
A major source of programmer errors comes from an incorrect understanding of when values
passed between functions are allowed to change. This chapter will cover:
References
In the introduction to functions, arguments are passed to functions and some value is returned
from the function. Even though there is a correspondence between the arguments to the
function and the parameters that take on the values of the arguments, any changes to the
parameter values were lost upon the return from the function.
This style of sending information to functions is known as pass-by-value. One benefit of this
approach is that a programmer can call a function without fearing the function will modify the
arguments in unanticipated ways.
However, this approach can also be constraining and takes a somewhat adversarial view of a
programmer and existing code. Consider taking two variables and exchanging their values in
what is called a swap. This is a common operation, for example in sorting collections of values.
An incorrect approach that ignores prior lessons on pass by value might yield something like
a = b;
b = temp;
}
int main()
{
int first = 1, second = 2;
swap(first, second);
return 0;
}
This code creates and initializes two variables before passing them as arguments to a swap
function that exchanges the values inside the function. Since the arguments are passed by value,
the parameters a and b have their values exchanged relative to the arguments first and
second, but the values in the main function stay the same.
The only difference is insertion of the ampersand between the type name and the argument.
This function would have the desired behavior of exchanging the values contained by two
variables.
Understanding the mechanisms by which the passing styles are executed helps keep the
different behaviors straight. In pass by value, the arguments are copied into new memory
locations. The parameters of the function work on these new memory locations inside the
function, keeping the original arguments safe from modification. Pass by reference instead
shares the same memory location between the calling argument and the function parameter.
For a variable that may contain a large chunk of data, such as a detailed geometric model, calling
by reference is actually more efficient than calling by value, as it avoids the step of copying the
data into a new temporary memory location. There is a way to retain the protection of call by
value with the efficiency of call by reference. The word const may be used before the type to
Page 33
tell the compiler to share the memory location but to not allow modifications. This is somewhat
of a hack, but can yield impressive performance gains.
Pointers
References are a purposefully limited compromise between the older C-style of sharing access
to a memory location and more restrictive language models. The older, C-style approach is to
use pointers. Pointers are a variable type that stores a memory location of a variable rather than
the data in the variable. Part of the confusion possible from pointers is that the special characters
& and * are used to mean slightly different things in different places.
When used with a parameter specification for a function, & specifies that the argument should
be passed by reference instead of by value. In practice, this means that the memory address of
the variable is shared between the variable in the argument and the parameter.
Ampersand can also be used directly on a variable as a reference operator which results in the
memory address of the variable. For the following code snippet
int main()
{
double val = 2.0;
cout << " val = " << val << endl;
cout << " val has memory location " << &val << endl;
the variable val is set to the number 2.0, then printed to the console. The following line prints
&val, which is the reference, or memory address, val is stored at in memory. The code
produces the following output
val = 2
val has memory location 0032F8A0
Your own values may vary. The output for the memory location looks strange. It is written in
base-16, or hexadecimal, which can be thought of as a compact way of writing binary numbers.
Each digit represents 4 bits with value 0-F (for 0-15), so the entire output has 8 characters
representing a 32-bit number. This program was run on a 32-bit operating system, and one of
the defining characteristics of an OS is the number of unique memory locations it can address,
which is related to the size of the address. If you run the code example on a 64-bit system, you
might see a 16 character output for the memory address.
Page 34
A pointer is a variable that can hold a memory address. A * character between the variable type
and variable name indicates that it is a pointer. The following code shows a pointer holding the
reference to another variable.
The main thing to notice is how the reference to val (&val) is stored in a pointer variable,
indicated with a *. When printing the value of val_location , just the variable name is used.
Just as & had multiple uses: once as a signifier to the compiler and once as an operator, so does
the *. Besides the use as a signifier to the compiler as a pointer type, it is also a dereference
operator. This use takes a pointer containing a memory address and returns the value stored at
that memory address. In the following line, *val_location is used
cout << "the memory location contains the value " <<
*val_location << endl;
and this code would output the original value of val, or 2.0. This is why pointers have a type
when they are declared. Memory addresses are all the same length, so at first glance, it seems
like there should just be a built-in type pointer instead of using *. However, when a pointer is
dereferenced, the type of the contained value is needed, especially as most variables are not
contained in a single memory address.
Here is a C-style swap that shows all the permutations of using pointers.
int main()
{
int first = 1, second = 2;
Page 35
swap(&first, &second);
return 0;
}
First, look at the call to swap in the main function. The variables have an ampersand in front of
them, which indicates that the value of the variable is not desired, but rather, the memory
location of the variable. This memory location is itself a number, but a number describing where
in memory the value of the variable is stored.
Next, examine the swap function. The parameters have a “*” in front of their names. This
specifies that the parameter is actually a pointer, or memory location, that contains a variable of
type int.
In the swap code, the parameters have a * in front of the variable name. This operation means to
work on the value pointed to by the pointer parameter. Upon return from the function the values
in first and second will have been swapped. This is now allowed because the pointers were
passed to the function and they never changed; only the values in the named memory locations
were modified. In summary, &variable gives the memory location of the variable values, while
*variable takes a memory location from a pointer to get access back to the variable.
Pointers can cause a great deal of confusion. For example, a pointer need not point to anything
and can instead point to a NULL, or zero value that means no memory location. If a pointer that
is NULL is dereferenced by application of *, then that can cause a run-time error.
Thus far, pointers have been used to point to variables that already exist and that were declared
in the code at some scope.
It is possible to assign a pointer a new variable that is created just for that pointer. This is a
somewhat useless idea right now, but later this will be very important, once we start to discuss
more complex variable types.
The process is to use the keyword new. New allocates a chunk of memory big enough to hold
the desired variable and then returns a pointer to that chunk. A pointer variable can then capture
that location. An example is
Page 36
A programmer could put such a piece of code inside a loop and do this many times. Eventually,
there will be no more memory left in the machine to allocate and an invalid memory location
will be returned from new.
The memory allocated from the new operator is reserved for further use, even if a different
memory location is stored in the same pointer. Multiple pointers can point to the same memory
location, so there is no way for the compiler to know in advance that the reserved memory if
free for other use. Because of this, the programmer must specify when the memory is no longer
going to be used. This is done with the delete operator. Essentially, for every new, there must be
a matching delete – not in a counting the textual appearances in a program sense, but in a
number of calls to each operation sense. The use of delete is illustrated below:
When a programmer fails to correctly account for deleting memory when appropriate, this is
called a memory leak. Eventually, such leaks will bring a machine to a crawl and make it
unresponsive. There are a number of modern approaches to avoiding such problems, and
modern C++ programming can largely go without using new, delete, and pointers.
C-style Arrays
Thus far, we have only dealt with data types that store a single value, such as an int or
double. Many situations call for collections of values. An example would be the gradebook for
a class, where many numerical scores need to be stored. One possibility would be to program in
a dedicated variable for each score, but this is inefficient and makes working with the data
almost impossible (how could you compute the average grade in such a case?).
The alternative is to use an array. An array can store multiple values of the same datatype. The
values are accessed by an index which specifies which value in the array is desired. Similarly,
values can be stored in the array by using the index.
int grades[20];
This tells the compiler to find 20 consecutive memory locations that can hold an integer. The
value specifying the size of the array needs to be constant, and the size of the array is constant
Page 37
during its life. These restrictions will motivate better C++ datatypes for arrays which we will
cover later in the course. Arrays can be initialized at declaration using curly braces
Notice that the size of the array does not need to be specified in this case – one of size five will
be created to hold the initialization values.
grades[0] = 95;
grades[10] = 40;
Grade 0 is" << grades[0] << endl;
cout << "
In this example, two values are assigned to two different locations in the array and the first value
in the array is retrieved in the output statement. An important thing to remember is that the
index starts at zero and goes to one less than the size of the array. This is very confusing – as the
first element in the array has a 0 index. One mental model that might help with this confusion is
to think of the index as an offset from the beginning of the array. So the first element has a zero
index and a zero offset in memory from the start of the array.
Another thing that helps a programmer understand the behavior of C-style arrays is that arrays
are essentially just a pointer. The array grades, without an [] and index, is just a pointer to the
first memory location of the array.
One important and subtle consequence of this, is that arrays are passed to functions as a
pointer, and the values in the array can be changed by the functions and those changes will
persist once the function exits. This is because the pointer passed to the function is passed by
value, so the memory location of the array is copied into the function parameter. The following
code demonstrates this.
int main() {
int scores[] = {95, 93, 10, 94, 10};
boostScores( scores, 5);
return 0;
}
First, look at the main function. A scores array is declared and assigned values. Then the scores
array is passed to the function boostScores. Once in boostScores, scores is treated as a pointer,
Page 38
and it no longer knows how many elements are in the array. So we must also pass in the size of
the array (which is pretty horrible). In boostScores, we can use the [] notation to access
elements of the array. The changes to the array persist because the pointer value has not
changed, just the memory it points to.
Arrays can be made dynamically using new. An example dynamically created array is done
using a pointer to the desired array datatype.
Notice the array is declared as a pointer, since it is not a static array. But once a chunk of
memory is allocated for it, the elements can be accessed with the [index] notation. A final
important syntax is that when the array is deleted, the programmer must use delete [] rather
than just delete. A plain delete will just free up the first element of the array.
Page 39
Chapter 7:
Classes and
Object-Oriented
Programming
✓ Object-oriented programming
✓ Classes
Object-Oriented Programming
The data-types and functions we have examined so far have been relatively simple, with most
functions only taking a few arguments. Imagine a more complex function that computes some
health risk result, with the following prototype:
Page 40
While this function has a complex list of parameters, it could have been much more
complicated. One approach to simplifying passing groups of related arguments to functions
around is to collect them into data structures called structs. Unlike arrays, where data of the
same type are stored in a single array variable, a struct collects a group of named elements in a
single data type.
struct Person_type
{
int age;
double height;
bool isAlive;
};
Once defined, a variable can be declared of that type just as if the struct were a built-in data
type. That variable is an instance of the type. Using the struct defined above, an example use is
Person_type bob;
The parts of the struct are called members of the struct. Each member is a named
sub-component of the struct variable. Elements are accessed by using a . notation. A program
wanting to initialize or modify a value stored in the Bob struct could use
bob.age = 32;
bob.height = 72.5;
bob.isAlive = true;
Essentially, any place a normal variable is used, a struct variable name followed by period and
the element name could also be used. The advantage is that the entire collection of elements can
be passed to a function by using the struct variable name without having to list all the elements,
as in
The struct structure needs to be defined before it is used, so a header file is often used to define
the struct.
An important aspect of using these user-defined types is that each variable, or instance, of that
type has its own copies of the members. So bob.age is a separate place in memory from
joe.age – changes to one are not spread to other instances of the type.
A more subtle problem is that the person calling this function must have a pretty clear idea of
what all the parameters are and what meaning is attached to them. For example, the int
drinks parameter could be number of drinks a day, or a week, or an internal scale used to
Page 41
gauge drinking. Professional code would include comments and documentation that precisely
defines what each of these is, but that is not always the case. Even in the best case, calling the
function would require careful examination of the code to see if it does what the caller thinks it
does.
The existence of this function may not even be clear until a programmer has used a large system
for some time, and this can also cause delay and duplicate efforts in adding to a large system. So
the healthRisk function may in a part of the system related to the Person_type struct code, or it
may be written in an area dealing with actuarial tables, or in a part for a doctor expert system.
Use of the function probably depends on long familiarity with the entire large system.
Objects are defined using a class. A class should be declared in a header file, and the code for
the class goes in an associated .cpp file. A class consists of a collection of variables and code
that works on those variables, called methods. All the elements in the class are called members
of the class. The main distinction between a class and a struct is that structs are just data, while
the class also contains members called methods that work on the data.
Some members of the class are public, or visible, to other code outside the class. Other
members are private. We will explore the utility of these constraints later, but the default for
members is private, and is over-ridden by using the public: qualifier.
Let us look at an example object class for holding colors. Colors in images and graphics are often
represented as an amount of red, green, and blue, with values ranging from 0-255 for each
component.
class Color
{
int red;
int green;
int blue;
public:
void setGreen( int value );
double getGreenRange0to1();
};
The code for the methods in the class can be defined in a separate file. Since the methods are
not free-floating chunks of code, they are defined as
Page 42
and the full code for the two methods in our example is:
double Color::getGreenRange0to1()
{
return green / 255.0;
}
Note how the data members of the class (in this example green), are visible to the method,
even though green has not been explicitly defined in the method. All members of the class are
visible to the methods of the class. Also note how the first method tries to protect access to the
data members of the class by doing error checking. The second method provides some
conversion routines which supply the data members in a different form.
Classes are used like other variables, although there is a special notation to get at members of
the class. Look at a main function that uses the class.
int main()
{
Color screenColor;
screenColor.setGreen( 200 );
First, an object is declared with Color screencolor . This declares a variable of type Color,
which is the class we defined. Next, the member green in the class is set by calling the method
setGreen with the notation object.method , with a period between the variable name and
method name. Perhaps we want to send green to some other code (like openGL), using a range
from 0.0 to 1.0 instead of the internal format of 0-255. The getGreenRange0to1 method
provides that and returns, like a function, the appropriate value.
Trying to use the (default) private data member green directly in the main function is illegal. So
a line like int newColor = screencolor.green; would be illegal. Even though green is
Page 43
a data member of screencolor , code outside the class cannot use private members directly.
If all the members of Color were declared public, then this statement would be legal.
The rationale behind keeping things private is to force other code to use tested interfaces to the
object which do things like error-checking. An object could be a very complex structure with
lots of internal data members and functions, but it can present a limited set of methods and data
to the outside world, simplifying the programmer’s job in understanding how to use the object. It
also prevents other code from messing up data or breaking some implicit constraints on the data
that is not fully understood.
Note that none of the data members are initialized and are showing garbage values.
Constructors
Up until now, getting an object in a “ready” state required multiple steps. In the example above,
a Color variable was declared, and then the values of the object were set in separate calls. This
has two problems: it is inefficient and it may leave the object in some half-valid state as the data
members are initialized one step at a time.
To address these issues, there are special method-like class members called constructors that
create the new object and that can also initialize the data members of the class.
These constructors exist even when not explicitly declared in the class – these are known as a
default constructor. In the Color class defined at the beginning of this chapter, there was no
explicit constructor. Yet, when an object of type Color was declared, as in Color
screencolor; then the default constructor allocated memory for the data members of the
class, with no further initialization or actions.
Page 44
Constructors have their own special syntax in declaration. The constructor does not have a
return type like a function, as there is an implicit return of an object of the class type. The
constructor is named exactly as the class is named. Then, there is any number of parameters for
the constructor, similar to a function or method declaration, followed by code. An example
constructor for our Color class might be as follows.
class Color
{
int red;
int green;
int blue;
public:
Color( int redVal, int greenVal, int blueVal );
void setGreen( int value );
double getGreenRange0to1();
};
And the code for the constructor defined in the .cpp file as
Calling a constructor only happens to create an object, not as a regular function call. The object
is declared as before, but now, the arguments are passed in following the variable name (not the
class name!). Use of the constructor might look like this:
Color inkColor(200,100,100);
Note that the Color object would be created fully formed and the data members initialized
before any other action can happen to the object. So the constructor has safely built a fully
initialized and consistent Color object without needed multiple steps in the main function.
Page 45
Overloaded Constructors
Overloading can be used to bring back a default-style constructor. A common practice is to
declare a constructor with no parameters and an empty code block, which allocates the
memory for an object. Such a constructor would look like
Color() {};
In this case, the code for the constructor has been defined inline with the declaration. Somewhat
confusingly, a constructor with no parameters is called the same way as the default constructor,
without parenthesis. So
Color inkColor();
is illegal. This is because the compiler views that statement as declaring a function prototype of a
function that returns a Color object, with function name inkColor , and which takes no
parameters. This potential ambiguity is done away with by not allowing an empty constructor to
be called in this way, and many people rightfully are somewhat unhappy when confronted with
this.
Object Pointers
Just as pointers to built-in data types are allowed, so are pointers to class objects. The following
code is an example of a class pointer.
Color screenColor(100,200,100);
Color *pageColor = &screenColor;
Just as new can be used to create an unnamed variable pointed to by a pointer variable, new can
be used to construct objects and store their location in a pointer.
The empty constructor can also be used in this context, although now both the class name
without parenthesis or with parenthesis is legal, as the ambiguity of the statement is removed.
Both of the following statements are legal.
Pointers to classes have their own syntax for accessing the members of the object. Instead of a
period between the object and the class member, the arrow operator -> is used. So the
following is the appropriate way to call a class method from an object pointer.
Page 46
pageColor->setGreen(50);
The arrow notation is equivalent to first dereferencing the object and then using the period
notation, as in
(*pageColor).setGreen(50);
Destructors
An object pointer that points to memory allocated by a new call needs to be deleted after it is no
longer needed to avoid a memory leak. When an object is deleted, either through an explicit call
to delete, or by having a local variable leave scope, then the object destructor is called. Just as
there was a default constructor, there is a default destructor. A destructor should cleanup
resources used by the object, either memory allocated in a constructor, or releasing system
resources. Destructors are labeled with a ~ notation. An example class with an empty destructor
is
class Color
{
int red;
int green;
int blue;
public:
Color( int redVal, int greenVal, int blueVal );
~Color() {};
void setGreen( int value );
double getGreenRange0to1();
};
For now, we have no good reason for having code in the destructor. Also, note that while
constructors can be overloaded, destructors are called automatically and thus there is only one
version with no parameters needed.
Copy Constructor
There is another constructor automatically built by the compiler along with the default
constructor, although this one remains even when custom constructors are defined. This
additional constructor is called the copy constructor, and it takes an already defined object of a
class and creates a copy of it. The copy constructor is used like
Color screenColor(200,100,200);
Page 47
In this example, a custom constructor is used to create a Color object. Then a different object is
declared, and its data members are filled with the values in the data members of the argument.
The automatic copy constructor is usually adequate for most purposes. However, if the class
constructor performs actions beyond that of initializing values, such as grabbing system
resources, a custom copy constructor might be needed to replicate that (somewhat
unadvisable) action.
A more subtle issue arises when the data members are pointers to memory allocated at some
point during the object’s lifespan. Since the copy constructor copies the values stored in the data
members, it copies the memory locations stored in the pointer and does not allocate new
memory for the new object. Thus, any changes to the values pointed to in either the newly
created object or the old copied object will show up in both objects, as they share memory. This
is likely to have unintended side-effects and memory leaks. Instead, a custom copy constructor
would need to allocate new memory and copy the values contained in the original to the new.
Page 48
#include <cstring>
class intPtr
{
int *val_ptr;
public:
nt value ) {
intPtr( i
val_ptr = new int;
*val_ptr = value;
}
~intPtr() {
delete val_ptr;
val_ptr = NULL;
}
int get_value() { return *val_ptr;}
nt value) { *val_ptr = value; }
void set_value( i
};
int main()
{
intPtr intVal(4);
intPtr other( intVal );
intVal.set_value(6);
return 0;
}
This defines a class which holds a pointer to an integer. The custom constructor allocates
memory and then assigns a value to that location. The destructor deletes that memory and then
assigns the value NULL (which is included from the header <cstring> and means a non-valid
memory location) to the pointer.
In the main code, one of these new objects is built. Then the default copy constructor is used to
make a new intPtr and copy the values of the data members from the old to the new. Since the
data member is just a pointer, the memory location stored in the pointer is now shared.
When intVal.set_value is called, the pointer is dereferenced and the value changed. Since the
other intPtr shares that memory location, it is now pointing to that changed value as well. Even
worse, when the program finishes, the destructor for each of these main variables is called, and
each one tries to delete the memory it points to. The second effort at delete causes an error.
Page 49
Instead, a new custom copy constructor needs to be built which allocates new memory for the
new object and copies over the contained value (if that is the desired behavior). This is
sometimes called a “deep copy”.
Copy constructors are declared just other constructors are, but the parameter must be a
reference of the same type as the constructor. Making the parameter const is usually the correct
choice.
Making a custom copy constructor usually implies that a custom destructor is needed as well,
along with what is called an assignment operator, which we will discuss in the next section.
Overloading Operators
C++ makes heavy use of special character operators in its syntax, as opposed to the use of
words as used in other languages. Many of the operators use special characters either derived
from mathematical notation, from older computer languages, or just some unique character.
This use of special characters also aids in internationalization, as operations are not linked to
English words.
This special character operator syntax would not make particular sense if it only applied to the
built-in data types and custom classes needed to define word-oriented methods to perform
actions. Instead, C++ allows operator overloading within a class, so that custom classes can also
use this same syntax.
Look at the example Color class. One could imagine wanting to add two objects of type Color
together. This could be done with
Color resultColor;
resultColor.red = screenColor.red +
otherColor.red;
resultColor.green = screenColor.green + otherColor.green;
resultColor.blue = screenColor.blue + otherColor.blue;
Page 50
The code declaring the operator would then look much like the expanded version given as the
not operator example.
Operators are declared in a special way, although similar to methods. The method prototype for
the ‘+’ operator in the Color class is:
So the operator returns something of the same type as the operand, uses a special syntax to
show which operator is being overloaded, then takes some arguments. In this case the argument
is the second Color object past the operator.
return resultColor;
Note that the operator prototype doesn’t really look like how the operator is called (val1 + val2).
Think of the operator as being a special way of calling a method. So instead of the also legal
val1.operator+(val2), you can use a more natural infix notation.
Operator overloading is a wonderful thing. It makes your special custom classes behave just as if
they were built-in types. You should not just grab random operators and overload them for any
behavior; the overloaded operator should match existing behavior as much as possible. For
example, do not overload new to print output to the screen – such behavior is legal, but
impossibly lazy, sloppy, and undisciplined.
Page 51
Chapter 7:
Managing Projects
A large part of the motivation for object-oriented program is to keep useful code together as a
way to encourage code reuse. C++ has specific mechanisms for organizing large projects and for
incorporating other code into your own project. This chapter discusses those mechanisms for
Visual Studio. Topics include
Code Reuse
Thus far, we have built small scale projects with just a few files: perhaps a main.cpp, then a class
.h file and its associated code in a .cpp file. The use of an #include directive has been the main
mechanism for gluing these pieces of a larger program together, and there has been some
mysterious stuff going on to make the code in one file available to the other.
Because of the wide availability of other people’s code on the Internet, useful and interesting
projects are now available to people by writing just a few lines of custom code and connecting
to these existing packages. This is a relatively new phenomenon based on
6. the popularity of the idea of open-source software, where code is published freely and
improved by collective groups of people interested in maintaining and improving that
software, and
7. a cheap and simple distribution mechanism; namely the Internet and indexed search,
which allows finding very specific systems which accomplish a desired task.
Really useful new systems can now be created, not by a traditional painstaking development
process, but instead by recognizing how existing capabilities can be combined in new ways to
allow new functionality.
Page 52
Libraries
It would be possible to create large projects simply by collecting large numbers of .h and .cpp
files that do certain tasks. There are a number of issues with this approach. First, those .cpp files
would have to be compiled into separate object files (usually saved with a .o) and then when a
main function is compiled, those object files would have to be searched each time a needed
function is called to find the matching compiled function. If there were thousands of object files,
this would be resource intensive. Secondly, updating and maintaining those files would require
careful management. Finally, many groups do not want to release the source code to a
proprietary project.
C++ uses a mechanism called a library (think of a library of not books, but instead classes or
functions that you can access), which is essentially just a bunch of object files stuck together
with an index at the top. One kind of library, called a static library, ends with .lib in VS and .a in
many other systems, such as Unix. A static library contains code that gets incorporated into the
final executable program file, making such programs easily distributable (but unable to be easily
updated). The alternative to a static library is a dynamic library, which will be discussed later.
Your code still needs to know how to call the functions or classes contained in a library. For this,
most libraries have an associated header file that can be included in your code. So most external
projects will require you to download a library of some sort as well as a folder or directory of
header files.
To illustrate making a library, look at the process of making a simple Circle class into a library. In
VS, start a new project and select the usual Win32 console application and name it circleLibrary.
In the wizard, go to the “Application settings” on the left panel. In the setting, select a static
library and uncheck “Precompiled header”.
Page 53
Add a circle.h and circle.cpp file to the project. Add code as needed, then build the solution. This
will make a circleLibrary.lib file.
From a code perspective, using a library is very similar to using a .h/.cpp file within a project. All
that is needed is to #include the header for the library in your .cpp file, like #include <circle.h>.
Then the classes and functions declared within that header file can be used in that .cpp file.
The compiler must know where to find the static library. When a .cpp file is included in a
project, the compiler knows to look at the associated .o file when trying to put together the
executable. Since the .lib file for the sample library is buried down in the project structure of the
library project, the compiler needs a little more help finding it.
There is actually a special step of the compilation process calling linking, where calls to objects
that were not defined in the project’s files are looked for in external libraries. The program that
performs the linking is called a linker. So it is the linker that really needs to know about the circle
class code.
The Project->Properties defines where these various external components might be found.
There are three main items to change:
8. Header files that are not part of the current project need to have a directory specified
where they can be found.
Page 54
Page 55
Then, the compile system needs to know where that library file actually lies, which is under
Linker->General
Now, if main creates a variable of type Circle, or calls a method of that class as made in the
library, the actual .lib file gets queried for the code that performs that operation, and it should be
indistinguishable from having the .h/.cpp files in the project.
Page 56
The examples above show setting properties for the debug compile. In the upper left, you can
also select All Configurations, so that the includes and libraries are setup for when you want a
release version.
You also need to decide how to specify the path. There are a few main options:
1. Give the full path (C:/something). This is common when a library is installed in the
system.
2. Use a relative path. Visual Studio defines an environment variable SolutionDir. Including
a line like $(SolutionDir)/myLib path gets you started wherever the .sln file is. If your
libraries are in that folder, you can give a path to them.
3. Defining a system-wide environment variable. Some projects will either automatically or
have you define an environment variable for use in project setup. We will discuss them
more later, but you could use it like $(CirclePath)/libs to get to the Circle library.
Page 57
Chapter 8:
Template Programming
A language with defined types, such as C++, can create repetitions of code to handle each kind
of type. Templates are an important innovation that allow programming generically for types.
Topics discussed in this chapter are
✓ Template programming
✓ Template classes
✓ Pitfalls of template programming
Templated functions
Imagine writing a simple max function that returns the maximum value out of two arguments
passed into the function. This code might look like
Such code is easy to write and understand. However, imagine another programmer starts to use
your function but has not read it carefully and calls it with doubles instead of ints.
Page 58
Being an astute programmer, you notice this flaw, and industriously start to make versions of
max for doubles, for ints vs. doubles, for doubles vs. ints, for chars, for chars and ints, for bools,
for bools and ints, bools and doubles, and so on. Exhausted, you are about to leave work, when
your boss asks you to add max functions that also handle a variety of class types, like Circles
and Squares and Spheres and Cubes, which all have overloaded operator> to compare their
areas. This is a frustrating situation, primarily because the code is exactly the same in all these
cases, with just the argument and function types varying from function to function.
The keyword template indicates that the following code is indeed a template. Inside the <>is the
keyword class followed by a user-defined, generic type name. A common type name is “T”. We
use the word genericType to indicate its meaning.
When a template function is called, the compiler tries to generate code using the type of the
calling arguments in place of the generic type name. In the max example, the calls to max with
int arguments generates actual code specifically built to handle ints and to return an int. When
max is called with doubles, new code is generated to handle doubles. All of this generation is
done at compile time, so it does not cause a performance slowdown.
This specifies that the max function will return a value of the same type as the parameters, and
that the parameters are the same type. There can be additional parameters of a fixed type as
well – not all parameters need to be generic. Similarly, the function could return a fixed type, like
a bool, instead of the template version.
Templated functions are often called with the template type given explicitly, as in
Page 59
If there is no ambiguity, then the <double> can be left off the calling sequence.
Note that this template function will also handle any user-defined class that also has an
overloaded > operator. If the type does not have the overloaded >, then a compile-time error
will occur.
Note that this is not completely correct, as the return type is always of the type of the first
parameter, but it demonstrates the principle. Also, the operator> is less likely to be well defined
for all combinations of user-defined types, although that could also be handled by other
template functions. Such multi-template functions are called by giving the desired types inside
the <>, as in
double d1 = 2.3,
float d2 = 3.4; / / declare some double values
double maxd = max<double,float>(d1, d2);
Template Specialization
Finally, some situations call for the same code for many types, but perhaps with one frustrating
case which needs some different behavior. For example, imagine wanting to compare char
values, but to make capital letters behave the same as lower-case, perhaps for alphabetizing.
The built-in char > char behavior counts upper case as always greater than any lower case
values.
While a custom function could be written with a different name, this would make the code more
difficult to read, and other users of the code would have to investigate the behavior of this
differently named function. Instead, templates allow for template specialization, in which case
the case is explicitly given. For example, a specialization of the max function might look like
Page 60
template <>
char max<char>(char val1, char val2) {
// toupper makes uppercase
if (toupper(val1) > toupper(val2))
return val1;
else
return val2;
}
The initial template keyword with an empty <> alerts the compiler that this is a specialization.
Instead of generic types, the actual type is used. Finally, the specialized type <char> is appended
to the function name, which is reminiscent of how the function is called -max<char>(‘a’,’b’). This
is followed by whatever custom code is desired, in this case, we promote the char to upper case
before comparing it.
Class Templates
Just as functions can be made with templates, it is also possible to make class templates. The
idea is that some part of the class, either internal data members or method return types, should
be based on some type parameter.
The case for template functions was clear – there are many examples where the logical
functionality of a chunk of code is the same but the types of the return values and parameters
vary. Besides the max function used as an example one could imagine things like swap, which
exchanges the values in two parameters. Really any operation that takes two values and returns
a value is a good candidate.
Templated classes, on the other hand, may appear to be a more exotic beast. However, there is
one very common use for template classes and that is for a concept called container classes.
Container classes are data structures that hold values. An array, for example, is a container class.
C++ already has a defined array structure, such as
int exampleArray[10];
which is a structure that “contains” ten integers. Note that array containers can hold different
types of values depending on what type is specified. For a beginning C++ course, we have spent
embarrassingly little time working with arrays. Part of the reason for this is that there are better
structures available in C++ and they make heavy use of templates.
One thing that standard C/C++ arrays do not do well is handle situations where the array needs
to handle more or fewer elements. Once a C-style array is defined, it cannot change how large it
is. Many people have written their own dynamically-sized array class which uses new and
delete to add a resizing capability and it is worth looking at such a class. Note that a normal array
Page 61
can be an array of any type of object, and this is a desirable property for our dynamically-sized
array.
One reasonable way to develop a template class (or function), is to develop it for a specific type,
and then generalize it as a template once the functionality for one type has been tested.
class DynArray {
ublic:
p
DynArray(int len=10) {len_=len; data_ = n ew i
nt[len];}
~DynArray() { d elete[] data_; }
int len() c onst { r eturn len_; }
int o perator[](int i) c onst { return data_[i]; } / /access
int& perator[](int i){ return data_[i]; } /
o / set
void resize(int newsize) {
i nt *newdata = n ew i nt[newsize];
f or (int i = 0; i < len() && i < newsize; i++)
newdata[i] = data_[i];
d elete[] data_;
data_ = newdata;
len_ = newsize;
}
private:
int len_;
nt* data_;
i
};
The class works by storing the number of elements in len_ and then allocating member in the
pointer data to store those values. Care must be taken to new and delete storage for data_ as
needed. Also, an assignment operator and copy constructor are not defined and should be
coded to allocate new memory as needed.
There are actually a number of new or slightly new language features used here and they are
worth discussing, even if a digression from our eventual goal of a template class.
Default Parameters: The constructor uses a default parameter. If the constructor is called
without any arguments, the value 10 will be used automatically. Default parameters can be used
Page 62
in any method, constructor, or function, but they must be the last parameters in the parameter
list.
Deleting Arrays: The destructor uses a “delete[]” instead of a plain delete. When freeing up
memory allocated by a new array, the delete operation must be told that the memory held by a
pointer is an array. A basic rule is if you new an array, you must delete[] the array.
Setters by Reference: The class uses an overloaded operator[] to mimic the access to a standard
array. The getter version returns an int and is a const method that does not change the value of
the object. The setter, instead of taking in a new value and returning void as seen before, returns
a reference to the data that is going to be set. So the getter now returns a reference to an
internal data value in the class. This allows statements like
This is usable in more situations that fake arrays. A setter can return a reference to a normal data
member, so a statement like
is possible. Setters that make a change and return void would make the left-hand side of
assignment operations be the void, which cannot be assigned a value. Many people argue that
this return of a reference to a private data member is a bad idea – that by directly exposing the
internal structure of private data members, you by-pass that protection. Others argue that the
basic idea of getters/setters is a bad idea and that instead code should focus on providing
high-level operations for an object, such as a “move” method rather than a bunch of x() and y()
getter and setter methods.
Once an example class is working, converting to a template class is simple. As with functions,
add the template keyword above the class along with the name of the generic type being used.
Then replace the specific types used in developing the class code with the generic type name. A
template version of the dynamic array class is
Page 64
Chapter 10:
The Standard Template
Library
C++ allows rich extensions to the language through mechanisms such as operator overloading.
Programmers working within a well-developed code base have access to data structures,
functions, and classes that make them much more productive than programmers starting from
scratch. The standard template library is a part of C++ that enriches the programming
experience for all. Topics discussed in this chapter are
In the last chapter, a templated version of a resizable array was developed; first as a class of
specified type, then converted to a generic container class that can hold many classes of
objects. While this first pass was pretty successful, one could imagine adding numerous
refinements, such as automatic resizing (should we always add just enough space, or might it be
more efficient to keep some extra space around to avoid recopying the old array?), or functions
that search the array for a value. For a time, early C++ systems were rife with re-invented and
re-implemented basic extensions to standard C data types. Some of them even worked
correctly.
The Standard Template Library, or STL, is an approved part of C++ that implements many basic
data structures and operations of computer science. For the most part, they are done far better
than you can do, and so you should try to use them rather than build your own version. The
objects and functions of STL live in the standard std:: namespace, although additional includes
are often required.
Page 65
As might be inferred from the name, the STL makes heavy use of templates. This makes sense as
many objects in the STL are containers, which hold and manipulate other data types. Before
looking too deeply at the design decisions in the STL, we will examine one particular class that is
both useful and matches up well with the resizable array implemented in the last chapter.
STL Vector
The STL vector is designed to be a resizable array class. It is called a vector to draw upon the
mathematical notion of a multi-dimensional holder of data. Unlike some languages, the STL
vector only holds one type of object and that type is defined in the template declaration of a
vector variable. So a vector that holds int is declared as
In order to use the vector template, the following #include statement must precede any use of
vector,
#include <vector>
Prepending the vector declaration with std:: can be avoided as usual with a “using namespace
std;” statement.
Notice that values is not an array, so it is not initialized with a size as an array would be
(values[10]). If an initial size is desired, the size is sent in to a non-default constructor,
which would pre-allocate ten spaces for ints in the STL vector named “values”. Adding a [10] on
the end rather than (10) would not call the constructor to size it properly, it would instead make
an array of 10 std::vector types, which would be a strange mix of the old and the new.
The vector can be used exactly as an array. Values can be retrieved and stored in the vector by
using the overloaded operator[] provided by the template vector class. The following chunk of
code allocates a vector and stores values in the vector.
vector<int> values(5);
Note that the current size of the vector is retrieved with a size() method, which returns an
unsigned int. This is already an improvement over a standard C array, where the size must be
tracked by the programmer. The size of the vector can be changed with the resize() method.
Page 66
So far, there isn’t anything new in the vector class over the class we developed. However, the
STL environment provides a rich set of functions that work on its classes as well as additional
means of adding and retrieving data from its classes.
Safe Indexing
The STL vector does provide safer indexing into the vector. Instead of using values[index],
elements of the vector can be retrieved or set using values.at(index). If the index is out of
bounds of the allocated vector, an exception is thrown. We have not yet discussed exceptions,
but they are a mechanism to catch and report or fix errors. Visual studio will break and report an
uncaught exception, but even this behavior is vastly preferred over merely using memory not
belonging to the vector, which is what a standard C array would do.
Automatic Resizing
One simple example of some of the additional functionality provided by the STL vector is the
push_back method, which allows adding information to the end of the vector without knowing
exactly where to put it or having to resize the vector to accommodate it. This might be useful
when asking for a list of numbers from a user, for example.
One interesting thing about adding data to a vector through push_back is that sometimes the
vector chooses to allocate additional space and save a recopying step. The actual space
available is found through the capacity() method, which can differ from the size value of the
vector. The capacity of a vector can be directly controlled through the reserve method.
Some programmers with experience in other languages may question why all out of bounds
allocations do not result in automatic resizing of the vector. One possibility may be that
excessive resizing does cause a huge performance penalty, and the STL developers did not want
to mask those performance decisions too much. At least when using push_back, there is a
conscious choice to allow a resize, and STL also tries to mitigate some of the performance
penalty through reserved capacity.
There are also a number of useful functions that work on STL vectors (and other STL data
structures), but in order to use those properly, we must first discuss another STL concept called
iterators.
STL Iterators
So far, we have accessed the data in an STL vector through array-like mechanisms. STL also
provides a generalization of a pointer called an iterator, which becomes more useful for other
STL data structures (like a list), but also applies to vectors. An iterator is defined as part of a
class, so to declare an iterator variable, you would use
Page 67
To access the data associated with the iterator, use the overloaded dereference operator. Note
that this is not really a direct mapping of a memory address to data – the iterator merely mimics
that behavior. So, using the above declared iterator, I could say
STL containers have a number of methods to start using iterators. To assign the first element in a
vector to an iterator, use the begin() method, as in
element = values.begin();
The end of the vector can be determined with values.end(). This is really one past the end of a
vector, so a for loop that iterates over the range of a vector would be used like
This is really the “proper” STL way to do this. One advantage is that not all STL container classes
supports the operator[] access to data, so the STL form is more interchangeable with other
structures, allowing easier code modification.
STL Algorithms
For the more casual user, iterators become more important when using methods and functions
that work on the container classes. For example, a useful STL function is “find”, which takes an
open-ended range of elements and a desired value and returns an iterator to an element equal
to a desired value or an iterator past the range bounds. The range of elements is specified with
iterators. An example for an entire vector is
If the value 2 was not found in the vector, then the iterator found would be equal to vector.end()
(recall that is one past the end of the vector), which can be tested for. Otherwise, the iterator
would point to the first found value in the range. This can be used to safely display the found
element, as in
Page 68
The range can be specified using arithmetic, at least for vectors, which support random-access
iterators. Other data types cannot jump arbitrarily ahead. So for a range of two values, find
would be called like
This would just look at the first and second elements of values. Also note that there is no bounds
checking on these operations, so it is possible to introduce errors using these operations.
STL provides a number of functions to determine min and max values, copy sections of vectors,
sort values, and to perform an operation on each element of a container. We will learn more
about STL functionality as we look at different container classes. Many of these need the
#include <algorithm> for C++ to use them.
Sequence Containers
An STL vector is an example of a sequence container, which are data structures similar to arrays,
where the elements have some linear ordering. Other STL sequence containers are deques (or
“deck”), and lists. A usual default choice of structure is the vector, but the others have specific
advantages and should be used where needed. The vector is efficient for accessing elements of
the vector by index, or with random access, but it can only efficiently remove elements from the
end of the vector, and somewhat efficiently grow at the end. The deque, on the other hand,
grows efficiently at either end of the sequence. Lists can have items added or removed
efficiently anywhere in the sequence, but elements can only be accessed by starting at the
beginning of the sequence and iterating to the desired location. Thus, specific applications can
be dramatically more efficient using lists compared to vectors.
Associative Containers
A different kind of STL structure is associative containers. While sequence containers are
designed to access elements by some kind of index, associate containers retrieve elements by
value. An example of this is the telephone book, where a value (the phone number) is retrieved
by a name value. While this could be implemented as a large array, it is not clear how to
efficiently map names to some kind of index in the array. Instead, associative containers can be
thought of as paired values. One value, called the key, is passed to the associated container,
which returns the paired value.
STL Map
The STL map is a specific example of an associative container. It is called with two values to its
template representing the type of the key and the type of the value. An example map intended
to hold employee IDs and the employee name would be declared as
Page 69
This map can be populated with data by adding key/value pairs. One mechanism is to use the
overloaded operator[], which accepts the key as a sort of index, and then returns or assigns the
value.
Note that there is no explicit control of the size of the map. Elements are added as needed. Also,
do not be fooled by the use of numbers inside the square brackets into thinking that the map is
really an array. If the map had been defined as Map<string, int>, the type inside the square
brackets would have been a string, like
employees[“Neo”] = 145;
Imagine wanting to output all the stored data in a map. If the keys were unknown, this would be
very difficult without using iterators. The iterators still define a begin() and end() for maps, and
allow iterator increment to move over each element. Iterators can get to the key and value pairs
by using a first and second public data member, as in
A loop of a map iterator from the map begin to end would also pull out all the key-value pairs in
the map. Unlike a vector, where all the values can be examined by looping over the indices from
0 to size()-1, the iterator is the only way to look at each item in the map without storing all the
keys.
Think about what kind of container you would use for the following situations:
4. Storing a collection of regular, ordered temperature readings over the course of a day
5. Storing a collection of time-stamped readings over the course of a day
6. Maintaining a collection of commands for a robot to execute in order.
7. Maintaining a collection of commands with the ability to insert high-priority commands
at the front of the order.
8. Storing a robot state at a position on a map
Page 70
Big O Notation
The charts of container performance rely on a measure called big-O notation, which is written
like O(n). Big-O notation gives an upper bound on performance as the number of elements
tends towards infinity, which means only the fastest growing portion of the bound is given. So
for an algorithm that gets more expensive quadratically with the number of elements, but also
has a linear and constant term, the big O would be written as O(n2), as that term dominates all
others as n goes to infinity. This is true even if the weight on the linear term in high compared to
the quadratic term. There are other notations for average and best case bounds.
Many STL operations are constant time, or O(1). Others are O(log n), which means it grows
slowly relative to the number of elements. Operations that are linear or quadratic need to be
used carefully as problems grow larger.
Chapter 11:
More on I/O – Strings,
Files, and Serial Ports
Surprisingly to most people, the C++ language standard provides few mechanisms for
communicating with hardware, and these types of activities are often accomplished through
machine and compiler specific extensions. Many of these actions are reliant on the use of strings
and file streams, which will be discussed, along with more specific implementations for sending
data over a serial port. Topics in this chapter are
✓ Strings
✓ File
Page 71
Strings
C-style Strings
Since strings are just arrays of characters, the original string definition in the C language was just
that, with a few special ways of initializing them. In C++, a C-style string is sometimes known as
a null-terminated string, for reasons we will soon see.
char cstring[2];
cstring[0] = ‘H’;
cstring[1] = ‘i';
Clearly, initializing a long string, such as a sentence, would be painfully slow. To get around this,
the use of double quotes around a collection of characters is interpreted as a series of chars.
One common issue is that functions that work on C-style strings need the array of chars to end
with a null value ‘\0’ (the \ denotes a special character of value 0) so they know when to stop
processing. So my example of “Hi” above is not quite right, a 3rd space needs to be reserved and
assigned to null. A more common way of declaring a string is using the double quotes directly,
as in
which allocates an array of the needed size to hold the chars in “Hi”, plus the null termination
character. While c-style strings leave no doubt as to their array-like structure, they are a
common source of errors.
C++ Strings
C++ provides an additional data type, the string class, to improve the usefulness and safety of
strings. C++ strings need to #include <string> and also live in the std namespace. C++ strings
mask the array nature of the variable and allow direct assignment of the string to the variable. An
example of creating and using a string is
#include <string>
using namespace std;
int main() {
Page 72
Just like other variables, a string can be declared with a default constructor and assigned later.
string name;
name = “David”;
String Input
The standard input stream used with the extraction operator can be used to allow user input.
Using cin extracts out the text up until a blank line, tab, or newline, so that multiple items can be
requested from a single line. Processing of the line only happens after enter is pressed. For
example, the following code allows a user to enter a name.
string name;
cin >> name;
If the user types the word David followed by enter, then the string would contain “David”. If the
user types David Johnson followed by enter, then the string would still only contain “David” as
the space breaks the processing for that operation. In order to capture the first and last name,
there are two approaches. Either the extraction operator can be used in sequence, similarly to
how a series of insertion operators are used with cout, or the getline command can be used,
which grabs the full line of text and puts in it a string. Both approaches are shown in the
following code snippet.
The getline is an appropriate way to enter strings with spaces into a single variable.
String Operations
C++ strings provide greater functionality than C-style strings. Strings can be easily concatenated
together using the + operator. So from the example above, a full name variable could have been
done using the first approach followed by
Comparison between C++ strings is done using the usual equality operator, ==. This is in contrast
to the more complicated C-style strcmp() function.
Page 73
String variables can also be accessed using array indexing. It is more correct to use
string::size_type as the type of an index rather than an int. An example for-loop, which uses that
index type as well as the length() method for strings is
This loop shifts each letter forward by 2, so the ‘a’ becomes ‘c’ and so on, which is a code known
as a Caesar Cipher.
Another form of indexing is possible. Strings are actually STL sequence containers, so that the
usual STL iterators apply, defined as a string::iterator.
String Searching
Strings have their own find method with slightly different behavior from that of the generic STL
find. This find takes in a substring and a starting position and returns the index into where the
substring was found, or the special string::npos to indicate the substring was not found. An
example use is
string fullname;
getline(cin, fullname);
string::size_type pos = fullname.find(“Neo”,0);
if (pos != string::npos)
cout << “Found him” << endl;
String Manipulation
A substring can be pulled out of the original string using the substr() method, which takes a start
location and the open-ended end of the interval (one past the desired end). On a substr call that
starts with 0, this is effectively the length of the desired substring.
Parts of the string can be modified or erased using higher-level functions than indexing into the
string array. Strings can be inserted into other strings using the insert() method, which also takes
a starting location and the string to be inserted. This could have also been used to make a
fullname from the last and first strings as follows.
Elements can also be removed from the string using the erase method. Erase takes a starting
index and the number of characters to remove from the string.
Page 74
String Streams
Strings can also be treated as streams using the stringstreams functionality. This can be useful in
placing multiple elements into a single stream, or parsing a single line into multiple elements. The
parsing example is more common, so let us examine how this works.
First, the program must #include <sstream>. The input string stream is an istringstream object,
which wraps the original string. Imagine an example where a function is given a whole line
containing a first and last name. The string stream can be used as follows to extract out each
component name.
String streams are often used to parse complicated input from a user and to avoid user
mistakes. If a desired input line were a name and an age, and the user mistakenly typed a first
and last name before the age, then a straight cin implementation would try to assign the last
name to the integer age. The cin.fail() method can detect when errors occurred, but the
programmer then needs to ignore the offending characters and try again. String stream
processing of the full line can more easily detect and recover from errors by allowing multiple
passes over the full line string.
File I/O
Reading from and writing to files is related to getting data from the console and outputting data
to the same – it is through the stream mechanism. C++ provides fstream and the more
specialized ifstream and ofstream for input and output to files.
The basic mechanism is to #include <fstream>, then to declare a variable of the appropriate class
type and open a file by name. For example, the following program sends a line of text to a newly
created file.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
ofstream outputFile;
outputFile.open("test.txt");
outputFile.close();
Page 75
return 0;
}
Opening a File
The important lines are the declaration of outputFile followed by its method open(). The open
method takes a null-terminated string with the file name of the output file. If the file does not
exist, it will be created where allowed. The open method takes an optional series of flags which
determine the behavior of the opened file. For example, open can be instructed to append new
information to the end of an existing file rather than rewriting its contents. An open for that
mode would look like
outputFile.open(“someexistingfile.txt”, ios::app );
where ios::app sets the append behavior (with app short for append). Another interesting
behavior is ios::binary, which writes data in a more machine readable (and compressed) format
than text. Binary form is also more appropriate for maintaining the precise values of things like
doubles, where the decimal text form does not exactly capture the value of the number.
The arguments to the open command can also be sent to the constructor for the stream, so that
the open can happen inside the variable declaration and a distinct open is not necessary.
Without an explicit path, the file is created and opened in the VS project directory. It is
instructive to write some data then to examine the file using an accessory such as Wordpad to
look at it outside the VS environment.
if (myfile.is_open())
There is also the general purpose good() method, which checks for a number of possible errors
is the file reading and writing process. A typical sequence would be to attempt opening a file,
then to wrap all the code outputting to the file in an if-then statement block that executes when
the is_open() or good() is true.
Closing a File
Files should be explicitly closed in order to open up use of the resource for others. The close
method accomplishes this. Close also synchronizes the internal stream state to the physical
Page 76
device storing the file, as do new lines in the data and the flush method. There is often a limit to
the number of open files allowed, and while large, failing to close files can lead to errors.
string line;
// this could use !inputFile.eof() instead
while (inputFile.good()) {
getline(inputFile, line);
cout << line << endl;
}
The sstream package could be used to parse the line into individual parts, or the input stream
could be used like cin along with the extraction operator to pull out individual data elements.
Use of the sstreams, while an additional layer of complexity, is encouraged to allow error
checking.
Serial ports are handled differently by different operating systems and compilers. Some systems
treat them essentially like files, and most take some form of this approach, but serial ports have
additional communication rules that need to be specified.
A serial port generally refers to hardware which communicates using the RS-232 standard. One
distinguishing feature of the standard is that data is sent in serial, or one data bit followed by the
next. Other hardware devices, such as USB, also follow the approach, which is in contrast to the
parallel port on many computers. On Windows machines, serial ports are generally called COM
ports, such as COM1 or COM4. Many modern machines lack serial ports, although there are
hardware converters between RS-232 connections and USB ports.
Terminal programs can be used to communicate with serial ports, and are a good way of
checking the basic communication strategy outside of a custom application. One free terminal
program is Realterm (h ttp://realterm.sourceforge.net/), although it takes some looking through
its different options to use effectively.
Serial ports generally require that the speed of the connection is specified, along with error
handling and communication characteristics. For the most part, this requires knowledge of the
device that will be listening or talking on the other end of the connection as the characteristics
must be typically set in advance rather than detected or agreed upon by the two devices, as that
Page 77
itself requires communication. This is usually done by reading the manual (a rare time where this
is actually needed!). The primary parameters to set are the following.
Speed or Baud Rate: The speed of the communication is sometimes set dynamically, or more
often, through DIP switches or jumper cables. There are a common set of speeds related to early
teleprinters that communicated with computers. The most common rates are 1200, 2400, 4800,
9600, 14400, 19200, 38400, 57600 and 115200 baud. Baud means symbols per second, and
since serial ports send either a high or low signal, baud is equivalent to bits per second.
ata Bits: The number of data bits indicates the bits per character sent.
D
Almost all modern systems use 8 data bits as this matches up with an
8-bit byte. Some systems have used 7 bits, for pure ASCII character
representation and other variations are possible.
Parity: A parity bit is used to detect errors due to line noise. An odd parity bit is added to the
data bits and set such that the number of 1 values in the data and the parity bit is odd. So if the
data were 11100000 the added parity bit would be 0, so there remains an odd number of ones
(3 of them). If the data were 11000000, then the added parity bit would be 1, to force the total to
three as well. If the number of received ones is even, then there must have been a transmission
problem. There are also even parity bits. More commonly, the number of parity bits is set to
zero, and error checking is handled at a higher-level in the communications.
Stop Bits: The stop bit is used by the hardware to resynchronize the data communication
between each character. There is almost always 1 stop bit used.
Handshaking: The serial connection may manage the communication between devices using a
handshaking protocol. The flow of communication may be managed by hardware, which
requires additional lines outside the data transmission lines, by data values on the data lines, or
not managed at all. Using a software handshaking causes problems with binary data, since the
values aren’t restricted to a subset of 0-255 like ASCII values are, so there are no free values for
control.
Serial ports still have some advantages over more recent wired protocols. They use fairly high
voltages, so that the cables can have long runs. They can also be very compact, with just a few
wires used. While they are starting to die out on standard PCs and laptops, they are still
common in servers, specialized scientific equipment, and small embedded systems.
Page 78
This is an instance where re-invention of the wheel is both difficult and likely to be less complete
than already existing solutions. The Boost asio library provides components that can be put
together into a serial port library, but still requires some work. As an example simple serial port
library, we will look at a recent cross-platform solution at https://github.com/wjwwood. Go to
the Releases tab to download as a zip.
The library follows the model of files, with an open() and close methods, and then some
specialized setup methods that determine the communication information. Instead of
overloading the insertion operator, the serial class uses a write() method.
The constructor takes a C++ string like “COM2” to indicate which serial port is being opened and
a baudrate. There is one additional oddity with opening serial ports. Ports numbered higher than
9 need a special string pre-pended to the COM name. The final string needed is of the form
\\.\COMXX, where XX is the COM number. If you are typing that into the console, you should be
able to type that directly. If you are using a pre-made string to add to what the user types in
(which is the recommended way), then it will look a little bit different, as I will try to explain
below.
We have seen some examples of adding special characters to strings. For example, the string
"hello\n" has a newline character added to the end. The backslash "\" alerts the compiler that the
next character is special. Looking at the string above, the characters that need to be added are
backslashes. The compiler is expecting a special character after a backslash. So backslash is
itself a special character, and "\\" adds a single backslash to a string. If I make a string within the
program, then "\\\\.\\" adds "\\.\" to the string. So something like cout << “\\\\.\\” << “COM12”
would send \\.\COM12 to cout.
Many of you will be using a USB-serial convertor to talk to the serial port. The convertor maps to
a COM port (and these USB convertors are often high-valued COM ports) and you can
determine which port by looking in the Control Panel at the Device Manager and looking at COM
ports.
During construction of a Serial object, the port opened. The parameters of the port canbe
adjusted using a number of setters, such as set Parity. The constructor also has optional
arguments to set most parameters. Success of the port opening is tested with a isOpen() method
of the port object.
The write() method sends a C++-style string to the serial port. Others variations exist, such as
sending a c-style string and number of characters.
Serial communication can get much more complex when the two connected devices talk to
each other. When the communication is one-way, the above information should suffice to get
started working with a serial port connected device.
Page 79
Chapter 8:
Using an Image
Processing Library
As a first example of using a large project, this chapter will discuss incorporating OpenCV, a
large image processing library, into your code. Such a large system provides examples of design
choices that are applicable to other projects. Topics discussed in this chapter are
✓ Installing OpenCV
✓ Working with images
✓ Data types for memory intensive objects
OpenCV
Image processing libraries are useful tools for acquiring data from the real world. They can help
set up equipment, such as cameras; they can improve the quality of images through such
operations as de-noising and smoothing; and they can help identify objects in the scenes
through segmentation or feature recognition.
Page 80
One currently popular, open-source image processing library is OpenCV. OpenCV is a good
example of a large codebase in that in order to use it, you must become comfortable with its
C++ objects, its conventions, and some of the underlying design decisions. Using OpenCV also
highlights one of the dangers of C++ - the encapsulation promoted by object-oriented
programming means that a developer can start using the provided objects and their interfaces
without knowing the messy details that lie beneath, but when the actual behavior clashes with
assumptions about behavior, then bugs are more easily introduced. In these types of systems it
really is critical to read manuals and tutorials.
Installing OpenCV
A common first step in any system is installing it on a computer. There are usually two main
possibilities for accomplishing this. The easiest is if the system includes pre-built static libraries
(.lib), dynamically-linked libraries (.dll) and the header files needed to use those in code. Recall
that development using an external library just needs a header file to include in any custom
code, as well as a library to link against.
The other choice is to download all the source code for the external system and to compile it in
to a library yourself. This can be more complicated, as you may have to install even more
libraries in order to successfully compile the system, and minor variations in your system and
compiler may generate different results or even have the project fail to compile. The main
advantage of compiling the system is that it allows a developer to modify the project to work
under slightly different circumstances, perhaps to support a new camera or new algorithm.
Additionally, if the system does get compiled under your setup, you can be confident that you
will be able to work with the libraries you generate as they already match your system.
The main web page for openCV is at http://opencv.org/. It contains a Download section, with a
link for a Windows installer. Our target is to download OpenCV2.4.9, the executable installer
should be saved to your computer. Once downloaded, this file can be double-clicking for
installation.
The preferred mechanism for using openCV is to use its dynamic linked libraries, or dlls. In
contrast to static libraries, where the code in the libraries gets incorporated into the final
executable, a dll defers final connection between the executable and the classes or functions in
the dll until the program is actually run. The process of using a dll is similar to that of a static lib,
but with one additional step.
When writing code, the proper header file that describes the available classes and functions in
the dll must be included. This tells the compiler that code for these prototypes will be provided.
The path to the include needs to be added to project properties. The .lib associated with the dll
must also be added to the project. The compiler works through the code and uses the .lib to
Page 81
make a preliminary connection between your code and the code in the dll. Then, at run-time,
the operating system looks for the actual code in the dll. The dll should be in the directory where
the executable is run, or the directory of the dll set as the PATH (or Path) environment variable in
the Control Panel. When the program is run, this information is used to locate the actual
machine language code needed. It is also possible to load the dll from the running program, so
that different dlls that provide different functionality can be swapped in on the fly.
Understanding Images
OpenCV is designed to manipulate images. These images can be loaded from a variety of image
formats, or captured directly through some device, like a webcam. Having some basic mental
model of an image is important for deciding how to perform operations on them.
Images are collections of data that specify the color of each picture element (or pixel) in the
image. These pixels lie in a 2D grid – together they make up an image. Images are usually
specified with a resolution, which is expressed either as
These are familiar from hearing about various specifications. For example, a webcam might have
a 0.3 megapixel sensor in it, or have a native resolution of 640 x 480 (those are roughly
equivalent). There are also various codes that indication the resolution, such as an XGA display,
which is 1024 x 768 pixels.
There are two other important parameters that define an image. One is the number of color
channels used in the image. A single color channel contains just grey scale information. A color
image has more possibilities, but a common one is a vector of three values specifying the (red,
green, blue) weights of the pixel. A fourth channel, the alpha channel, is commonly used to
specify a transparency value.
Each value can also have a precision associated with it. Many image formats use integer values
from 0-255 for each pixel, which corresponds to 8 bits per color. An image with one bit
precision with one color channel is a black-and-white image. The image format details how the
bits are allocated to different parts of the color.
When fewer than eight bits are used in a color channel, then sometimes this indicates an index
color. In an indexed color, the value in the pixel is compared with indices of a table, and the
entry in the table at that value is used.
Page 82
OpenCV Basics
A large system like OpenCV takes some time to become productive in using it. The basic object
in OpenCV is a Mat. A Mat is a matrix and an image store. We will concentrate on its use as an
image. A Mat is declared by giving is its rows and columns and type, as in
Note the use of the size() accessor available from the Mat class.
The type is based on OpenCV keywords. A useful example is CV_U8C3, which means each pixel
has 3 channels (the “C3”), or values for red, green, and blue, and each value is stored as an
unsigned integer of 8 bits (the “U8”), which allows a range of 0-255 for each color.
Assignment Operator
One place where OpenCV has unexpected behavior is in the case of a simple statement like
The default behavior is to make what is called a shallow copy of objects when possible.
Essentially, the underlying data is shared. This is for efficiency reasons, as image data is large and
slow to copy, so programmers should be forced to explicitly make a real, or “deep” copy. The
way to make a deep copy is the clone command
OpenCV does this by defining its own operator= in the Mat class. The compiler automatically
defines an operator= for when one variable is assigned to the other. The default operator= copies
the data values of the class data members. If there are data members that are pointers in a class,
those pointer memory locations are copied, and the pointed to data becomes shared between
the two objects. OpenCV takes advantage of that to make that the default behavior, but also
adds additional information about how many times a pointer is shared, so that it can be safely
deleted when possible.
Page 83
The convert color function changes the form and/or number of channels in an image. It can be
used to convert from a color image to grayscale, for instance. In this case, the image is being
converted from a red,green,blue image (stored in OpenCV’s default (b,g,r) style) to a
hue,saturation,value format. Imagine the primary colors laid out in a triangle. If you fill in the
gaps between those colors and smoothly change from one color to another, you get a circle of
color values. The position along this circle is essentially the hue. Saturation is how dominant that
particular hue is. A high saturation is a very “pure” color. A low saturation is closer to a grayscale
value. The value parameter is how light or dark the color is.
Another operation is thresholding. Thresholding looks at an image and if the pixel value falls
within a specified range, it sets the pixel in a new image to white. If the source pixel falls outside
the range, the new image gets a black pixel. The OpenCV function for thresholding is:
Here, hsvImage is the source image. Every pixel is examined to see if its value lies above the 2nd
argument and below the 3 argument. Since the source image has 3 color channels, the min and
rd
max must be specified with three values each, one for each channel. So this particular call is
thresholding such that the hue must lie between 75 and 95, the saturation must lie between 110
and 240, and the value must lie between 10 and 90. The thresholded image is the same
resolution as the source image, but it has only one color channel, as the pixels are all black or
white.
OpenCV supports a number of intuitive arithmetic operations on images, such as adding them
together, subtracting them, scaling the components and logical operations. There are also many
classic image processing operations such as smoothing and edge finding. Learning about these is
best done by looking at the documentation.
Page 84
Chapter 12:
Class Inheritance
Thus far we have avoided one of the primary defining features of object-oriented programming:
class inheritance. This approach allows new classes to build on the data members and methods
of other classes. This chapter looks at two primary reasons for using inheritance:
Object Inheritance
A goal of structured programming is to reduce the amount of redundant code. Some techniques
we have seen in this direction is pulling repeated code into a function, where is can be easily
reused, or more formally into a class, and template code, which reduces re-implementation of
code for different data types.
There are other scenarios where we would like to extend the functionality of existing classes.
Imagine wanting a class with the dynamic array functionality of the STL vector, but which also
offers some statistical analysis of real numbers contained in the vector. While there are some
stand-alone functions in the STL space for performing computations on vectors, we wish to
encapsulate the data and methods in a class.
One approach would be to use an STL vector inside the new class as a data member. An
example with a method to compute the average of the values might be
class Stats
{
public:
std::vector< d ouble > data;
double average() {
Page 85
One issue here is that we have exposed the data member named data as a public member. This
has the benefit of allowing statements like
Stats vals;
vals.data.push_back(1.0);
vals.data.push_back(2.0);
but the notation of having to go from the object name (“vals”) to the data member (“data”) to its
methods is pretty clunky. In addition, the data member should be hidden, or made private, to
encourage encapsulation.
If the data member were to be made private, then the class provides no way to add data or
retrieve values from the class. So the programmer will need to replicate a large portion of the
STL vector methods in the Stats class, by providing a Stats class version of operator[],
push_back() methods, safe access through at(), and so on. This repetition is both frustrating to
the programmer and dangerous in that it adds another layer of code that needs to be tested and
validated.
Inheritance
The solution in C++ is to use inheritance. In inheritance, data members and methods of one class,
called the “base class” can be used in another class. The syntax for adding a base class to
another class is as follows in a class prototype.
The : public baseClassName notation indicates that the functionality of the base class
should be available to the derived class. This example looks a little more complicated than it
needs to be as it uses a particular instance of a template STL class as the base class.
The equivalent class to our original Stats class might look like:
double average() {
double sum = 0.0;
if (size() == 0) r eturn 0.0;
nt index = 0;
for (unsigned i
index < size(); index++)
sum += (*this)[index];
return sum / size();
}
};
Note that there is no longer an explicit data member in the class. There is still a vector in the
class, but the information is now in the base class. The size() method can also be called directly.
It comes from the base class, so it is accessible to the StatsVec class as well as from variables of
type StatsVec. A main program using StatsVec might call size as follows
StatsVec vec;
vec.push_back(1.0);
double avg = vec.average();
vec.push_back( vec.size() );
In this example, the vec variable can use things like size() and push_back() directly without
having to go through a data member intermediary. So vec behaves like an STL vector, but it also
has the average method it defines in its own class.
Constructors
There are some things that are not accessible from the base class. The base class constructors
and assignment/copy operators are not directly usable. This is because the derived class needs
its own code to be made properly, and values should be assigned to any new data members in
the derived class. Default constructors are provided, but a programmer typically needs to make
more powerful constructors.
There is a bit of a dilemma here. We would like to avoid rewriting the implementation of the
base class constructors, but they cannot be used inside the derived class constructor since
constructors are not called explicitly, they are called implicitly during an object’s construction.
C++ uses an initializer notation to get around this problem. A constructor for StatsVec could be
added to allow an initial size of the vector.
The body of the constructor is empty, but a size is passed to the constructor in the base class.
Page 87
Note that we can freely use methods from the vector class, such as size() and capacity(). At the
end of this new method, we want to call the actual base class push_back() method, and this is
done by giving the proper scope to the class by prepending the base class name and :: to the
call. Once again, the class name looks a little funny since it is a template class in another
namespace.
Access Control
There is a new access control specification that can be used when one class inherits from
another. Private members are still private to any code outside a class. Protected members are
public to derived classes and their friends, but private to code outside those classes. If classes
are going to be derived from a class, this is often the proper choice for access control. On the
other hand, you may wish to preserve a strict interface to the base class and only manipulate it
through its public methods.
Polymorphism
Polymorphism, which roughly means “the capability of assuming different forms”, has a distinct
meaning in programming. In computer science it is commonly the idea that different types can
be computed with using a similar interface (as defined through a class, for example). We have
actually seen several examples of polymorphism, but they are typically not what people think of
when speaking about polymorphism in object-oriented programming. The common sense of
polymorphism is that several related classes can have a common interface (i.e. their methods
are the same) with possibly different behaviors, yet all can be handled and worked with
generically using a common base class. Before this is explained further, it is worth defining a little
more precisely some of the other forms of polymorphism already encountered.
Page 88
Ad-hoc Polymorphism
Ad-hoc polymorphism is essentially the overloading process used in functions and methods.
The “+” operator works on a variety of data types, yet its behavior can be either subtly or vastly
different. For example, look at the subtle difference of adding two ints compared to two
doubles, or the much different behavior of using plus on two strings, resulting in concatenation.
In this case, there is a common interface, the operator+, and the behaviors depend on the type
of the objects involved.
Parametric Polymorphism
Parametric polymorphism is implemented in C++ through the template concept. This is a fairly
strict concept of polymorphism, as the same code applies to all data types, so the behaviors
should be very similar. The idea of template specialization allows some variation, but for the
most part, this is most successfully applied when trying to save some time dealing with the
strongly-typed requirements of C++.
Coercion Polymorphism
Coercion polymorphism is essentially the explicit or implicit casting that can allow a function,
method, or operation to work on different types by first casting the different types into a form
the code can work on. This can also happen somewhat unexpectedly when a class has a
constructor that takes a value of one type, and then uses that constructor to cast it into a new
form. A subtle bug can happen when a constructor changes a value into something else,
by-passing the normal strict type checking in C++.
This happens because C++ is allowed to try once to convert inappropriate parameters into
another type by casting, or by using any one-parameter object constructors that are available.
This can be a nice shortcut, for example if you call int_max(2, 3.0), the 3.0 can be turned into an
int and accepted by the function, but can also cause mistakes when the programmer
misinterprets the appropriate calling sequence but the compiler allows it.
An example of the latter case might be when a class myArray has a constructor myArray(int
size) and a function print( myArray array). The print function should print out each element in the
array. Later in a main function, the programmer defines an int count = 10, then even later
carelessly calls print(count), expecting it to print the value of the count variable. Instead, if there
was no function for print with an int argument, the compiler sees the constructor for a myArray
that takes an int argument and uses it. The count variable is converted into a myArray of size 10
with no values, and this array is sent to the print function. Because this is not caught by the
compiler, it unexpected behavior may not surface under casual testing and later cause
problems. These are also the kind of problems that can have a programmer convinced that there
is something seriously weird going on, as printing an int causes 10 empty values to be printed
instead.
Page 89
The cure for this particular problem is the explicit keyword, which only allows constructors to
be used when explicitly called. They are a good idea to be used by default on single parameter
constructors, or constructors with only one non-default parameter. The explicit keyword is used
before the constructor declaration, like explicit myArray(int size).
The general approach is derived from the idea that a pointer to a base class can also point to
publicly derived classes as well. However, methods called through these pointers will always
use the base class implementation rather than any overridden methods in the derived class. A
slightly different approach makes the base class an abstract class, which is a general definition of
an object, with only the derived classes actually allowed to be realized.
Examine the following example, which uses an abstract base class and two derived classes.
class Pet {
public:
oid speak() = 0;
virtual v
};
Page 90
The class Pet is abstract. You may use pointers to Pet in your code, but not actual objects of type
Pet. Its methods are all virtual, indicated by the virtual keyword in front of the method prototype.
There is no code for the method and it is assigned equal to zero.
The derived classes of Pet, on the other hand, can be created. They both inherit from Pet, but
then override the virtual function with their own definition. The advantage of this approach can
be seen in the following bit of code.
myAnimals.push_back( &myCat );
myAnimals.push_back( &myDog );
myAnimals[0]->speak();
myAnimals[1]->speak();
Here, one object of each derived class is created and then a reference for each pushed onto a
vector of type Pet *, a pointer to the abstract base class. This vector can now share different
types in one array. Because the derived classes have the same interface, other functions can be
written for Pet*, and the appropriate derived method will be called inside the class.
One can imagine using this type of approach for things like defining a robot in a simulation
environment, where a base class of Robot defines common functionality (walk, plan,
manipulate, and so on) and then derived classes for particular hardware would create definitions
appropriate for the particular device. Another example might be code to work with different
cameras, where a camera base class provides a common structure for more specific definitions
in derived classes based on the actual camera.
Page 91
Chapter 13:
Event-Based
Programming
Event-based Programming
Far back in the early days of computers and programming – yes, even before I was born, the
basic model of computing was for a programmer to write a program and then to submit the
program to be run. The program was added to a collection, or batch, of programs and run when
machine time was available. Clearly, this model precluded an interactive session with the user.
As computers advanced, direct connections to a computer were available, although the basic
output was to a printout on a continuous roll of paper and input was through a keyboard. Again,
while the user could now enter information, it was only when the computer asked for data, so
input was under the guidance of the program logic.
This is in contrast to the kinds of applications that most people are now familiar with; ones that
respond to all kinds of user input, from key presses in games, to mouse clicks and motion, and
more recently, to finger presses and swipes on a screen. The order of these actions by the user
is not predictable in advance by a program, so the logic of the program must be more flexibly
constructed.
One model of computation to handle these sorts of interactive systems is called event-based
programming. In contrast to the largely linear flow of non-interactive systems, where a program
can be read from the top of the main function and traced down to the bottom, an event-based
Page 92
program is less sequential looking. The basic idea is that something external to the program,
such as a user, generates actions called events. These events are stored in an event queue and
dispatched to pieces of code called event handlers, or callback functions. These event handlers
process the event, perform some action, and then implicitly return control to the event
dispatcher.
The main function no longer dictates the sequence of actions a program performs. Instead, it is
largely responsible for setting up the match between an event and the event handler. This
process is called registering the event handler. One issue with event-based programs is that
understanding the logic of the system takes effort, as a typical calling sequence of event
handlers must be inferred and finding the “important” parts of the program might be difficult. For
example, the key part of the code might be in a handler for mouse clicks, or it might lie in a
screen refresh event. Another issue is that setting up a proper graphical user interface (GUI) can
be tedious. There are events for resizing windows, for place and removing another window on
top of the application, and hundreds of others, and these events should all generate appropriate
actions in response. Luckily, many GUI toolkits provide a GUI builder which generates default
actions for typical events.
The standard C++ library provides limited support for highly interactive programs, as these
depend on the operating system to generate specific events. Instead, a number of
cross-platform tools have been developed which mask the implementation details from users.
As a demonstration platform, we will look at event-based programming in the context of the Qt
GUI environment. First, though, we will look at a more C-style example of event-based
programming.
The glut library is a utility library designed to run alongside OpenGL applications. OpenGL is a
standard graphics library similar to DirectX on windows. Here is an example glut-style
event-based programming application.
#include <GL/glut.h>
void K eyboardCB( unsigned c har key, i nt x
, i
nt y
)
{
witch(key)
s
{
c ase ' q': e
xit(0)
;
c ase ' h':
printf("SimpleGL p rogram - h
it 'q' to q
uit\n");
break;
}
}
void IdleCB()
Page 93
{
im.simulate_step();
s
g lutPostRedisplay();
}
Void DisplayCB()
{
raw();
D
}
First, look at the main loop. There are two calls to glut which initialize glut and create a window
for drawing and interaction. Then there are a number of calls which register several programmer
created functions to specific events in the system. At the end, the event loop is started, which is
a glut provided call invisible to the programmer called glutMainLoop. The program control goes
to this function and only returns when exit() is called.
The KeyboardCB function is written by the programmer, and sent as an argument to glut to let it
know that this particular function will handle keyboard events. The callback function is of a
preset and known format so that the way the glut system calls the function matches the way the
programmer writes it. In this particular case, the glut system will send a char variable indicating
which key is pressed, as well as an x and y location of the mouse in the window when the key
was pressed. The programmer-written keyboard function does not use the x and y values.
Page 94
Parsing this looks complicated, but it is saying that it takes an argument that is a function named
“func” which has to have parameters key, x, and y. The function is really treated as a pointer
when it is passed.
The Qt Toolkit
The Qt toolkit is probably the dominant GUI toolkit available for development. Besides handling
events, it also has a rich library of classes which allow a developer to place buttons, sliders, text
boxes and many more interaction objects on the screen. These types of objects are often called
widgets.
Qt is much more object-oriented than the C-style glut toolkit. Instead of registering callbacks
with the system, the programmer must inherit from some standard base classes and provide
event-handling methods for particular objects. In many ways, this is a much cleaner
development style as there is not this mysterious binding between events and callback
functions. However, there is still no explicit, visible call to the event methods, so the flow of
control in the program can still be difficult to discern.
As with any large system, using Qt takes time and practice to become proficient in. One of the
simplest things to do in Qt is to open up a window, which is a basic capability in an interactive
system. To show the power of widgets, we will put a text editor in the window.
#include <QApplication>
#include <QTextEdit>
har *argv[])
int main(int argc, c
{
QApplication a(argc, argv);
QTextEdit textEdit;
textEdit.show();
return a.exec();
}
This part looks very similar to the glut example. Just like glut, the Qt system is initialized, but in
this case by making a Qt object called a QApplication. Then a text edit widget is made. After
showing the editor, the main event loop is started by called exec. At this point, a user generates
events by typing or clicking the mouse and the widget responds with its pre-programmed
Page 95
functionality. A good UI library will have dozens of preprogrammed widgets with the
functionality needed to easily make a modern looking program.
By looking at the documentation, we can learn about these widgets and their existing
capabilities. For example, we can add a button to the scene with a QPushButton object and
showing it. QPushButton is constructed with a string that is displayed inside the button and the
include for QPushButton needs to be added to the code for it to work.
If you try to add the button, you will probably see that the button and editor are displayed in
two separate windows. In order to specify where items should go, you must create a layout.
Layouts
User interfaces (UIs) are typically built up by layering collections of elements. Rather than
specifying exact locations of buttons, sliders, text boxes and so on, individual elements are
added to groups which provide hints as to what the user interface should look like, but also
provides flexibility in case of window resizing.
The active elements of the UI are essentially all widgets. Beyond the basic widgets, such as a
button, which generally have some direct interaction mechanism, there are more complex
widgets which have their own complex behavior or interaction process. An example of a
complex widget is QCalendarWidget, which provides a visual mechanism for selecting a date.
Other widgets provide some organizational capability, such as QGroupBox.
Layouts are different from widgets, especially from the organizational widgets. The organizations
widgets provide some interface mechanisms, such as tabs, for moving between groups of
widgets. Layouts specify how a collection of widgets should be placed on the screen. Widgets
are added to a layout, and the layout is passed to a parent widget by calling the widget’s
setLayout method or passing the widget in to the layout constructor. An important thing to
remember with layouts is that they do not hard-code pixel coordinates of where widgets should
be placed. They specify the style of how several widgets should be placed, like a vertical line,
and use these style to make final decisions on widget size and position. An example use of a
QVBoxLayout (vertical box layout) is:
16
17 window.show();
18
19 return app.exec();
20 }
Styles
Visual styles are an important part of a satisfying UI experience, although the defaults supplied
with GUI libraries are generally usable. Qt allows setting very specific style and color properties
on widgets and their children, or setting styles to match established look and feel of a computing
environment, such as for Mac OS X.
Specification strings can be passed to widgets to set colors and other individual elements. An
example showing a background color change is
QWidget w;
w.setStyleSheet( "background-color: yellow" )
;
A more complete stylistic choice can be set through creating a known style out of the
QStyleFactory, which is then applied to the application.
QObject::connect(quitButton, SIGNAL(clicked()),
&app, SLOT(quit()));
Page 97
In this case, clicked() is a signal generated by a QPushButton when it is clicked by a user. That
signal is connected to an action slot in the QApplication called quit(). That action terminates the
event loop and finishes the program. You need to look in the documentation to learn what
signals and slots are available. The real power of Qt is exposed by writing your own signals and
slots, which makes the Qt framework very flexible for new situations.
Macros
The words SIGNAL and SLOT in the connect call above are examples of macros. Macros can act
like functions, but are really a compiler directive that performs a textual substitution. A simple
example of a macro is
That Qt is using macros means they are doing some pretty complicated things that forced them
into an awkward construct with the macros.
Static methods
The connect call also uses some unusual syntax. Instead of a stand-along function connect, it is
QObject::connect(...). This is a static method of the QObject class (which is a base class for many
Qt classes. A static method can be called without making an object of that type, in this case an
object of QObject type. Static methods are somewhat like stand-alone functions (which are
clearly called without needing an associated object), but the static method still has access to
private methods of the class, so it is “more powerful”. It is somewhat unusual to need a static
method, but Qt has some very tricky stuff to make it all work.
Page 98
void pushed() {
Pushed the button!" << std::endl;
std::cout << "
}
int main ()
{
int m,n;
m = operation (7, 5, &addition);
}
Here, the last parameter of the operation function is int (*functocall)(int,int), with the first int the
return type of the function and the int,int inside the parentheses indicating that the passed in
function should take two ints as its own argument.
The ability to pass in functions can be very powerful. An example usage is in a numerical
method, where a general solver determines if the system has converged by using a passed in
Page 99
function, allowing users to quickly extend the power of the library with their own convergence
test.
Strangely, calling operation like m = operation (7, 5, &addition); is the same as calling it with
addition with no ampersand. C interprets both as a function pointer. When passing a class
method, however, the & must be used.
Summary: The connection syntax for Qt uses a static class method that either uses SIGNAL and
SLOT macros to connect the two widgets or uses class method and function pointers to link the
GUI event and an action.
Another example of using a connect is to link a slider value with a display value. A portion of
code that would replace the text edit and push button from the last example is
Note how the changed value of a slider gets propagated to the display of the lcd number. A
movement of the slider triggers the valueChanged signal, which carries an integer value along
with it. This signal is connected to the display slot of the LCDNumber display (akin to an LCD
clock number), which stores and displays the passed in number.
Building a Qt Application
Qt applications are typically built by adding a custom widget to a QApplication. In this case, the
custom widget inherits from the Qt system, so that it acts like other widgets in the system. In this
section, we will work, step-by-step, through building a simple notepad app.
Page 100
Project Structure
The wizard provides a simple main function that shows the custom NotePad object and starts
the event loop with a.exec(). There are also notepad.cpp and .h files with an initial structure for
our custom class. The NotePad class looks like
public:
NotePad(QWidget *parent = 0);
~NotePad();
private:
Ui::NotePadClass ui;
};
The first line shows that it does indeed inherit from QMainWindow. The next line is a
strange-looking Q _OBJECT which, if you hover your cursor over it, shows that it is a complex
macro adding some Qt code into the class in a compact and hidden fashion.
Following the macro are public constructor and destructor for the class and a private data
member called ui. The ui data member gets used when a designer uses the graphical user
interface builder to add widgets to the application. The Qt compile system looks at the graphical
builder data file, generates a pile of C++ code in a custom ui class (in this case, NotePadClass),
and attaches it to the code we write through this private ui data member. In order to understand
this more fully, we will use the graphical builder and add some UI elements to our application.
The Designer has a lot of interface to lean. In the center, is the current
state of the NotePad class. Looking on the right at the Object Inspector,
Page 101
you should see that the class already has some widgets, including a QMenuBar. On the left are
UI elements you can add to the application.
You should start with a layout, which defines how the visible elements are arranged. Start with a
Vertical Layout, drag it onto the NotePad and stretch it out. Look down on the list of widgets on
the left for Input Widgets and drag a Text Edit onto the layout. Then drag a Push Button onto the
layout. You should see each new widget appear in the Object Inspector at right, as well as see
them on the NotePad.
Right-click the graphical representation of the PushButton and select Change objectName, and
change it to quitButton. Right-click it again and change text to Quit. You should see something
like
Page 102
Testing
Qt Designer can check the functionality of the UI without compiling. Go to the Form menu and
select Preview. You can type in the Text Edit and when you hit quit, the preview goes away.
Compiling
In order to run the actual program, you need to save the .ui file. So use File->Save. Quitting Qt
Designer also saves the file. In Visual Studio, hit F5, or build the project. There are several things
that happen here. Qt runs a Meta-Object Compiler (moc) on the code, which generates the full
C++ code. It also converts the .ui file into the ui_notepad.h file. Look under Generated Files and
open up ui_notepad.h. In there you can see data members for each of the widgets you added in
Qt Designer, such as the QTextEdit *textEdit and the QPushButton *quitButton. The setupUI()
method in there adds the widgets to the layouts and sets any text on the objects. At the bottom
of the method is code for a connect between the quitButton and NotePadClass that specifies the
connection we drew in the Qt Designer. The code should bring up an application that allows
typing in the text edit and quits when the button is pushed.
Customization
The built app does not do anything that Qt does not already provide for us. We would like to
add our functionality to the Qt application. In this case, the programmer can add custom slots
and signals to the project class, in our case, the NotePad class. As an example, we will add a
button that loads a file and dumps the contents into the Text Edit widget.
Open Qt Designer again by clicking on the .ui file. Return to Edit Widgets mode using the icon on
the Tools bar. Pull the Quit button out of the vertical layout. Add another button called Load.
Select both buttons then choose “Horizontal Layout” from the icon bar directly above the
NotePad layout screen (see circled region in image). Drag the collected buttons to the bottom of
the TextEdit to make a connected text edit with two buttons. The Object Inspector should show
the proper hierarchy of layouts and widgets – compare yours with the image.
Page 103
We need to write a custom slot in our NotePad class to load a file when the load button is
pressed. Save the .ui and open the notepad.h file.
Custom slots
Qt provides new access keywords beyond the standard C++ public”, protected: and private:.
You can now declare methods to be public signals or slots as well. We will write a custom slot
that loads a file and puts its contents in the textEdit. In the notepad.h file we add a public slots:
line and declare the method
public slots:
void loadAndDisplay()
Then we will add the code to the .h file (it could/should be in the .cpp).
Qt provides some useful and familiar widgets to select a file and provide error messages. Look at
the following example.
void loadAndDisplay() {
QString fileName =
QFileDialog::getOpenFileName(
this, "Open File", "",
"Text Files (*.txt);;C++ Files (*.cpp *.h)");
if (fileName != "") {
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
QMessageBox::critical(this, tr("Error"),
tr("Could not open file"));
return;
Page 104
}
}
The first part of the code calls a getOpenFileName dialog, which pops up a browser for files. The
rest of the arguments do things like specify what types of files to look for. The second part of the
code checks to see if the file name could be opened and pops up a message box with an error
message if it fails.
Other things to notice are that Qt is such a big system that it has its own versions of C++ strings,
called QString, and a replacement for files QFile. This is a bit of a pain as a programmer must
learn these new data types, but they are largely similar to standard ones.
QTextStream in(&file);
ui.textEdit->setText(in.readAll());
file.close();
Which makes an input stream and reads the whole file (through in.readAll() ). Note that we can
change the contents of the textEdit box with a ->setText call. Since the textEdit is defined in the
ui object, it is accessed in our code through ui.textEdit.
The place to make the connection is in the constructor for NotePad, as that is where the
application is setup. NotePad.cpp has an existing constructor which sets up the ui data member.
Following that, we can make a connection between the loadButton and the custom slot. The
needed code is
NotePad::NotePad(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
QObject::connect( ui.loadButton, SIGNAL(clicked()),
LOT(loadAndDisplay()));
this, S
}
In the constructor, we connect the loadButton, defined in the ui object and its clicked() method
to our custom widget (this) and its loadAndDisplay() slot. This pattern is quite common – make
Page 105
a custom slot, then connect it to a signal in the class constructor. At this point, the application
can be tested – for example, load a project.cpp file and it should display in the text box.
Model-View Widgets
More complex user interfaces need to track and edit information in concert with information
collected and stored elsewhere. For example, a simulation may have a list of parameters that
can be changed by the user. The GUI could have widgets, possibly sub-classed with custom
data members to contain some of this data that needs to be displayed and edited.
This data now needs to be synchronized with the matching information in the main program.
This is not insurmountable, but becomes more error prone and complex as the interplay of data
becomes more complex as well.
Model-View widgets address this by allowing external storage for displayed and edited
information. The main advantage of this form is that the model can be shared among several
views while staying synchronized. The disadvantage is that it adds another layer of complexity
to the code, and the model must be derived from an abstract QT base class in order to be
readable by the views. An example of a list of string items shown in one or two (uncomment the
second) views is in the following code.
har *argv[])
int main(int argc, c
{
QApplication a(argc, argv);
QWidget w;
QStringList list;
Item1" << "
list << " Item2" << "Item3" << "Item4";
QStringListModel* model = n ew QStringListModel();
model->setStringList(list);
ew QVBoxLayout(&w);
QVBoxLayout *layout = n
ew QListView(&w);
QListView *listView = n
listView->setModel(model);
layout->addWidget( listView );
// layout->addWidget( listView2 );
w.show();
return a.exec();
Page 106
Page 107
Chapter 14:
More on Debugging
Debugging is often treated as an art, learned by painful experience. Modern debugging tools
provide a wealth of information that can help a programmer to understand running code. The
main concepts of the chapter are:
✓ Debugging approaches
✓ VS debugging tools
Approaches to Debugging
“If debugging is the process of removing software bugs, then programming must be the process
of putting them in. ” - Edsger Dijkstra
Debugging, then, can be viewed as an introspective process where both the problem and
program code must be examined and tested until program results are inconsistent with the
expectations of the programmer. This inconsistency then can provoke further understanding of
the problem or code and eventually yield a fix. The goal of debugging should be to produce test
cases and output from the code that can quickly produce these inconsistencies.
A more formal view of debugging quickly yields some parallels with the scientific process. The
programmer observes the program and some problem, makes a hypothesis about the cause of
the problem, designs an experiment to test that hypothesis, runs the experiment, and analyzes
the data. This process should be repeated until some confidence is gained about the validity of
the hypothesis.
Most novice programmers fail to do much more than the first step, which is to observe the
problem. After that, they often spend a lot of time looking at textual representations of the code,
Page 108
which is a poor representation of a running program. The code must be run and tested to see the
problem in action.
A programmer could spend all day looking at that and it basically looks correct, unless there has
been a recent reminder about cin leaving dangling newlines, which then are taken as a line from
getline. The programmer might have been so concerned about whether the robot was actually
listening to communications that all the debugging effort is spent towards the end of the process
rather than where the command was acquired.
Simplify
Another problem comes from becoming overwhelmed by the complexity, or just the tedium of
working through some example data set. After all, that is why we want to have the computer do
the work in the first place – it is a pain for a human to work through it. In response to this
problem, the programmer can try to create a minimal set of input that still produces the
symptoms of the problem. Another approach is to use the debugger tools to only start
Page 109
examining the working code after certain conditions are met. This approach can be especially
important if the full problem takes a long time to run. Having to wait for ten minutes to get to a
program crash can be excruciating.
Be Willing to Move On
A famous quote from the detective Sherlock Holmes states “When you have eliminated the
impossible, whatever remains, however improbable, must be the truth.” (from The Sign of the
Four). People can become bogged down in a small chunk of code that they hypothesize contains
the error. If all the variables are monitored and match what the expectations are, then the error
likely lies before or after and the debugging person must be willing to expand the search to
these regions.
Program Defensively
There are many ways to program in a defensive manner so that bugs and errors are more
quickly identified.
1. Quickly get to a minimally running program and add functionality to it, testing as new
parts are added. This helps fix compile-time errors, as the error must be in the recently
added code, and run-time errors, as many errors are caught before the program
becomes large and complex.
2. Take the time to test helper functions as they are written. If you add some small helper
function, like a clamp function that keeps a variable within some range, write some small
test code at the beginning of your program to check the function correctness. Be
especially sure to check the boundary conditions and special cases, such as negative
values, end of loop values, empty lists, and so on. If no easy mechanism exists to
produce output, set breakpoints and use the debugger.
3. Keep some tests in the code to check for correctness. The assert() mechanism can help
catch subtle errors and can encode the programmers assumptions. For example,
Page 110
The assert will cause a program break and dump a message when the assertion is false.
There is a near mythology given to the best debuggers. Typically the stories follow a common
motif of a problem stumping everyone, the hero arrives, examines the evidence, and quickly
deduces an outlandish, but correct solution. This is an encoding of the idea that subtle bugs
require a complete view of the entire workings of a program with a fantastic attention to detail.
The process of debugging can build up that complete view in your mind, and the tools and
techniques you use to aid the debugging process can aid in that attention to detail.
Some Specifics
Errors can be generally divided into two main areas. There are compiler errors, which are
usually quickly dispensed with, and runtime errors, which are failures of logic rather than syntax,
and are more complicated to deal with.
Compiler Errors
Compilers spew out an uncomfortable amount of text when there are syntax errors in a program
and this can often overwhelm beginner programmers. There are two main things to keep in
mind when looking at compiler errors:
1. The first error given is the most important and possibly the only true error. The other
errors may be from when the compiler got off track from the first error. So fix the first
error and glance at the others, but don’t work too hard on them until you compile again
after the first is fixed. As a related issue, if the compiler starts to complain about
something far from where you just added code, it is probably something like a missing
quote, or semi-colon, or parenthesis where the compiler chewed through a bunch of
text trying to complete a statement.
2. The code below an error could not have caused the compiler error. So if an error is
proving difficult to find, commenting out sections of code may eventually narrow the
area of search.
Linker errors are another form of compiler error. These are often in the form of statements about
unresolved or undefined symbols or functions. Forgetting libraries is the most common cause
here. Another common cause is not adding class code properly, for example adding a method in
Page 111
a .cpp file but forgetting to prepend the class:: to the method, or having an inconsistent
parameter list between the header and .cpp file.
Runtime Errors
There are a few common categories for runtime errors.
1. Memory access faults: Pointers that are set to incorrect memory locations can cause a
program crash. The debugger is usually the best way to find these as the debugger will
stop on the line that caused the crash. Sometimes the crash happens down in the system
code. In this case, you should use the call stack to see where in your code the system
code was called. The call stack keeps track of which function called what at the current
point of execution.
2. Division by zero: Some systems will crash on illegal math operations. VS sets the floating
value to an error state for some of these, so there might not be a crash.
3. Logic errors: These are among the most difficult problems to solve as the program runs
but it does not produce correct output. This usually requires very careful verification of
the program to determine the cause and suggest a fix. This is where careful use of the
programming environment’s debugging tools is important.
Unit Testing
Many software systems have formal expectations for how you should build up and test your
code. One concept is that of unit testing, where each small software “unit” has associated test
code which checks for common conditions. There are formal systems for managing this test
code, but the idea of it is sound – you should write code to test your code as you go.
Debugging Tools
Setting breakpoints in the debugger is an important first step. The program halts execution at a
breakpoint and allows examination of program state. Clicking in the column at the far left side of
the code allows setting a breakpoint. Right clicking on a breakpoint provides a menu of useful
options for when the breakpoint is activated. Right clicking on a line of code also gives the option
of running the program until that line is hit.
Data Tips
Once a breakpoint has been hit, there are a number of ways to examine program state. One of
the easiest is to use the Data Tips in VS. Hover the cursor over a variable, and a small window
holding the variable’s value will float over the variable name. This will disappear when the cursor
is moved, or it can be made permanent by pinning it to the code.
Page 112
One cool feature of the data tips (and other ways of looking at variable values), is that you can
hand-edit values and then continue the program, so you can trigger special case behavior
yourself, or get past some troublesome piece of code to test later parts.
Locals
The same variables can be seen in the Locals window. Use the + options by the names to see the
full details of more complex data structures. If you have been running a simplified test case on
some problematic code, this is a good way to compare your expectations of program behavior
with the actual values.
Watch Window
The watch window is a way of adding specific variables of interest to a list to observe them. If
the locals window gets too full, or it is hard to track a moving around variable as you change
scope, the watch window allows fuller control.
Autos Window
The autos window tries to show variables that contribute to the local line of computation as
automatically determined by Visual Studio. It can also be helpful when faced with a large
number of values hiding the information you want to look at.
Immediate Window
The immediate window allows some limited, direct interaction with program code. For example,
you can call a helper function with some test values and get an answer out. It is not really
intended as a way to make C++ act like a scripting language, or to be a shortcut test suite instead
of writing code to test your program, but as a way to quickly answer some simple questions
about program behavior.
http://www.codeproject.com/KB/cs/MasteringInDebugging.aspx#heading0009
Page 113
Chapter 15:
Multi-threaded
Programming
Responsive code often depends on several tasks running simultaneously. In addition, modern
computer systems provide multiple hardware cores to efficiently run code in parallel. This
chapter discussed multi-threaded programming using the portable pthreads library. The main
concepts of the chapter are:
✓ Threads
✓ Starting and stopping threads
✓ Sharing data between threads
Computer Processes
When a program is run in a modern operating system, there are already multiple concurrent
programs running, handling services and user interface functions. Operating systems that
provide this functionality are called multi-processing. A computer program can also have
multiple processes, which are largely independent programs that happen to be started by one
application.
Page 114
consistency as the location of the program code is shifted into different parts of memory. So a
pointer with value 0x20000 in one process and a pointer with the same value in another
process are actually pointing to different parts of memory – the processes are protected from
messing with each other. A crashed program in one space cannot ruin other processes or the
operating system.
The stack is an area of memory that holds things like local variables and information saved while
calling a function. It is called the stack since variables that are added most recently to the stack
(from entering a local scope) are removed first as well (when the local scope is exited).
The heap is the memory used when a program explicitly calls new or delete to dynamically
allocate a variable. Notice that the program cannot predict when an object allocated in this
fashion will be removed, so a stack’s last-in-first-out behavior makes no sense for these objects.
Each process gets its own stack and heap to work with. A process can create a new child
process through a fork(). These are essentially independent and have to share information the
same way any two programs would – through shared files or network connections. Child
processes are also fairly resource-heavy to get started, so they are not appropriate for simple
tasks.
Threads
In contrast, a thread is an intermediate form of parallelism. They live in the same address space,
so global data visible to one is visible to the other. They also share the same heap, as that is a
location where these shared variables often live. They do have their own local stack, so variable
scoped inside the thread is not shared with the other thread. When a program starts, the main
execution path is essentially a single thread.
Modern computers can utilize threads in multiple ways. The computer might have several CPUs,
allowing for independent execution of each thread. A CPU might also have several cores, each of
which can execute a thread. So each thread can ask for instructions from memory and execute
them independently. Another resource for parallel programming is modern GPUs, each of which
might contain hundreds of simplified cores. These cores handle very lightweight threads very
efficiently and can speed complex computations by 100x if the problem maps well to the GPU
processors.
Thread Scheduling
Normally, all the processes and threads in a system share access to the CPU and other system
resources. The mechanism by which these resources are shared varies, but a basic approach is
called round robin scheduling. In this scheme, each threads gets a small share of time on a
processor, like 20 ms, then the next thread gets a turn, and so on. Note that there is no
guarantee than a process will even resume execution on the same core as it started on. There is
Page 115
a small overhead involved in changing threads – memory must be loaded into the processor
cache, the processor pipeline flushed, and so on. However, this approach maintains the illusion
of simultaneous interactivity for each thread.
Thread API
Threading is not a current part of the C++ standard, although the next revision has a planned
std::threads. Because of this, there are competing models for creating and controlling threads.
Windows has its own version, and some large libraries like Boost also have a thread model. One
of the most portable versions is POSIX threads, or pthreads, and that is the model we will use.
Pthreads
To use pthreads, you need to install the .dll, .lib, and header files in your Visual Studio project. It
is then fairly simple to create a new thread, as in the following example:
#include "pthread.h"
#include <windows.h>
#include <iostream>
int main()
{
pthread_t threadStruct;
int id = 1;
int result = pthread_create(&threadStruct, NULL,
PrintHello, (void *)id);
cout << "made thread: " << id << endl;
getchar();
return 0;
}
Looking the main function, there is a call to pthread_create. This function prototype is
Page 116
void *arg);
where the thread parameter is set by the thread creation process and is a C-style struct
containing some thread information. The attr parameter is normally just 0 for default
parameters, but can be used to control some local stack and scope parameters. The third
parameter is a function pointer, which is the main function the thread will execute. This function
is designed to take a single parameter of type void * and return an error value of type void
*. Essentially this means the argument can be of any type as long as it is cast in the
start_routine function to the correct type. The last parameter, arg, is the actual value being
sent to the start routine. Again, this can be a structure or class if complex data is needed.
Once execution returns from the create function, there will be two simultaneous paths of
execution. The code will continue in the main() function as normal. However, there will also be
concurrent action in the function that was sent to the thread create function. The new thread will
continue until it is explicitly deleted, or until there is a return from the passed-in function.
In this case, the thread function will typically take the form of an infinite loop. It wants to repeat
its action continuously while the main thread performs another action repeatedly. It is also
somewhat common to explicitly create two new threads in these situations, one for the display
and one for the simulation, and have the main thread just sit or perform some other minor task.
Another common model for threading is to have threads handle some short-term computation
and then exit. For example, an image processing program could split an image into four parts,
send each part to a separate thread for analysis, then return, hopefully having done the work in
the time. This can be generalized into what is called a “thread pool” model, where a master
1/4th
thread finds workable chunks of problems and sends them to a free thread in a thread pool.
Another use of threads is to pipeline an analysis process. Using another hypothetical image
processing problem, one thread could acquire an image and pass it to another thread which
converts the color space, which passes it to another thread which looks for edges. It still takes
the same amount of time for an image to be acquired and analyzed, but multiple images can
simultaneously be in the pipeline, so the number of frames per second can be increased.
Page 117
int main()
{
// Setup clock stuff
clock_t start = 0, finish = 0;
double duration;
// Thread structures needed by create
pthread_t threadStruct1;
pthread_t threadStruct2;
start = clock();
int id = 1; // Fill the first half of the table
int result = pthread_create(&threadStruct1, NULL,
threadFunction, (void *)id);
int id2 = tablesize/2;// Fill the last half of the table
int result2 = pthread_create(&threadStruct2, NULL,
threadFunction, (void *)id2);
// Wait for the threads to finish
pthread_join( threadStruct1, 0 );
pthread_join( threadStruct2, 0 );
finish = clock();
duration = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "Threaded time: " << duration << endl;
Page 118
getchar();
return 0;
}
There is a new pthreads call in the main function. The call, pthread_join, blocks execution in the
calling thread until the specified thread has exited, and “joined” the calling thread. This is needed
in the example, otherwise the computation threads would be created and the main function
would continue executing while work in the threads was still being done, so the timing functions
in the main thread would be inaccurate.
int main()
{
pthread_t threadStruct1;
pthread_t threadStruct2;
int id = 1;
int result = pthread_create(&threadStruct1, NULL,
threadFunction,(void *)id);
int id2 = 2;
int result2 = pthread_create(&threadStruct2, NULL,
threadFunction,(void *)id2);
pthread_join( threadStruct1, 0 );
pthread_join( threadStruct2, 0 );
Page 119
We have seen the pthread_create call before. The pthread join is also there to ensure that the
counter value is not output to the console window before the threads finish their work.
Each thread adds one a million times to a counter. Thus, after each of the two threads finishes
executing, a value of 2 million should be output to the console.
Instead, a fairly random value between 1 and 2 million is displayed when the code is run. This is
because each thread is attempting to read and write to the counter at the same time. So if the
first thread has read a value and is in the process of adding a 1 to it, the second thread may also
be reading the counter and will get the same value as thread one. Each thread adds one to the
same value, and each writes the value back to be stored in the counter. At other times, the first
thread manages to write the value back before the second thread grabs it, so each thread
increments the counter by 1.
This is called a race condition, which is when multiple threads each try to access a shared
variable at the same time. Sometimes, this causes skipped modifications to that variable. At
other times, more disastrous effects can happen, such as reading two bytes from one state of
the variable and two bytes from another, forming a completely inconsistent object.
Mutexs
There are a variety of mechanisms for protected access to shared data; the primary one used in
pthreads is a mutex. Mutex is short for mutual exclusion, and use of mutexs prevents multiple
threads from using a shared resource simultaneously. A mutex is a software lock that is placed
before entering a critical section of code. Other threads that attempt to lock the mutex
themselves are blocked until the original locker of the mutex releases it. Acquiring and locking a
mutex is an atomic operation, which means that it is an operation that can complete without any
other thread being able to change the state during the operation.
A typical sequence for using a mutex is to declare it globally, initialize it in the main thread, then
place lock and unlock calls around sections of critical code that use shared variables in the
thread. Note that the mutex itself is shared, there is one instance of the mutex that different
threads attempt to lock.
Going back to the example code with the race condition, the following elements might be added
to make it thread safe.
Page 120
//In main
// Must init the mutex before creating the threads
pthread_mutex_init(&mutex, NULL);
The main function initializes the mutex before the call the pthread_create. Note that the mutex is
only initialized once, not per thread, and the default state of the mutex is unlocked after
initialization.
The thread function is very simple – it just adds to the counter increment. Normally, a thread
would have much more non-critical code than critical code, but in this case the threads are very
rapidly locking and unlocking the mutex. If one thread manages to lock the mutex, the other
thread blocks while trying to lock it and waits until the thread is unlocked. Only one thread at a
time can be using the shared counter variable, so one cannot be reading the variable while the
other is in the process of updating it, and all the increments are now independent of each other
and all are counted. There is, of course, so cost in the locking of a mutex, and an even larger cost
from a thread having to block while waiting for the other thread.
4. It may be tempting to put a mutex around the entire body of a thread function in order to
guarantee safe data access. Obviously, this prevents any code from running in parallel,
so a programmer should keep the critical sections as small as possible.
5. A related mistake is to put a mutex around some code that performs some complex,
possibly looping, calculation, which forces all the other threads sharing that mutex to
block for long periods of time. Instead, try to perform the calculation on a local version of
the data, then copy the data with mutex protection into a shared structure when the
computation is completed.
6. Mutexs do not magically protect a section of code. They require cooperation from other
threads and the programmer to place them everyone a shared variable is used. So even
if 2 threads properly use a mutex, a 3rd thread that ignores the issue could still cause data
corruption. The mutex only provides an atomic lock which blocks other threads – it does
not somehow make variables const to other threads or something like that.
7. Different mutexs should be used to protect different variables if the variable access
occurs in different places in the thread function. We do not want other threads blocking
for shared variable A when variable B is really being accessed.
8. Having multiple resources under mutex protection can lead to deadlock, where each
thread may have some shared resource, but neither thread will relinquish a resource
until it can obtain the other. As an example, imagine two people (threads), each wanting
to draw a picture. The two resources are a pencil and a piece of paper. Each person is
successful in obtaining one resource, but stalls, waiting for the second to become
available. Neither is willing to give up the resource they have, so a deadlock occurs.
Page 121
Debugging Threads
The execution of threads is non-deterministic – the time intervals a thread gets on a processor
depends on other services being run. For a single thread, that may affect overall timing, but
instructions must still deterministically go in a certain order. For more than one thread, that is no
longer true, so dangerous behavior may be hidden until unlikely events occur. This makes
debugging difficult.
Also, traditional debugging approaches, such as slowly stepping through the code, no longer
make as much sense. Debugging of multi-threaded systems has received more attention as
these systems become more common-place, and there are recent additions to Visual Studio to
aid in this process.
Perhaps the most prominent tool is the Threads window. The current active thread is shown
with a yellow arrow. This can alert the programmer to a switch from one thread to another
when stepping with the debugger. It is also possible to freeze out other threads when
concentrating on the behavior of one thread. This doesn’t aid debugging subtle interactions of
threads, but it does allow for easier verification of one thread’s code.
A much more complete view of debugging for threads in Visual Studio can be found at
http://msdn.microsoft.com/en-us/magazine/ee410778.aspx.
Page 122
Chapter 16:
Include Files and Multiple
Inheritance
This is not really a chapter but more two unrelated topics that I should merge back into the
older chapters at some point. The topics are:
Complex #includes
Any project that gets to a certain size can have a complex web of dependencies in the code. This
can cause problems, or even errors, so we need to discuss the tools available to alleviate these
issues. First, it is important to see what might cause these problems. Look at the following
project with several includes:
File: point.h
class Pt
{
public:
double x,y;
};
File: vector.h
#include “point.h”
class Vector
{
public:
Pt base, head;
};
Page 123
File: main.cpp
#include "point.h"
#include "vector.h"
int main()
{
Pt p;
Vector v;
}
There are 2 classes defined in header files and a main file that uses and includes each. Compiling
this causes the following error.
1> main.cpp
1> error C2011: 'Pt' : 'class' type redefinition
1> see declaration of 'Pt'
The main thing to notice is that the error is in main, rather than one of the .h files, and the
compiler is complaining that the Pt class is being redefined. Obviously we are not trying to do
that, so what is the real cause?
Recall that #include files are essentially pasted into the main file during compilation. The main
file includes point.h, which defines the Pt class. Then the main file includes vector.h, which also
includes point.h. Each of those files gets added to the main file code, so the Pt class is included
yet again and when the compiler reaches that location, the Pt class is redefined.
A more careful programmer could realize this and remove point.h from the main file, which then
uses vector.h to do the proper include. This has several problems:
9. This requires a lot more knowledge about the structure of the code, so that instead of
using an object and including it, there has to be some hierarchy of includes where the
programmer must only include the one that contains the others. Reorganizing the files
based on some new class would be very difficult.
10. This may not be possible. Imagine adding a new class, Circle, which uses the Pt class to
define a center. Circle includes point.h and so using both the vector class and the circle
class in the main file would cause point.h to be included twice and compiled twice.
To solve this issue there are preprocessor directives which force the compiler to only read it and
compile it once. First, a non-portable solution for Visual Studio is to add
#pragma once
Page 124
A more portable solution, and probably the preferred method, is to use #define to define a
constant, then use a conditional preprocessor directive to only allow compilation once. An
example of this is
#ifndef __SERIAL_H
#define __SERIAL_H
Code…
#endif // __SERIAL_H
The second line is a #define directive, which is a very general command. In this context, the
directive tells the compiler that a value __SERIAL_H is defined. The existence of this definition
can then be checked when including the file. The basic logic is if __SERIAL_H is not defined, the
compiler defines it and also uses the code. If it has been previously defined, the compiler skips
to the #endif directive, essentially doing nothing.
In C programs, using a macro was useful for moving numerical values repeated throughout
some code into a consistent place for later changes. For example
double vals[100];
for (int i = 0; i < 100; i++)
cout << vals[i];
loops over a C-stye array. A programmer might decide to change the 100 in the array definition,
but forget the 100 value in the loop. Instead, a #define macro can be used to avoid
synchronization problems, like
Now, any change to VAL_SIZE gets propagated to the locations where it is used.
This may seem like innocuous and even desired behavior, but it can be very subtle behavior
since it is literal text substitution. Making a const datatype, like a const int is preferred over
defined macros.
Page 125
We have discussed deriving a class from a base class in a process called inheritance. Recall the
augmented stats class derived from the STL vector
First, we never really defined what the “: public baseClass” notation meant. The public keyword
controls how inherited members of the base class are protected in the derived class. Using
“public” means that any member inherited can have up to a public level of access, but if they are
defined to be more restrictive, they will keep the more restrictive level. Using “private” instead
would force members that are public in the base class to be private when accessed through the
derived class. This is not a very typical usage, and it is usually easier to just have the derived
class include a data member of the type you were going to privately inherit from. So 99.9% of
the time you will want a public inheritance.
Access Control
There is a new access control specification that can be used when one class inherits from
another. Private members are still private to any code outside a class. Protected members are
public to derived classes and their friends, but private to code outside those classes. If classes
are going to be derived from a class, this is often the proper choice for access control. On the
other hand, you may wish to preserve a strict interface to the base class and only manipulate it
through its public methods.
Multiple Inheritance
Some of the advantages of inheritance over composition (adding classes as data members to a
class) are cleaner access to methods and data and avoiding replication of functionality. What if
Page 126
you were designing a class that contained multiple classes as data members? The single
inheritance model does not cover incorporating multiple classes into a derived class.
Luckily, it is also possible to have multiple inheritance, simply by listing all the base classes with
a comma in between them, as in
Class X now has the members and functionality of classes A, B, and C. A derived class can only
directly inherit from a base class once, so class X : public A, public A is illegal (as well as
strange). However, it is possible to indirectly inherit from a class more than once. If class A
inherits from class Z, and class B inherits from class Z, then that is allowable. There is now a
possible conflict when calling the indirectly inherited members of Z from the derived class A.
The ambiguity can be resolved by referring to the Z class through the other classes’ namespace,
like A::Z.
In general, multiple inheritance can create too much openness between classes. The derived
classes have internal access to a large portion of the code and ideas of protecting code from
changes elsewhere start to break down. I find it rarely useful to take advantage of multiple
inheritance and you should use it with caution.
Page 127
Chapter 17:
A Vector-Matrix Library
and Catching Exceptions
Most scientific and engineering computations rely on fast and reliable numeric. While Fortran
was the traditional language of numerical computations, modern C++ quality libraries as well.
This chapter discusses using one example library as well as some topics to improve the quality
and safety of your code.
The Boost libraries are a large collection of code designed to make developing in C++ faster and
safer. They cover a large range of topics, from language extensions like “foreach”, which applies
code to each element of a list, to geometry libraries. Many Boost libraries are planned for
inclusion in the next major revision of C++, so learning Boost can provide a head-start on the
language evolution.
The main Boost library we will be examining is the numeric package, and in particular, the uBLAS
portion of the library, which mimics and provides connections to some standard libraries for
solving systems of equations. Such vector and matrix libraries can be an important part of a
programmer’s toolkit, especially when dealing with scientific and engineering simulations.
Installing Boost
Boost is very easy to install, as most of the libraries are fully templated code. The main
implication of this is that the libraries are merely header files which are compiled as needed, and
thus are largely compatible with a large number of environments and compilers. Boost is
available at http://www.boost.org/ and we will be looking at 1.47, the most current release with
a Windows installer. Boost should be added to Program Files/boost to match the discussed
setup, although any place is really fine.
Boost is largely composed of .hpp files. There is ongoing debate as what an .hpp file should be.
Some, treat it as an alternate extension for a header file, similar to how a .C or .cpp extension is
Page 128
used for C++ code. Others seem to treat it as a header file for template classes that may contain
substantial code, as a blend of .h and .cpp. Others argue that a template class should have the
definition of the class in a .h and the template functionality in a .hpp file. In any case, boost is
largely template code and largely uses .hpp rather than just .h files.
Boost has a number of sub-directories, so the compiler is usually setup so that it knows about
the main boost directory, and all others are found through a partial path to the desired portion of
Boost. For example, the Boost vector class (of the mathematical variety) can be included by first
telling Visual Studio to look for include files at C:\Program Files\boost\boost_1_47 and then in a
program using
#include <boost/numeric/ublas/vector.hpp>
The template vector class is part of the uBLAS project, which is classified as a numeric project,
and is part of the larger boost library.
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <iostream>
matrix<double> A(2,2);
A(0,0) = 0; A(0,1) = 1;
A(1,0) = 2; A(1,1) = 3;
The program creates an object x, which is a vector of type double and a matrix A of type double,
assigns values to each, that multiplies the matrix A and vector x to compute y. Note that
Page 129
indexing into the vector and matrix is done with () rather than the array style []. Each of these
data types is worth discussing in more detail.
Vectors
The Boost vector template has a number of options. It can be row or column major and use both
stl::vector style expandable arrays and C-style fixed arrays (for slightly greater speed). In
scientific computing, the type of the vector is almost always a double for adequate precision in
computation. Typical vector operations are supported, such as adding two vectors or computing
their dot product. Some operations are demonstrated in the following code.
vector<double> x(3);
std::cout << "Not assigned: " << x << std::endl;
// Fill with zeros
x = zero_vector<float>(3);
std::cout << "Zeros: " << x << std::endl;
// Fill with a value (1.0)
x = scalar_vector<float>(3, 1.0);
std::cout << "Ones: " << x << std::endl;
// Assign each index
vector<double> y(3);
y(0) = 1.0; y(1) = 2.0; y(2) = 3.0;
std::cout << "y: " << y << std::endl;
// Basic Operations
vector<double> result = x + y; // Vector Addition
std::cout << "x + y: " << result << std::endl;
result = result * 2.0; // Scale
std::cout << "result * 2.0: " << result << std::endl;
double dot = inner_prod( x, y ); // Dot product
std::cout << "x dot y = " << dot << std::endl;
The Boost vector has a typical mix of overloaded operators and functions to achieve a rich set of
mathematical operations. More unusual are some of the assignment functions for filling the
vector with ones or zeros.
A system of linear equations is a collection of linear equations with each equation using the
same variables as the other. Each variable is weighted by a scalar value called the coefficient
and each equation is equal to some value called the constant term. In the following equation, the
a terms are the coefficients, the x terms are the variables, and b is the constant.
Page 130
A non-linear equation would have variables raised to a power other than one, or multiplied by
another variable.
Given the above notation for a single linear equation, a system of such equations would look like
the following, where the subscript indicates the row and column of the coefficient.
Importantly for use in the Boost uBLAS library, we can pull apart the coefficients and the
unknown variables and treat the entire system as a matrix multiplication.
Ax = b
where A is the matrix of a coefficients, x is a the vector of x variables and b is a vector of the b
constants. The variables x can be solved for by multiplying each side of the equation by the
inverse of A,
−1
Ax = b A−1Ax = A−1b x = A b
since the inverse of A times A is the identity matrix. Normally, this full process is not carried out
and x is solved in the process of finding the inverse.
Example
Let us look at the following example. Imagine having three chemical processes (x1,x2,x3) that
each generate three byproducts, (b1,b2,b3). Process x1 generates the byproducts at the following
rates (0.2, 0.2, 0.3), x2 generates them at rates (0.1, 0.1, 0.1) and x3 generates them at rates (0.5,
0.2, 0.1). If the desired amount of byproduct is (4,3,3), how long should each process run?
A = [0.2 0.1 0.5 0.2 0.1 0.2 0.3 0.1 0.1 ], x = [x1 x2 x3 ], b = [4 3 3 ]
Small matrices like these are solvable by hand, through techniques like back substitution. Larger
matrices really depend on computers for reasonable solution, and very large matrices require
specialized data structures and techniques.
Boost uBLAS is primarily designed as a data structure for vectors and matrices, and most users
of it connect to other packages through uBLAS bindings to do the actual hard work of solving
systems of equations. However, for simple cases, there is enough internal functionality to a
Page 131
reasonable job. The following code is not part of uBLAS, but is a suggested approach for
inverting smaller matrices.
#ifndef INVERT_MATRIX_HPP
#define INVERT_MATRIX_HPP
#include <boost/numeric/ublas/vector.hpp>
#include <
boost/numeric/ublas/vector_proxy.hpp>
#include < boost/numeric/ublas/matrix.hpp>
#include < boost/numeric/ublas/triangular.hpp>
#include < boost/numeric/ublas/lu.hpp>
#include < boost/numeric/ublas/io.hpp>
// perform LU-factorization
int res = lu_factorize(A,pm);
if( res != 0 ) r
eturn false;
rue;
return t
}
#endif /
/INVERT_MATRIX_HPP
The following code initializes the A matrix and b vector, then solves for x by inverting A and
computing inverse(A)b.
Page 132
vector<double> x(3);
matrix<double> A(3,3);
A(0,0) = 0.2; A(0,1) = 0.1; A(0,2) = 0.5;
A(1,0) = 0.2; A(1,1) = 0.1; A(1,2) = 0.2;
A(2,0) = 0.3; A(2,1) = 0.1; A(2,2) = 0.1;
vector<double> b(3);
b(0) = 4.0; b(1) = 3.0; b(2) = 3.0;
std::cout << b << std::endl;
matrix<double> invA(3,3);
InvertMatrix(A, invA );
This example is not very resistant to special cases and other approaches are both faster and
more robust, but that is a topic for a numerical methods class.
Page 133
Chapter 18:
Exceptions
Run-time errors are particularly bad for deployed code – they cause the program to crash or
generate nonsense answers. Exceptions and error handling can improve response to these
problems.
A run-time error is one which occurs during program execution (as opposed to compilation
errors, which are detected during the compilation process). Run-time errors can happen due to
unusual circumstances (such as unexpected input), or untested execution paths (from code that
was compiled, but never properly tested).
Detecting and responding to unusual cases is important, as doing so can alert the user that the
resulting computation is invalid. As an example issue, look at the following case.
This code crashes the program as there is not enough memory to allocate a billion dimension
vector. While this is a simple case to find, a more common problem would be a computation
that allocates blocks of memory as needed, and the error may not occur until some way through
the computation. If alternate strategies were available, they should gracefully be used instead of
continued allocations.
Return Codes
One mechanism for testing for errors is to use return codes, such as used in opencv and the
Serial library. Basically, calls to methods return a value that indicates the success of the method,
and the calling code can test and react to those values.
This is a reasonable approach for simple cases, such as whether or not a camera opened
properly. But other situations may generate a number of possible errors, each of which might
need to be tested and responded to separately, and this testing might have to be repeated in
several places. Even more commonly, the function generating the error might have to pass that
Page 134
error code up through several layers of calling functions in order to properly respond to the
error. Compare the simple case of
Note how the success status needs to be propagated up through the calling functions. In fact,
the serial port library we used did have a similar calling sequence during the Serial::myWrite
method – it called other methods to do the actual work.
While this does not seem too troubling, studies report that conditional statements are on the
order of 10x more likely to contain errors than other statements, so writing code that is full of if
statements, as needed to interpret return error codes, is likely to cause development delays and
decrease code robustness.
Page 135
As another example, think about writing overloaded operators, such as operator+. That should
return both the result of the computation and any error (such as number overflow or
divide-by-zero) that might occur. Using return codes, one would have to give up operator
overloading and instead use named methods like
where the error code gets sent through a pass-by-reference parameter to the method. An
overloaded operator has no place for an extra error parameter.
Try-Throw-Catch
There is an alternate mechanism in C++ to handle these kinds of run-time errors, called
exceptions. The code of interest is surrounded with a “try” block. Then either the code in the
block or some code called within the block calls a “throw” command when an error occurs. A
catch block handled the thrown exception and either reports the error or tries to work around
the error.
The big allocation above can be modified to use these structures as follows.
t ry {
vector<double> big(1000000000);
}
atch(...) {
c
std::cout << "Allocation error" << std::endl;
}
The catch(…) indicates that any exception can be handled. When possible, more specific
exceptions can be handled by catching a type of error. The code must #include <exception> to
catch these standard exceptions.
atch(std::bad_alloc&) {
c
std::cout << "Allocation error" << std::endl;
}
The use of “bad_alloc” is actually a class type. A parameter name could be used as well, but in
this case knowing the type of the exception was enough to determine the handler needed.
Your own code can throw exceptions, which can then be caught by a handler. Here is a
made-up example.
try
{
throw 20;
}
catch (int e)
Page 136
{
std::cout << "An exception occurred. Exception " << e
<< std::endl;
}
In this case, the code threw an integer exception, which was caught by an integer handler.
After the catch, the code execution resumes at the next statement after the catch, not below the
throw. So the rest of the code in the try block is ignored after a throw.
So far, these examples are not too different from what could be handled by some if statements.
The power of this approach is that the handler can be at a higher level than where the error
occurred, so you can put try around code to a library, and then handle errors that might occur
inside there.
Think about the difference in code using return errors, where the code to handle errors is
intermixed with the code that should be happening in the absence of errors. The “good”
execution path is difficult to discern from the “bad”. With try-throw-catch, the good path is
distinct from the “bad” path, which is handled in the catch statements.
Page 137
Chapter 18:
Version Control
Managing changes to a codebase – either by one person over time or a group working
concurrently – is an important task for which a number of tools are available. This chapter looks
at using Subversion (SVN) version control in the context of Visual Studio.
Many inexperienced programmers invent some form of version control at some point. Perhaps a
file is copied from main.cpp to main.cpp.old, or even to a main.cpp.workingNov18_11PM, in an
effort to maintain some history of changes to a system, usually before plunging into a mess of
edits fueled by pressing deadlines and a lack of sleep.
More formal version control systems maintain a database of changes to a file or group of files,
and allow comparison of versions, reverting to older version, and merging of changes into a
consistent file. This idea of version control is also called source control or revision control. A key
advantage of most formal systems is that they also provide tools to allow teams of people to
work on a single project. Most systems have some notion of a repository, where the full project
history lives, and of a local working copy of the project for each developer, where changes can
be developed and tested. Note that the local copy is typically a full version, so that actions like
compilation can be done quickly.
Control Models
Some of the first version control systems used a lock-edit model, where a developer claimed
the rights to change a particular file and applied edits to the file. The edits should be done in
advance before locking, but a more typical scenario was for a person to grab a file to prevent
others from messing with its structure and holding it for a length of time. Even when the
approved approach was used, there are times when the new edits had some unforeseen
consequence, and the file remained locked during some final testing and revision by the
programmer. These types of problems often led to other team-members subverting the controls
of the system in order to make progress on their own work, or at least to work around the guy
Page 138
that left for the weekend leaving some files locked. In some ways, this approach leaves a team
dependent on the behavior of the worst teammate.
An alternate, and more current, model is that of edit then merge. In this paradigm, each
programmer is free to edit files in the local working copy. Any conflicts are resolved during a
merge phase. One nice side effect of this approach is that it encourages people to submit their
changes as soon as possible, so that the later people have to work out any merge conflicts. This
approach should reduce time conflicts over files, and makes the most productive teammates get
the most benefit from the system.
Repository Models
Most systems rely upon a centralized server model for the repository. All team members must
have access to the machine where the repository is located. The Subversion (SVN) file control
system is an example of this, and clients can connect over a variety of mechanisms, such as
secure shell or http. More recently, decentralized systems, as exemplified by the ‘git’ system,
maintain local repositories and update each other through a patch process.
SVN
We will look at SVN as a model for version control. It seems to have better integration with
Visual Studio than git, which is also a very (more?) popular system. SVN provides a visual studio
extension which adds file and context menu choices related to version control.
I installed the SlikSVN subversion system, the TortoiseSVN Explorer window integration
front-end, and the AnkhSVN visual studio extension. A cleaner experience is to use a
command-line prompt to issue commands to the repository directly, but this collection of tools
is closer to the usual Windows experience.
SVN Workflow
A typical SVN workflow is to create the repository, then add files to the repository. Files can be
already existing files, files that had been just created, or even files that are being worked on for
some time before being added. After adding the file, you must commit the file to add the actual
contents of the file to the repository.
Once a file has been further edited, its changes need to be added back into the repository, which
is done by doing another commit on the file. The commit process usually just requires an OK and
some kind of comment on the changes is often enforced. The commits create a history which
can be viewed by a developer.
At other times, the repository file has been changed by someone else and the commit requires a
merge. This should be done by first running an SVN update, which attempts to reconcile the
working copy and repository version by changing your working copy. Often the merge can be
done automatically if the changes were in different parts of the file. SVN will show a diff (or
Page 139
difference) between the two files as well for some visual verification. At other times, the merge
process requires the developer to approve either one or the other version, in a process similar to
track changes in Microsoft Word. After a successful update, the local changes can be
propagated to the server with SVN commit.
A new local working copy can be made by doing a checkout of the repository. This can be a
new member joining the team, or just a developer wanting two places to try out ideas. Older
versions of a file can be found by using SVN revert, either by going back step-by-step or using a
version number found by looking at the commit history.
Then, start a new empty project in Visual Studio. In the Solution Explorer, right-click on the
solution name and choose the “Add Solution to Subversion” option, which asks for the location
of the repository. The main confusing thing here is that if the repository is just a folder on the
local drive, then the URL request is for the file location in browser form, something like
file:///C:/Users/David/Documents/SVNrepository/.
You can then create any files as needed. When they are created, they should be added to the
repository, by right-clicking on them in the solution explorer and looking at subversion options.
This merely alerts the repository that a files is ready, the actual file is not submitted until there is
a commit.
Try adding a few lines to define a main function, then do a new commit. Add a few more lines
for a counter variable and commit again. These show up in the history.
If the two working directories make a change in the same place, the first can commit the change
without a conflict as it does not yet know about the second change. The second person can use
the compare options to see that there is a conflict between the two files. By doing an update, the
file will show any conflicts as follows
int main()
{
<<<<<<< .mine
int counter = 3;
Page 140
=======
int counter = 2;
>>>>>>> .r11
int other;
counter++;
return 0;
}
The local version is .mine and the conflict with the repository version is shown with its version
number. Right-clicking on the file in the explorer and choosing resolve resolves the rules
according to the specified rule.
SVN also contains tools to examine the history of a file, to revert a messed-up file back to the
last committed version, and to split a repository into multiple branches. Any serious
development effort will use a version control system to coordinate efforts between a team of
developers.
Page 141
Chapter 19:
Python: Another View of
Object-Oriented
Programming
So far C++ has been our only view of an object-oriented language. All programming languages
are the result of numerous design choices that determines how the language is used and how
code is expressed in it. This chapter provides a quick introduction to Python, which expresses
object-oriented principles in a very different language from C++.
Python Characteristics
High-Level Languages
A high-level language has statements that perform high-level or abstract (relative to the CPU
machine-language) actions. Standard C++ is fairly high-level, but the addition of the Standard
Template Library and mechanisms for adding class data types means that many programmers
work at a high-level in the language. Python is also high level, with some built-in data types that
surpass basic C++ in terms of easy access to powerful data structures.
Page 142
In contrast, dynamically-typed variables can hold different kinds of values, and in the Python
case, variables do not need to be declared as having a particular type. The actual values are
typed, that is, an integer knows it is an integer and what operations can be used on it, but a
variable can hold an integer, and then be assigned a real value in a following statement without
difficulty. Note that all functions are then polymorphic, as there is no checking as to what type of
argument actually gets sent to the function.
An interpreted language such as Python is instead run within an interpreter. The interpreter
parses each statement in the program and performs actions depending on the programmed
statement. The program cannot be run without the interpreter. This process results in much
slower code than a compiled program, but it allows much closer examination of the program in
action, helping development, and it can often be much easier to make informal programs. For
this reason, Python is often used as a scripting language, where the intent is to automate some
simple task. Because it is a general-purpose language, Python is also used to prototype
algorithms, to quickly develop GUIs and test code, and to bind together other libraries into a
usable system.
Many basic statements in Python are similar to other languages, so it is easy to get started. Also,
by typing statements in the interpreter shell, Python gives immediate feedback, so it is very easy
to experiment with Python concepts in syntax.
>>> 1 + 1
2
You can see that Python has some concept of types by trying out division, where integers stay
integers
>>> 5/2
2
And reals stay real
>>> 2.5 / 2
Page 143
1.25
>>> val = 1
>>> val
1
>>> val = 1.2
>>> val
1.2
>>> val = ‘hello’
>>> val
‘hello’
In the example above, the variable val holds different values.
>>> list[2]
‘a’
Python supports more interesting indexing than C++, negative indices count back from the end
of the list, so an index of -1 is the last element in the list
>>> list[-1]
‘a’
Lists also support slices, which are subsets of a list
>>> list[0:2]
[1, 1.2]
Note that the slice is specified as an open-ended range, so the last value is not part of the slice.
A slice with no last value goes all the way to the end of the list.
Page 144
Conditionals
Python uses an if statement as a conditional. An example is
>>> x = -1
>>> if x < 0:
... x = 0
... else:
... x = 1
...
There are a few new aspects to this example. At the end of the if line is a “:”, which is used to
indicate that an indented statement block is expected. Note that there are no special statement
block characters, such as the { } used in C++. Instead blocks are only indicated by indenting, and
prompted by the use of the colon. This is a fairly controversial style choice, but it avoids errors in
C++ when the physical visual indenting is not matched by the curly braces. The else statement is
also terminated with the colon.
In the interpreter, an extra blank line is needed to indicate that the block is finished and that the
statement can be interpreted. When developing in a text editor, the blank line is not needed,
only proper indentation to show the end of a statement.
For Loops
Python also uses for loops for fixed length repetition, but the for loops iterate over lists rather
than number sequences. Using the list from the last section, all the elements of list could be
printed using
When a number sequence is needed, the range command creates a list of numbers in the given
range, which mimicks the C++ for loop with an integer index.
Using the interpreter shell is not a very convenient way to develop large programs. Instead,
Python can be developed from within an editor environment. A simple, but effective one is IDLE,
which opens up a Python shell along with a text editor that is connected to the shell.
Page 145
Functions can be created and edited in the IDLE text window, then sent to the interpreter using
“F5”. Once sent and defined, they can be called from the interpreter window or from other
functions. The interpreter restarts to a clean environment when the code is sent over.
Defining Functions
Functions can be quickly defined in Python. The def keyword indicates that start of a function,
which has a name and a parameter list. The return value is not needed as it can be of any type.
An example number doubler function is
>>> doubler(2.5)
5
>>> doubler(‘cat’)
‘catcat’
Importing Modules
First, we need to gain access to the standard random function. Similar to C++, many useful parts
of the language need to be brought into the current environment to be used. This is not exactly
the same as libraries and header files in C++, as there is no mechanism to compile libraries into
machine ready form. However, many Python libraries are stored in a partially-interpreted form
that speeds parsing and execution.
The command to bring in a Python module is “import”. Documentation has told us that the
random function is in a namespace called random, so the command (typically at the top of the
script), is
import random
Page 146
The functions in random can be accessed through the random namespace. A call to the standard
random function would look like
num = random.random()
We can write our own random function that allows setting the range of the random number like
import random
Having to always set the min and max can be tedious, so we will use default values in the
parameter list
Classes
In Python, classes are still evolving, and there is a split between what is called the old and
new-style classes. They are not that different, but old classes are essentially just a namespace,
with methods being part of the namespace and class objects being instances (copies) of the
namespace. New-style classes more classes more formally a type in Python. The main
difference is that new-style classes inherit from a Python base class called object (or from a
class derived from object). We will follow that convention, but won’t dwell much on the
subtleties between old and new-style Python classes.
Defining a Class
Classes are defined with the class keyword, followed by the class name, followed by
parentheses around any base class, and then an indented block with methods. An example is
class TestClass(object):
"""Create a simple class to say hello"""
def hello(self):
print "hello"
Note the colon used after the class name to indicate the start of a block.
The next line with the triple quotes is what is called a doc string. Doc strings are alternatives to
the typical block comments found in C++ before function and method definitions and they
Page 147
should be used to describe what the class or method does. There are many standards about
what should be in doc strings. Unlike comments, these doc strings are retained through
execution, so that they can be used from the shell to assist in usage, not just for help while
programming. For example, in the Python shell, a doc string can be retrieved like
>>> TestClass.__doc__
'Create a simple class to say hello'
>>>
The last bit of code uses the def keyword to define a method of the class. All class methods have
a special initial parameter called self, which is the equivalent to the “this” keyword in C++, except
that it is explicitly declared and is not a pointer. It is also only called “self” from convention; it
could just as easily have been called “me”, but everyone will be confused if you try that.
x = TestClass()
print x.hello()
TestClass myVariable
since there is no need to declare variables of a certain type – assignment for the first time
creates the variable.
One oddity of the Python class approach is that a method can be called with zero elements, but
the interpreter automatically inserts the calling object as the first argument, where it becomes
“self” in the method. This is equivalent to calling
TestClass.hello(x)
but don’t do that. Perhaps it exposes for us a bit of the underlying mechanisms Python uses to
make class objects. Methods can have additional parameters, but the first one is always self and
it is not explicitly passed as an argument.
class MathVec(object):
Page 148
This class also shows Python data members. Data members are created by initializing them in
the class constructor (or other methods) as part of self. They can be retrieved or changed
outside a class method as follows
>>> v = MathVec(1,2)
>>> v.x
1
>>> v.y
2
There is not a sense of private in Python classes. A convention is that if the data member (or
method) starts with a double underscore, like self.__value, then that member should not be used
outside the class code.
If we want a more flexible class that handles different dimension vectors, we need a constructor
that can handle either two or three values passed in. One option is to use the *args parameters,
which is a tuple of all the arguments sent to the constructor. So the following code can handle 2
or 3 dimensional vectors.
def printVec(self):
print self.x,self.y,
if self.z == None:
print
else:
print self.z
v1 = MathVec(1,2)
v2 = MathVec(1,2,3)
v1.printVec()
v2.printVec()
There are also a few new things in this code. The z value gets a value of None as default, so that
the data member exists but has no value. The printVec method looks for a None z value before
Page 149
deciding whether or not to print that dimension. Also, in the printVec method, the first print has
a trailing comma, which suppresses the newline, which allows the z to be added to the line if
needed.
Inheritance
Python supports a simple inheritance scheme. Base classes are listed inside a set of parenthesis
immediately after the derived class name and before the colon. Base class functions and data
members are available to the derived class, although they can also be overridden by the base
class. An example of a base class and derived class is the following.
class Animal(object):
def __init__(self):
self.age = 0
def printAge(self):
print "Age:",self.age
In this example, Animal is the base class, and it has a data member self.age. Cat inherits from
Animal, and the constructor for Cat immediately calls the base class constructor before doing its
own initialization. The ageCat method in Cat can use the self.age data member from Animal.
Once an object of Cat class is instantiated, then the object can also directly call the Animal
methods, as in the following.
pet = Cat()
pet.printAge()
pet.speak()
pet.ageCat()
pet.printAge()
Python also supports multiple inheritance by putting comma separated classes in the () after the
class name.
In general, the class mechanism for Python is more explicit that C++. The self parameter needs to
be used when referencing data members inside class method code. The calls to base class
constructors are explicitly made, instead of hiding in the C++ initialization list. In Python, the use
Page 150
Python has powerful mechanisms for concisely building up collections of data. In this section,
we will look at two of these features: list comprehensions and generators.
List Comprehensions
Like mathematics, programming languages attempt to describe solutions to problems in a
non-ambiguous way, so it should not be a surprise when features from one creep into the other.
For example, functions are a formalism originally described in mathematics and borrowed for
most programming languages.
Another mathematical notation is set building. Sets are related to lists (although there are also
data structures that are more formally related to mathematical sets than lists). An example
mathematical set can be construction mathematically like
which says “take the elements of the set with values 1,2,3, double each one, and put the result in
a new set”. The constructed set would contain the values 1,4,9.
In Python, we can use list comprehension syntax. The equivalent statement would be
where x**2 means x raised to the second power. We have already seen what part of this means
– in defining loops the syntax
for x in [1,2,3]:
was used to assign x to each element of the list [1,2,3] and execute the statement in the body of
the loop. So the list comprehension shown above is equivalent to the Python code
newList = []
for x in [1,2,3]:
newList.append(x**2)
You can decide if you like the more concise list comprehension syntax or the more C++-like loop
form.
Page 151
However, there are advantages to the list comprehension. Conditions to the list-building can be
added to the syntax, which would require yet another line of code in loop form. For example,
we can filter out elements of the input list that are negative, as in
While this looks a bit mysterious, with practice, it does read in a commonsense fashion.
Python Generators
Before discussing generators, let us look at another example of using a list comprehension. I
could write code that would build up a list of prime numbers like
import math
import time
def checkNPrimes(N):
primes = []
for testNum in range(1,N):
if isPrime(testNum):
primes.append( testNum )
return primes
primes = checkNPrimes(100)
This code defines a function isPrime(num) which tests if num is a prime. There is another
function that checks the first N numbers and appends them to a list to return if a number is
prime.
which could be encapsulated in a function or just used in place of the function call. If I wanted to
print out the list of primes, I could do so with a loop over the result
Page 152
which works well, but when we start asking for a higher range of numbers to check, the full
effort of computing the list is done before we can start to see the results.
Generators are a mechanism to interrupt computations and return results to a calling function,
and then allow the computation to restart. A simple way to make a generator is to turn a list
comprehension into a generator by replacing [] with (), as in
When I ran the print loop on the original list expression with a range from 1-5000, the Python
shell paused several seconds before starting to print the results. When I ran the generator
version as above, and used that in the loop, the initial results were printed out immediately, with
increasing pauses as larger numbers were tested.
Sometimes, it is more natural to turn a function into a generator directly. The main syntax
needed is the “yield” statement, which is used like a return, except the next time the function is
called, it returns execution to after the yield.
def genNPrimes(N):
for testNum in range(1,N):
if isPrime(testNum):
yield testNum
Note that a list is no longer being built up. A loop calling the generator, like
would start to print results immediately and terminate after checking 10000 numbers (when the
genNPrimes function exited its loop). If a list were desired, like the original function produced,
wrapping the generator function is a list() call would turn the stream of individual numbers into a
list.
Generators are useful when a collection of items are computed, but operations on the list only
depend on the current element. If each element is resource heavy, such as consuming a lot of
memory, a large list of results would be difficult to compute, whereas a generator could
compute each element one at a time and make it available for processing before computing the
next.
Page 153
Lambda Functions
The list expressions use small pieces of code to compute something on the elements of the list.
This idea is generalized in the concept of lambda functions, which are small unnamed functions
designed to just persist in a very local scope (or frame). The main difference between lambda
functions and regular Python functions is that the lambda function is an expression which gets
evaluated while a function formally uses a return statement to show what value exits the
function.
Let us write a function to square a number and compare that to a similar lambda function. The
Python function would look like
def square(x):
return x**2
>>> square
<function square at 0x028A6230>
>>> square(3)
9
And passing a parameter to it yields a result. The lambda expression of this is
lambda x: x**2
<function <lambda> at 0x028A6270>
But this is an anonymous function with no name. I can pass in an argument directly, as in
Notice that the lambda is never given a name. I can also assign the lambda function to a name,
and call that name, like
The real value of these anonymous functions is in places where a function is needed, but more
conciseness is desired. For example, Python has a map function which applies a function to each
element of a list. So, we could write
def square(x):
return x**2
Page 154
In this case, square is a common enough function that is likely to exist, but if it were some other
specific function, notice how several lines are needed to define and then use the function. A
lambda version of this would be
Page 155