C++ for C/Java Programmers Notes





The following is the table of contents.

1.Objectives

2.Road Map

3.Why Object Oriented Programming?

    1.The crisis in software engineering

    2.How does Object Oriented Programming (OOP) help?

    3.OOP and Client/Server computing

    4.OOP and the Job Market

4.Why Abstract Data Types (ADTs)?

    1.Stacks

    2.The Stack ADT

    3.Formal definition of ADT

5.Why C++?

6.An annotated C++ bibliography

7.Incompatibilities between ANSI C and C++

    1.Identifiers

    2.Function declarations

    3.Global data

    4.Structs

8. Types

9.Assignment statements

10.Initializations

11.Miscellaneous

12.How to share header files between C++ and C

13.C++ comments

14.I/O, I/O and Off to Work We Go...

        1.Standard places to read and write

        2.A first look at output

        3.A first look at input

        4.Formatted input and output

        5.Opening and closing files (streams)

        6.I/O condition states

        7.More on output

        8.More on input

        9.seek and tell

15.Pass by reference

16.Default arguments

17.Intro to Classes: Abstract Data Types (ADTs)

        1.The Stack ADT

        2.Declaring the public interface

        3.An array implementation of int_stack

        4.The main thing about int_stack

        5.Declaring the public interface for a linked list int_stack

        6.Rewrite main

        7.A linked list implementation of stack

18.Inline functions

19.Overloaded functions

        1.'+' is an example of an overloaded function

        2.Overloading stack

        3.Interactions between default arguments and overloaded functions

20.constructors and destructors

21.new and delete vs. malloc() and free()

        1.new vs. malloc()

        2.delete vs. free()

        3.delete[]

22.Overloading operators

        1.Operators which can be overloaded

        2.Operators which cannot be overloaded

        3.An example - complex numbers

23.Friends

24.Template functions and classes

25.Support of object oriented programming: inheritance and virtual functions

        1.The virtual base class vstack

        2.A derivation of stacks using arrays

        3.A derivation of stacks using linked lists

        4.The main program

        5.The function that uses vstack

26.public vs. protected vs. private access

27.multiple inheritance

28.Designing with objects

        1.Rules of thumb

        2.Design steps

        3.Discovering classes

29.More on const

        1.Unlike C, const really is constant in C++

        2.const pointer vs pointer to const vs const pointer to const

        3.const reference variables

        4.const reference variables and arguments to functions

        5.const references to class data

        6.returning const

        7.Linkage of const variables

30.More on reference

        1.References to pointers

        2.Functions that return lvalues

31.Copy constructors

32.The base/member initialization list

33.The this pointer

34.More on operator overloading

35.User-defined conversion functions

36.Argument matching

37.Catch, throw, and try

38.Potpourri

        1.A word about default arguments

        2.Debugging constructors and destructors

        3.``is a'' vs. ``has a'' vs. ``kind a''

        4.resource acquisition is initialization

        5.Allocating small objects
 
 


Objectives

At the end of this course on C++ you will:

    Have seen all the features in C++ that are not in ANSI C

    Know about the (minor) incompatibilities between C++ and C

    Have been introduced to abstract data types (ADTs) and have seen how C++ facilitates their use

    Have been introduced to object oriented programming and have seen how C++ supports this paradigm

However, you will NOT:

    Be comfortable with C++

    Be able to write even 10 line programs without a manual

    Be an expert on ADTs (that would take 2-3 academic courses)

    Be an expert in object oriented programming (ditto, or lots of experience)

    Be an expert in object design (ditto)

As an analogy, you'll have seen a training film on how to ride a bicycle, but won't yet have tried one.

There are only three known ways to get proficient at C++:

    Practice

    Practice

    Practice

Road Map

Why Object Oriented Programming?

    The crisis in software engineering

    A rule of thumb in software engineering is:

    What this means is that it takes 4 programmers to write a program that is twice as large as a program written by one

    programmer, in the same amount of time.

    The crisis occurs because the size of large programs doubles every 2-5 years.

    For example, rumor has it that programs like MS Word and WordPerfect currently require 20-30 people to produce a new

    version a year, and that these programs are 250-300,000 lines of code. Assuming new versions will double in size every 4

    years, gives the following table:

That is, by the year 2001, either nobody will be able to afford to write the next version, or it will take 4 times as long to

produce the next version with the same staff.

How does Object Oriented Programming (OOP) help?

The above rule assumed each program was one large piece of code. Breaking a program up into separate functions doesn't

help because of interactions between functions. The only types of things that can work is if a program can be constructed

from smaller pieces, each of which is completely independent.

This is exactly what object oriented programming is about. Programs are constructed of objects. Each object contains data

and code and is completely self-contained. There are ways of manipulating these objects so that all the ``real world''

manipulations can be done like:

objects can be used as-is

objects can be used mostly as-is but with some extra code for the problem at hand (inheritance and derived objects)

objects can be selected based on criteria known only at run-time (virtual objects)

objects can be generalized to work on other objects whose type is not known until link-time. Consider sorting an

array. You should be able to write an object that sorts arrays no matter what type of object is in each element.

(templates)

OOP and Client/Server computing

Each object is an independent entity that communicates with other objects by sending messages. (To: Sort-array, Message:

sort this array of integers.) This maps very nicely onto Client/Server computing which has client programs (objects) on various

machines interacting with server programs (objects) on other machines via some kind of protocol (messages).

OOP and the Job Market

Microsoft, Borland, Word Perfect, and many other large programming firms are now insisting on object oriented

programming experience, usually in C++, for all their programming positions.
 
 

Why Abstract Data Types (ADTs)?

Abstract Data Types (ADTs)  are a higher level way of looking at programming. Prototypical ADTs include lists, stacks, and

sets.

Stacks

Suppose your program needs to keep track of a stack of things. You have at least two choices for ways of implementing

stacks:

As an array

As a linked list

Each method has it's advantages:

Arrays are very efficient on storage, but are difficult to extend, easy to implement

Linked lists have the overhead of a pointer for each item, are easy to extend, but are a bit harder to implement

Depending on the needs of your program, you will pick one of these methods. Unfortunately, you may not be able to

determine which is the best method to use until the middle or end of a project.

The Stack ADT

Ignoring how you implement lists, what sorts of things do you want to do?

void init(stack S, unsigned int nitems)

void push(stack S, item x)

item pop(stack S)

boolean isempty(stack S)

boolean isfull(stack S)

In the best of all possible worlds, you would like to be able to declare things to be of type STACK and ITEM, then choose

at the last possible moment (perhaps when you are linking your final program together) which implementation to use.

This is precisely what ADTs are about.

Formal definition of ADT

An abstract data type (ADT) is a mathematical model with a collection of operations defined on it. [Aho, 1983]
 
 

Why C++?

C++ is the world's most successful object oriented programming language. In [Stroustrup, 1993] Bjarne Stroustrup, the

original author of C++ says:

``C++ did three things [...]

1.  It produced code with run-time and space characteristics that competed head-on with the perceived leader in that field:

C. Anything that matches or beats C must be fast enough. Anything that doesn't can and will - out of need or mere

prejudice - be ignored.

2. It allowed such code to be integrated into conventional systems and produced on traditional systems. A conventional

degree of portability, the ability to coexist with existing code, and the ability to coexist with traditional tools, such as

debuggers and editors, was essential.

3. It allowed a gradual transition to these new programming techniques. It takes time to learn new techniques. Companies

simply cannot afford to have significant numbers of programmers unproductive while they are learning. Nor can they

afford the cost of failed projects caused by programmers poorly trained and inexperienced in the new techniques failing

by overenthusiastically misapplying ideas.''

A quote that I am unable to locate said something similar to ``this system took 45,000 lines of Ada, not counting the

comments. The C++ version was 18,000 lines of fully commented code.''

C++ allows old style programming, object oriented programming, and abstract data types. All of these prudishness fit

together fairly nicely in a production environment.

An annotated C++ bibliography

Entsminger, Gary. The Tao of Objects. Redwood City, CA: M&T Books, 1990.

Just like watching tennis on TV improves your own game, reading this book will get you in the proper mindset for

object oriented programming. Highly, highly recommended.

Eckel, Bruce. Using C++. Berkeley, CA: Osborne/McGraw-Hill, 1989.

I haven't seen this book, but it is Entsminger's favorite. Entsminger says ``A good book to explore and study if you

want to get to the heart of C++.''

Ellis, Margaret, and Stroustrup, Bjarne. The annotated C++ Reference Manual. AT&T, 1990.

This is the base document used by the ANSI C++ committee. When you really want the answer, look here. This is

truly a reference manual.

Stroustrup, Bjarne. The C++ Programming Language, 2nd edition. Don Mills, ON: Addison-Wesley, 1992.

This manual is in the style of Kernighan and Ritchies The C Programming Language. An okay manual by the author of

the language.

Hansen, Tony. The C++ Answer Book. Don Mills, ON: Addison-Wesley, 1990.

This is a companion manual to The C++ Programming Language, containing answers to all the problems posed at the

ends of chapters. An okay source of C++ examples.

Lippman, Stanley. C++ Primer, 2nd edition. AT&T, 1991.

As much as I distrust 600+ page books calling themselves ``primers,'' this does baby-step you through some of the

subtle interactions in C++.

Pohl, Ira. C++ for Pascal Programmers. Don Mills, ON: Benamin/Cummings, 1991.

A good book for Pascal programmers.

Stroustrup, Bjarne. A History of C++. In SIGPLAN Notices,vol 28 no 3, March 1993.

A good description of which features were added to C++, in what order, and why they were added. Reading this

helps make sense of some aspects of C++.

Aho, Alfred, Hopcroft, John, and Ullman, Jeffrey. Data Structures and Algorithms. Don Mills, ON:

Addison-Wesley, 1983.

An excellent book on abstract data types (ADTs). Buy anything from these authors.

Nagler, Eric. Learning C++: A Hands-On Approach. St. Paul, MN: West, 1993.

This book baby-steps you through C++ an example at a time. I have three small complaints: the author doesn't know

about array initialization, nor does he know that `const' has been fixed in C++, and it is based on Borland C++ (with

the occasional reference to AT&T's CFRONT). Otherwise it is an excellent book that may fit your learning style.
 
 

Incompatibilities between ANSI C and C++

Most constructs in ANSI C are legal C++. The exceptions are:

Identifiers

C++ adds the following new keywords:

You can not use these names as identifiers in your program.

C++ identifiers can be any length; C reserves the right to restrict the length

Function declarations

In C++, a function must be declared before it can be called; in C, the function is assumed to return an int

In C++, the function declaration f(); means f takes no arguments; in C, it means f can take any number of arguments.

Use f(...) to get the latter in C++

``old style'' function declarations and calls of undeclared functions are anachronisms and may not be supported by all

implementations

Global data

In C++, a global object must be defined exactly once; in C, it may be declared several times without using extern

In C++, a global const has internal linkage; in C, it has external

Structs

In C++, a class (struct) may not have the same name as a typedef declared to refer to a different type in the

same scope

In C++, a struct is a scope; in C, a struct, an enumeration, or an enumerator declared in a struct is exported to

the scope enclosing the struct

Types

In C++, the type of a character constant is char; in C, it is int

In C++, the type of an enumerator is the type of its enumeration; in C, it is int

Assignment statements

In C++, like pre-ANSI C, pointers must be assigned values of the proper type; in C, any pointer can be assigned a

value of type void*

In C++, assignment to an object of enumeration type with a value that is not of that enumeration is considered an

anachronism; ANSI C recommends a warning

Initializations

In C++, goto's are not allowed to jump over initializations, unless the initialization is in a block and the entire block is

bypassed; in C, you can bypass the initialization

In C++, surplus characters are not allowed in strings used to initialize character arrays; in C, they are

Miscellaneous

C++ requires consistency even across compilation boundaries; some C implementations may consider certain

incompatible declarations legal

C++ allows fewer variations in conforming implementations than C does

How to share header files between C++ and C

Do not declare any classes, overloading, etc

Do not use names as both structure tags and some other type

If function f takes no arguments, declare it f(void)

Global consts must be declared explicitly as either static or extern

Use the predefined C++ preprocessor name __cplusplus (two underscores) to distinguish information for C++ from

that for C

Functions that are to be callable from both languages must be declared to have C linkage:

extern ``C'' double sqrt(double);

or

extern "C" {

double sqrt(double);

double atan2(double, double);

}

C++ comments

In C, there is just one style of comments: text surrounded by /* and */.

In C++ there are two styles of comments: the C style and text between // and the end of a line.

I/O, I/O and Off to Work We Go...

In C++, input and output are first class citizens; in C, they were no better than any other library function. When reading things

in C you usually have to put an & in front of the name to pass the address. However, some things like strings are already

addresses, so you don't put the &. C++ fixes all of this.

Standard places to read and write

In C, you have three files opened for you: stdin, stdout, and stderr.

In C++, you have four streams opened for you: cin, cout, cerr, and clog. (cerr is an unbuffered connection to stderr,

clog is buffered connection.)

1.  A first look at output

Let's start with an example:
 

#include <iostream.h>

main() {

cout << "Hello, world!\n";

}

<< is an operator that evaluates left to right. It takes an ostream on the left and an object on the right, outputs the object then

returns the ostream as its result. All this is necessary to make the following program work:
 
 

#include <iostream.h>

main() {

char c='0';
short h = 1;
int d = 2;
long ld = 3;
float f = 4.0;
double g = 5.0;
char s[] = "six";
cout << c << h << d << ld
<< f << g << s
<< endl;

}

endl (endline) is a predefined ostream operation that both inserts a newline and flushes the buffer. The output is:

012345six

2. A first look at input

>> is an operator that evaluates left to right. It takes an istream on the left and an object on the right, inputs the object then

returns the istream as its result. If the input operation fails it returns 0. The following program:
 

#include <iostream.h>

main() {

char c;
short h;
int ;
long ld;
float f;
double g;
char s[100];
cin >> c >> h >> d >> ld
>> f >> g >> s;

cout << c << h << d << ld
<< f << g << s
<< endl;

}

And the input:

01 2 3 4 5six

Produces the same output as the previous program. Notice that you did not have to put &'s in front of c, h, d, ld, f, and g as

you would have in C.

If the input operation fails it returns 0. This allows the following program to work:
 
 

#include <iostream.h>

main() {

int d, sum = 0;
while( cin >> d )
sum += d;
cout << "The sum is " << sum
<< endl;

}

Given the same input as above it produces:

The sum is 15

3. Formatted input and output

To get formatted I/O using iostream.h use the following functions:

#include <iostream.h>

#include <math.h>

main() {

int i = 1024;
cout << "i (decimal) = " << dec << i << endl;
cout << "i (hexadecimal) = " << hex << i << endl;
cout << "i (octal) = " << oct << i << endl;
cout << "You owe me: $"; cout.precision(2);
cout.fill('*'); cout.width(9);
cout.setf(ios::fixed,ios::floatfield);
cout << 123.45 << endl;
cout << "Precision: " << cout.precision() << endl
<< "Width: " << cout.width() << endl
<< "Fill: " << cout.fill() << endl;

// Note width gets reset to 0
cout.setf(ios::scientific,ios::floatfield);
cout.precision(10);
cout << "sqrt(2.0) = " << sqrt(2.0) << endl;
cout << "i (still octal) = " << oct << i << endl;
}

This produces the following output:

i (decimal) = 1024
i (hexadecimal) = 400
i (octal) = 2000
You owe me: $***123.45
Precision: 2

Width: 0

Fill: *

sqrt(2.0) = 1.4142135624e+00
i (still octal) = 2000
Opening and closing files (streams)
opening and closing files is very easy.

#
 
 

include <fstream.h>

main() {

char ch;
ifstream inf("data");
ofstream outf;
fstream inoutf("database",ios::in+ios::out);
outf.open("results");
if( inf.good() ) {
while( inf.get(ch) ) {
outf.put(ch);
}

}

inf.close();
outf.close();
inoutf.close();

}

All types of stream constructor take a second parameter called the open_mode.

4. I/O condition states

after opening or after any I/O operation you can test the status of a stream:

More on output

The following functions can be used on ostreams and iostreams:

seek and tell

The following functions can be used on istreams, ostreams and iostreams:

Pass by reference

In C, everything passed to a function was passed by value. That is, a copy of the parameter is made and the copy is passed.

That means that a function like swap has to be called carefully:
 

#include <stdio.h>

void swap(int *a, int *b)

{
int t = *a; *a = *b; *b = t;
}

main() {

int i = 5, j = 7;

swap(i, j); /* Nothing happens to i and j */
printf("i = %d, j = %d\n",i,j);
swap(&i, &j); /* This works */
printf("i = %d, j = %d\n",i,j);

}

This produces the output:

i = 5, j = 7
i = 7, j = 5

C++ adds a new way to pass variables: by reference:

#include <iostream.h>

void swap(int &a, int &b)

{
int t = a; a = b; b = t; // looks more normal
}

main() {

int i = 5, j = 7;
swap(i, j); // This works!
cout << "i = " << i << " j = " << j << endl;
}

This produces the output:

i = 7, j = 5

There is no inefficiency in C++ doing this. The code generated for the call and execution of swap is likely to be exactly the
same for the two programs above.

Default arguments

C++ allows you to provide default values for arguments to functions. These must be the righmost arguments. Any or all of

them can be left out.
 

typedef enum {false, true} boolean;

void draw_axis(double Xlen, double Ylen,

double Xmin = 0.0, double Ymin = 0.0,

double Xscale = 1.0, double Yscale = 1.0,

boolean Xlogarathmic = false,

boolean Ylogarithmic = false,

boolean full_grid = false);

Now,

draw_axis(10.,10.);

Will draw a set of axis. However, if you want to mention the Yscale:

draw_axis(10.,10.,0.0,0.0,1.0,0.5);

Will scale the Y axis by 0.5. To get a full grid:

draw_axis(10.,10., 0.,0., 1.,1., false, false, true);

Don't let the complexity of this example throw you off. Default arguments are very useful. For example fstream.h defines

open for ostream as

void open(const char *name, int mode=ios::out, int prot=0664);

So ostream's open up by default with mode=ios::out and prot set so that you and your group can read and write the file

and others can just read it (the operating system may over-ride this). Since these are reasonable defaults, most of the time you

won't change them nor will you have to specify them.

Beware! There is a subtle interaction between default arguments and overloaded functions that we will talk about when we

know about them both.
 


Intro to Classes: Abstract Data Types (ADTs)

C++ supports ADTs through information hiding and definitions of public interfaces. To see what this means, let's revisit the list

ADT we first met in chapter 6:

The Stack ADT

Ignoring how you implement lists, what sorts of things do you want to do?

void init(stack S, unsigned int nitems)

void push(stack S, item x)

item pop(stack S)

boolean isempty(stack S)

boolean isfull(stack S)

For now, let's assume all items are integers. (We'll make our ADT more general when we learn about templates.)

Declaring the public interface

File: mydefs1.h

Note:

classes look a lot like structs. In fact, C++ treats structs as a class whose members are by default public

by default, members of classes are private. We could say private after line 3, if we wanted to make this explicit

An array implementation of int_stack

File: stack1.C

Note:

We'll talk about new (line 10) later. It essentially does a malloc(sz*sizeof(int)).

assert( exp ) does nothing if exp is non-zero, else it raises a run-time exception

Note the fully qualified names must be given (int_stack::init et al)

The main thing about int_stack

File: stackmain1.C

Note:

It's short!

I've tested it by pushing 501 things onto s1 and by adding another s2.pop() after line 22

The (correct) output is:

19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

If we forget to call init, we are in big trouble

We haven't provided any way to get rid of a stack

Declaring the public interface for a linked list int_stack

File: mydefs2.h

Note:

The public part of the interface looks the same

Rewrite main

File: stackmain2.C

Note:

There is but a one character change, on line 2!

This has been tested with 21 pops at line 16. It correctly detects the empty stack.

A linked list implementation of stack

File: stack2.C

Note:

line 9 makes sure there was free store available

nitems is completely ignored by init (lines 14-17)

delete t works like free( t )

Some of these functions sure are short!

Inline functions

As we have seen above, lots of useful functions are very short. To incur the 5-20 machine instructions associated with

function call and return seems awfully expensive. (This was why the old FORTRAN technique of writing everything in main

got developed. Ugh.)

To address this, C++ allows you to put explicitly the inline modifier on function calls. For example:

inline int abs( int a )

{
return a < 0 ? -a : a;
}

C++ treats this as a suggestion only. The compiler is free to implement this as a true function. However, most implementations

will expand this function in-line, so no function call and return is done.

Contrast this with the C preprocessor method:

#define ABS(A) ( ((A) < 0) ? -(A) : (A) )

(I believe all those brackets are necessary to avoid unwanted side-effects.)

You can also implicitly inline functions inside classes (these functions are referred to as methods in the C++ literature) by

defining them inside the definition. ie.

File: mydefs1a.h

Note:

This is an extreme case! Everything is done inline. (There is no stack1a.C)

Need to make a modified version of stackmain1.C that includes mydefs1a

Let's take a look at the linked list implementation too:

File: mydefs2a.h

File: stack2a.C

Overloaded functions

When two functions have the same name, but are distinguished by the type of their operands or the type of the value they

return, they are said to be overloaded. This simplifies programming, and is something you are already very familiar with.

'+' is an example of an overloaded function

Take the simple C assignment:

a = b + c;

What does it do? There are several possibilities:

It adds signed integers b and c then stores the result in a signed integer location

it adds doubles b and c then stores the result in a double location

Many other variations to do with adding integers to unsigned integers, or integers to floats or doubles

In spite of the complexity of this description, we would still say + is not that complicated. It takes b and c and ``does some

reasonable form of addition'' on them, then does whatever it takes to store it in location a. The writer of the C compiler has to

worry about all the possibilities; you mostly don't care.

Overloading stack

Suppose we want to have a stack of doubles in our program, as well as a stack of integers.

However, we don't want to use names other than push and pop on each type of stack.

Let's try it:

File: mydefs1b.h

File: stackmain1b.C

Note:

The output of this program is:

19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

t s r q p o n m l k j i h g f e d c b a

push and pop figure out which type of stack they are using

You can write line 14 as push(s1,i) if you find it more natural

Interactions between default arguments and overloaded functions

Consider the following two overloaded functions:

int fa(int i, int j=5);

int fa(int i, char c='e');

What does this mean?

int k = fa(3);

Does it mean k = fa(3,5) or k = fa(3,'e')? C++ compilers are required to flag this as an ambiguous statement and

treat it as an error, but not all compilers do.

constructors and destructors

If your abstract data types are going to be first class citizens, they need to be shown a bit more respect. When you declare an

array or a structure or an integer in a function, C++ allocates the memory for you when the function is called. Similarly, when

the function returns, C++ deallocates the memory. Wouldn't it be nice if you could get access to this?

Note:

We want to be able to say

{
int_stack s1;
// ...
}

// storage for s1 has been deallocated

and not have to do the s1.init();

We don't want to lose the ability to do the functional equivalent of s1.init(500);

which allocates a stack of non-default size.

We would also like to be able to be able to do things like int_stack array_of_stacks[10];

Or even

int_stack *ps;

ps = new int_stack;

// ...

dispose ps;

C++ uses special member functions based on the name of the class. For int_stack the constructor would be called

int_stack::int_stack (compare with int_stack::push etc.); the destructor would be called int_stack::~int_stack.

Let's try it:

File: mydefs1c.h

File: stackmain1c.C

Note:

The (correct) output is:

19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0

10 11 12

Using just one constructor as before say, int_stack( int nitems = 100), works for everything except the array

declaration int_stack ... s4[5]. There the default constructor int_stack() gets called 5 times, so we need to declare such

a constructor.

There are three equivalent ways of declaring a stack of non-default size:

int_stack s1(500); // or

int_stack s1 = 500; // or

int_stack s1 = {500};

Here are some other equivalent declarations:

char s[] = "hello"; // or

char s[] = {"hello"}; // or

char *s = "hello"; // or

char *s = {"hello"};

//

int i = 5; // or

int i(5); // ARM says this works, g++ says it doesn't

int i = {5};

Line 18 of main: The selector -> has been extended to select member functions

Line 13 of the header file uses delete[] which deletes arrays.

new and delete vs. malloc() and free()

new vs. malloc()

If T is some type in your program (char, int, double, or some struct or class) and

T *tp, *ta;

then

tp = new T;

is equivalent to the pre-ANSI C style of:

tp = (T *) malloc(sizeof(T));

Note:

The ANSI C style of tp = malloc(sizeof(T));

doesn't work in C++ because void * (the type returned by ANSI malloc) does not coerce to all pointer types. C++

points out that this is a hole in C's otherwise careful type system.

Similarly,ta = new T[n];is akin to the pre-ANSI C style of:

ta = (T *) malloc(n * sizeof(T));

except

the default constructor for T, T::T(), gets called n times if it exists

the size of the resulting array is saved ``somewhere'' for use by delete[]

Note:

ta = new T[0]; still returns a pointer to an object of type T

if no storage is available new returns 0, so a quick and dirty way of checking is:

ta = new T[n];

assert( ta ); // or even assert( ta = new T[n] );

delete vs. free()

Continuing our example from above,delete tp; is equivalent to the ANSI C free(tp);

Note:

delete[] tp; is not defined when tp is allocated without using the array syntax

delete[]

Continuing our example from above,delete[] ta; is equivalent to the ANSI C free(ta);

Note:

delete ta; is not defined when ta is allocated using the array syntax

deleting parts of arrays or other proper subsets of the memory returned by new is not defined

Overloading operators

Most operators can be overloaded. A few cannot.

Operators which can be overloaded

Note:

Both the binary and unary forms of can be overloaded.

( ) is function call. [] is subscripting.

Defining your own operator ::new allows you to take over the management of the free store. Use it for debugging

only. (What if two programmers on a project rewrite this?)

Operators which cannot be overloaded

An example - complex numbers

File: Complex.H from Gnu g++ distribution (modified for display here)

Note:

Most of the operators are defined in a proper, usable form; this is a good template for your own classes

A more careful definition might declare all of the non-member functions as friends

These declarations declare const functions returning doubles:

double real() const;

double imag() const;

operators follow the usual rules of precedence and order of evaluation (left to right or vice versa). There is no way to

modify these rules. (If you want + to evaluate before times you have to do it using parenthesis.)

In case the meaning of this class is not clear, we can now write programs that look like complex numbers were always

part of C++, like this:

#include <iostream.h>

#include <Complex.h>

main() {

Complex a,b,c(5),d(1,2);

a = c * d;

b = a - c;

cout << "b = " b << endl;

}

Friends

A function that is a friend of class X has access to all of X's private parts. (This is also the '90's definition of friend :-).) It is

particularly useful for the I/O operators >> and << which work on istream& and ostream& objects and so cannot be

member functions of your class. It is also useful for efficient access. Let's revisit our linked list implementation of int_stack,

and see how simple it becomes:

File: mydefs2b.h

File: stack2b.C

Template functions and classes

In one of our array implementation of stacks we had two classes: int_stack and char_stack. This seems like a fair bit of

rather error prone work to have to create a new class of stack for every class of object we might want to put on a stack.

Templates were invented for just such situations as these.

File: mydefs1d.h

File: stackmain1d.C

Support of object oriented programming: inheritance and virtual

functions

One of the promises I made to you at the beginning was that you could put off implementation choices to the very end. We're

now going to declare a pure virtual class called vstack, two derivations (arrays and linked lists), a main program that

declares stacks of both types and passes them one at a time to a function that has no idea which type of stack it is getting and

indeed handles an array_stack on the first call and a linked_stack on the second.

The virtual base class vstack

File: vstack.h

A derivation of stacks using arrays

File: arraystack.h

A derivation of stacks using linked lists

File: linkedstack.h

The main program

File: stackmain.C

The function that uses vstack

File: reverse.C

public vs. protected vs. private access

members of a class can be:

private, the name can only be used by member functions and friends of the containing class

protected, the name can be used by the above and by member functions and friends of derived classes

public, the name can be used by any function

Use these to control visibility in the interface.

multiple inheritance

We'll work through an example of a theoretical University payroll system.

Designing with objects

This section has largely been paraphrased from [Enstlinger,90]and [Stroustrup, 1992].

Rules of thumb

1.Don't expect to know everything before you start

2.Get something working as soon as possible

3.Change the design as you go

4.Try to make your mistakes as early as possible

Design steps

1.Find the concepts (classes) and their fundamental relationships

2.Refine the classes by specifying the operations on them

3.Classify these operations. Consider construction, copying, and destruction

4.Consider minimalism, completeness, and convenience

5.Refine the classes by specifying their dependencies on other classes

6.Consider inheritance

7.Use dependencies

8.Specify the interfaces for the classes

9.Separate functions into private, public and protected operations

10.Specify the exact type of the operations on the classes

Discovering classes

1.Look for external factors; interactions between objects (classes) and the world outside them. These help you to find

the objects themselves. Look for boundaries in the real world. These are often reflected in your objects. Look for

initialization and cleanup. These belong in constructors and destructors.

2.Look for things that are duplicated. Are there are two or more of some object? Might you someday want to add

another one of these objects? Common interfaces belong inside an abstract base type.

3.Look for the smallest common denominator in the system. For a text editor, you sometimes want to edit characters,

words, lines, blocks, files. This gives you a whole set of related objects and relationships.

4.Will this class work in other situations? Is it general? Which things stay the same? Which differ?

5.Look at the data. Group data that seems to belong together. Is this an object? Should this data be hidden or at least

not be carelessly modified? Put it inside an object

6.Write a main program using the objects you have so far. Do you have everything you need?

More on const

const is tricky enough that it deserves a topic of its own.

Unlike C, const really is constant in C++

The following declarations work in C++ but not in C:

const int maxn = 1000;

int a[maxn + 1];

const pointer vs pointer to const vs const pointer to const

When pointers and const are involved in the same declaration, what does it mean? Does it mean the pointer doesn't change

(const pointer)? Does it mean the thing the pointer points at doesn't change (pointer to const)? Or does it mean neither the

pointer nor the thing it points at change? Luckily, it is possible to express all three concepts in C++:

int i = 1, j = 3;

int *const p = &i; // const pointer

const int *q = &i; // pointer to const

const int *const r = &i; // const pointer to const

*p = 5; // okay

p = &j; // error

*q = 5; // error

q = &j; // okay

*r = 5; // error

r = &j; // error

const reference variables

You can have a reference to an object, and tell the compiler that you don't want to use this reference to change the variable.

int i;

const int j = 1;

const int &r = i;

const int &s = j;
 
 

i = 2; // okay

j = 2; // error

r = 2; // error

s = 2; // error
 
 

const reference variables and arguments to functions

Rather than have a large object pushed onto the stack, you can pass a reference to the object. If you want to indicate that the

function won't be making any changes to the object, you can pass it by const reference. This is probably the most common

use of references, since it acts like an ``efficient call by value:''

void print_large_array( const int &ar[], int n )

or

void print_large_array( const int *&ar, int n )

const references to class data

A class acts like a struct that is passed implicitly to all its member functions. You can indicate that a member function does not

modify the data area of its class by using const, giving the same ``efficient call by value'' for the implicit class argument that the

previous section gave to explicit arguments:

class sample {

int n;

public:

void set( int i ) { n = i; } // Allowed to change n

int get() const { return n } // Not allowed to change n

}

returning const

You can indicate that the return value has some degree of const:

const int n = 1;

const v() { return n; }

const int w() { return n; }

const int *x() { return &n; }

int *const y() { return &n; } // error return of non-`const *'

// pointer from `const *'

const int *const z() { return &n; }

int main()

{

int i = 1;

const int *q = &i; // pointer to const

int *s; // pointer

i = v(); // okay

i = w(); // okay

q = x(); // okay

s = x(); // error assignment of non-`const *' pointer from `const *'

q = y(); // okay

s = y(); // okay

q = z(); // okay

s = z(); // error assignment of non-`const *' pointer from `const *'

}

Linkage of const variables

Unlike C, const variables declared at the outermost level have internal linkage by default:

#include <iostream.h>

const int zero = 0; // Internal in C++, External in C

extern const int one = 1; // External in both C++ and C

static const int two = 2; // Internal in both C++ and C

More on reference

References to pointers

References to pointers can be used in place of ``pointers to pointers'':

#include <iostream.h>

void setstr( char *&var, char *str )

{

var = str;

}

int main()

{

char *s;

setstr( s, "hello" );

cout << "s is '" << s << "'" << endl;

}

The output is:

s is 'hello'

Functions that return lvalues

Lvalues are ``things that can occur on the left-hand side of the assignment operator.'' References allow functions to return

lvalues:

int a[10];

int &f( int i )

{

return a[i - 1990];

}

int main()

{

f(1994) = 7; // Sets a[4] = 7

}

This is also useful for overloading operators; operator= for type T would be of type T &.

Copy constructors

C++ will call the copy constructor for class T when:

An instance of class T is created from an existing instance of class T. For example:

A function is called and an argument of class T is passed by value:

An unnamed temporary variable is created to hold the return value of a function returning an instance of class T

Examples:

class T {

char *p;

public:

T() {};

T( char *s ) { p = strdup( s ); }

~T() { delete p; }

};

T f( T c )

{

return c;

}

int main()

{

T *a = new T( "hello" );

T b;

T c = *a; // copy constructor will be called for c

b = f( *a ); // copy constructor will be called twice,

// once to pass a to f, once to set b

delete a; // The string that b and c point to is gone!

}

C++ will supply a copy constructor for each class that recursively copies member data from the source instance to the

destination. However, if any of the class members are pointers, then this will give you two pointers to the same memory

location. The copy constructor should be of type:

T( const T & )

That is, it is a constructor that can read but not modify (that's why the const is there) another instance of T. C++ passes the

instance by reference for efficiency. For our example:

class T {

char *p;

public:

T() {};

T( char *s ) { p = strdup( s ); }

~T() { delete p; }

T( const T& u ) { p = strdup( u.p ); }

};

The base/member initialization list

C++ has extended the syntax of constructor functions. You can initialize any data member by giving its name followed by a

list of one or more values in parenthesis. These go in a comma separated list after the closing parenthesis for the argument list

for the constructor. A colon (`:') separates the initialization list from the parameters. For example:

class student {

int sid;

char *fname;

char *lname;

double gpa;

public:

student(int id,char *f,char *l):sid(id),fname(f),lname(l),gpa(0){}

};

You can use any values you want, even the results of function calls.

Of course, you could have also written this constructor like this:

student(int id,char *f,char *l){sid=id;fname=f;lname=l;gpa=0;}

Sometimes you have to initialize data members this way. For example, we might decide that a students sid, fname, and

lname should never change after initialization, so we declare them to be const. However, now the only way we can initialize

sid, fname, and lname is through an initialization list:

class student {

const int sid;

const char *const fname;

const char *const lname;

double gpa;

public:

student(int id,char *f,char *l):sid(id),fname(f),lname(l){gpa=0;}

};

Of course, gpa can be initialized using either method.

The this pointer

Every class T has an implicit data member called this, that is provided by the compiler as if you had declared:

T *const this;

It contains the address of the class instance. In early versions of the C++ language this was used a lot. In modern C++

there are very few uses. If you find yourself using the this pointer a lot, you probably are doing things the wrong way. The

major use of this is for writing member functions that manipulate pointers, especially those where you want to use pointers to

link several class instances together. The following example is from Stroustrup, pp 147/8:

class dlink {

dlink *pre; // previous

dlink *suc; // next

public:

void append( dlink * );

// ...

};

void dlink::append( dlink * p )

{

p->suc = suc; // That is, p->suc = this->suc

p->pre = this; // Explicit use of ``this''

suc->pre = p; // That is, this->suc->pre = p

suc = p; // that is, this->suc = p

}

dlink *list_head;

void f(dlink *a, dlink *b)

{

list_head->append(a);

list_head->append(b);

}

More on operator overloading

Here are some points to consider when overloading operators:

No default arguments are allowed

You can only use the same form (unary or binary) as already exists in the predefined operators. That is, you can

overload + as either unary or binary, but ~ must be unary and | must be binary.

new and delete can be overloaded as static member functions. The following:

must be overloaded as member (not friend) functions. (You probably should also treat the other assignment operators

like +=, *=, etc. the same, but C++ won't insist on it.) All the others operators can be defined as either a nonstatic

member or a global friend.

At least one of the arguments must be an instance of the class to which the operator belongs. Note: For member

functions the first argument is implicitly an instance of the class.

Here's how the compiler translates your code from infix into functional notation. @ is the operator being overloaded, X

and Y are class instances:

User-defined conversion functions

Conversion functions convert class objects to other class objects or to one of the predefined types. Here are the rules:

It must be a member (not friend) of the class from which you want to convert

The conversion to type foo is declared as operator foo()

There are no arguments to the function

There is no explicit return type; the return type is implicit in the function name

The function must return a value of the same type as the name of the function

The function very likely will not modify any class members, so you can put const after the (empty) argument list. i.e..

operator foo() const.

For example,

class integer {

int number;

public:

integer( int n = 0 ) : number( n ) {} // constructor

operator int() const { return number; } // convert to int

operator double() const { return (double)number; } // to double

};

int main()

{

integer in;

int j = in; // implicit cast

int k = (int)in; // explicit C-style cast

int l = int(in); // explicit C++-style cast

}

If you wanted to add a conversion to class X you could add the member function:

operator X() const; // convert to X

Argument matching

The following is a simplified version of the argument matching rules for deciding which function to call. For a more complete

set of rules consult either Stroustrup or the ARM.

The function name must be within scope

A set of conversions must exist so that the function could conceivably be called

The best-matching function is the intersection of sets of functions that best match on each argument. Unless this list has

exactly one member, the call is illegal.

The function so selected must be a strictly better match for at least one argument than every other possible function,

otherwise the call is illegal

A function with n default arguments is treated like n+1 functions with different numbers of arguments

Non-static member functions are considered to have an extra argument specifying the object for which it is called

An ellipsis (...) is a match for an actual argument of any type

If there are two sequences of conversions of different length that lead to the correct type (for example,

int->float->double and int->double) then only the shorter sequence will be considered

The following trivial conversions are considered ``best'':

Next best are the integral (char, short int, enum, bit field to int) promotions and conversions from

float to double

Next are the conversions from unsigned to signed, floating to integral, and all the pointer and reference conversions

Next are matches with user-defined conversions

Last are matches with ellipsis

Catch, throw, and try

catch, throw, and try are an elegant exception handling message. Since I do not have access to a compiler that supports

them, the following code should be taken as an idea of how they work, and not as a working program:

#include <iostream.h>

class divide_by_zero;

double divide( double x, double y) throw( divide_by_zero )

{

if( y == 0.0 ) {

throw divide_by_zero;

}

return x / y;

}

int main()

{

double a, b;

cout << "Enter dividend and divisor: ";

while( cin >> a >> b ) {

try {

cout << a << " / " << b " = "

<< divide(a,b) << endl;

}

catch( divide_by_zero ) {

cerr << "Even C++ can't divide by zero!" <<

endl;

}

}

}

Note:

You thow objects and catch types

To catch all possible exceptions use:

catch()

Inheritance can be used to group exceptions

You can declare the list of exceptions a function can throw, although the code works without it. That is, you can

declare divide as:

double divide( double x, double y)

If an exception is not caught, then the function terminates (calling all appropriate destructors) and returns. This process

continues recursively until main is reached. If main doesn't catch the error then the function unexpected() is called,

which has a default meaning of terminate() which calls abort().

Potpourri

A word about default arguments

Only specify default arguments (int foo( n = 0) ) in the declaration (the part that is the candidate for the ``.h'' file); do not

put them in the definition. They are part of the public interface. You do not want to have them declared in two places,

because that is a code maintenance problem.

Debugging constructors and destructors

The following code is extremely useful when trying to figure out which destructor is having problems:

#include <iostream.h>

class Tracer {

char *p;

public:

Tracer( char *s )

{ p = s; cerr << s << " entered" << endl; }

~Tracer()

{ cerr << "exit from " << p << endl; }

};

Here's an example of it's use. (Normally you'd interleave other class declarations):

int main()

{

Tracer a("one");

Tracer b("two");

}

The output is:

one entered

two entered

exit from two

exit from one

``is a'' vs. ``has a'' vs. ``kind a''

There are three kinds of hierarchical relationships in C++: inheritance (deriving one class from another), inclusion (including

one object as a data member of another), and template (generalizing a specific class given a description of a more general

class). The following chart may help:

resource acquisition is initialization

(This is from Stroustrup, section 9.4).

Whenever a function needs to acquire some resource - memory, a file pointer, or whatever - it needs to handle it responsibly.

For example:

void use_file(const char *fn)

{

FILE *f = fopen(fn, ``w'');

// use f

fclose(f);

}

What if something goes wrong while using `f'? Will `f' get closed? How can we guarantee that the call to fclose(f) will

happen? Consider the following code:

#include <stdio.h>

class FilePtr {

FILE *p;

public:

FilePtr( const char *n, const char *a )

{ p = fopen(n,a); }

FilePtr( FILE *pp) { p = pp; }

~FilePtr() { fclose(p); }

operator FILE*() { return p; }

};

void use_file(const char *fn)

{

FilePtr f(fn, "w");

// use f

}

Note that no matter what happens, the destructor ~FilePtr() gets called for f.

Allocating small objects

(This is from Stroustrup, section 5.5.6). Say you want to have lots of small objects around, and you don't want to have the

considerable overhead of allocating and freeing them. You can switch to a type-specific allocator as in the following example.

Note:In the code below:

The builtin new operator must store the size of the object somewhere; it likely allocates 12 bytes per node because of

this, our allocator likely only allocates 8 bytes

You should be able to declare ``static node *nfree'' inside of class node; my compiler wouldn't let me.

Saying ``node *q = new node[NALL]'' would cause infinite recursion

I could not make this work for templates

The code for main is the same as stackmain2a.C (except we #include mydefs2c.h)

File: mydefs2c.h

File: stack2c.C