
Beginning Visual C++ 2005 (2006) [eng]-1
.pdf
Class Inheritance and Virtual Functions
using namespace System::Collections::Generic; |
// For generic collections |
//Class encapsulating a name ref class Name
{
public:
Name(String^ name1, String^ name2) : First(name1),Second(name2){} virtual String^ ToString() override{ return First +L” “+Second;}
private: String^ First;
String^ Second;
};
//Class encapsulating a phone number
ref class PhoneNumber
{
public:
PhoneNumber(int area, int local, int number): Area(area),Local(local), Number(number){}
virtual String^ ToString() override
{ return Area +L” “+Local+L” “+Number; }
private: int Area; int Local;
int Number;
};
int main(array<System::String ^> ^args)
{
// Using List<T>
Console::WriteLine(L”Creating a List<T> of integers:”); List<int>^ numbers = gcnew List<int>;
for(int i = 0 ; i<1000 ; i++) numbers->Add(2*i+1);
//Sum the contents of the list int sum = 0;
for(int i = 0 ; i<numbers->Count ; i++) sum += numbers[i];
Console::WriteLine(L”Total = {0}”, sum);
//Using LinkedList<T>
Console::WriteLine(L”\nCreating a LinkedList<T> of double values:”); LinkedList<double>^ values = gcnew LinkedList<double>;
for(int i = 0 ; i<1000 ; i++) values->AddTail(2.5*i);
double sumd = 0.0;
for each(double v in values) sumd += v;
Console::WriteLine(L”Total = {0}”, sumd);
LinkedListNode<double>^ node = values->Find(20.0); // Find node containing 20.0
559

Chapter 9
values->AddBefore(node, 19.9); values->AddAfter(values->Find(30.0), 30.1);
// Sum the contents of the linked list again sumd = 0.0;
for each(double v in values) sumd += v;
Console::WriteLine(L”Total after adding values = {0}”, sumd);
// Using Dictionary<K,V>
Console::WriteLine(L”\nCreating a Dictionary<K,V> of name/number pairs:”); Dictionary<Name^, PhoneNumber^>^ phonebook = gcnew Dictionary<Name^,
PhoneNumber^>;
//Add name/number pairs to dictionary Name^ name = gcnew Name(“Jim”, “Jones”);
PhoneNumber^ number = gcnew PhoneNumber(914, 316, 2233); phonebook->Add(name, number);
phonebook->Add(gcnew Name(“Fred”,”Fong”), gcnew PhoneNumber(123,234,3456)); phonebook->Add(gcnew Name(“Janet”,”Smith”), gcnew PhoneNumber(515,224,6864));
//List all numbers
Console::WriteLine(L”List all the numbers:”);
for each(PhoneNumber^ number in phonebook->Values) Console::WriteLine(number);
// List names and numbers
Console::WriteLine(L”Access the keys to list all name/number pairs:”); for each(Name^ name in phonebook->Keys)
Console::WriteLine(L”{0} : {1}”, name, phonebook[name]);
return 0;
}
The output from this example should be as follows:
Creating a List<T> of integers:
Total = 1000000
Creating a LinkedList<T> of double values:
Total = 1248750
Total after adding values = 1248800
Creating a Dictionary<K,V> of name/number pairs: List all the numbers:
914 316 2233
123 234 3456
515 224 6864
Access the keys to list all name/number pairs: Jim Jones : 914 316 2233
Fred Fong : 123 234 3456
Janet Smith : 515 224 6864 Press any key to continue . . .
560

Class Inheritance and Virtual Functions
How It Works
Note the using namespace directive for the System::Collections::Generic namespace; this is essential when you want to use the generic collections classes with specifying fully qualified class names.
The first block of code in main() uses the List<> collection using the code from the previous section. It creates a class that stores integers in a list and then stores 1000 values in it. The loop that sums the contents of the list uses the default indexed property to retrieve the values. This could also be written as a for each loop. Don’t forget — the default indexed property only accesses item already in the list. You can change the value of an existing item using the default indexed property, but you cannot add new items this way. To add an item to the end of the list, you use the Add() function; you can also use the Insert() function to insert an item at a given index position.
The next block of code in main() demonstrates the use of the LinkedList<> collection to sort values of type double. Values of type double are added to the end of the linked list using the AddTail() function in a for loop. You could equally well use the AddLast() function to do the same thing. Values are retrieved and summed in a for each loop. Note that there is no default indexed property for accessing items in a linked list. The code also show the use of the Find() and AddBefore() and AddAfter() functions to add new elements at a specific position in the linked list.
The last block of code in main() shows a Dictionary<> collection being used to store phone numbers with names as keys. The Name and Phone number classes implement an override to the inherited ToString() function to enable the Console::WriteLine() function to output suitable representations of objects of these types. Three name/number pairs are added to the phonebook dictionary. The code then lists the numbers in the dictionary by using a for each loop to iterate over the values contained in the collection object that is returned by the Values property for phonebook. The last loop iterates over the names in the collection returned by the Keys property and uses the default indexed property for phonebook to access the values. No try block is necessary here because you are certain that all the keys in the Keys collection are present in the dictionary — if they are not, there’s a serious problem with the implementation of the Dictionary<> generic class!
Summar y
This chapter covered the principal ideas involved in using inheritance for native C++ classes and C++/CLI classes. The fundamentals that you should keep in mind are:
A derived class inherits all the members of a base class except for constructors, the destructor, and the overloaded assignment operator.
Members of a base class declared as private in the base class are not accessible in any derived class. To obtain the effect of the keyword private but allow access in a derived class, you should use the keyword protected in place of private.
A base class can be specified for a derived class with the keyword public, private, or protected. If none is specified, the default is private. Depending on the keyword specified for a base, the access level of the inherited members may be modified.
If you write a derived class constructor, you must arrange for data members of the base class to be initialized properly, as well as those of the derived class.
561

Chapter 9
A function in a base class may be declared as virtual. This allows other definitions of the function appearing in derived classes to be selected at execution time, depending on the type of object for which the function call is made.
You should declare the destructor in a native C++ class base class that contains a virtual function as virtual. This ensures correct selection of a destructor for dynamically-created derived class objects.
A native C++ class may be designated as a friend of another class. In this case, all the function members of the friend class may access all the members of the other class. If class A is a friend of B, class B is not a friend of A unless it has been declared as such.
A virtual function in a native C++ base class can be specified as pure by placing = 0 at the end of the function declaration. The class then is an abstract class for which no objects can be created. In any derived class, all the pure virtual functions must be defined; if not, it, too, becomes an abstract class.
A C++/CLI reference class can be derived from another reference classes. Value classes cannot be derived classes.
An interface class declares a set of public functions that represent a specific capability that can be implemented by a reference class. An interface class can contain public functions, events, and properties. An interface can also define static data members, functions, events and properties and these are inherited in a class that implements the interface.
An interface class can be derived from another interface class and the derived interface contains the members of both interfaces.
A delegate is an object that encapsulates one or more pointers to functions that have the same return type and parameter list. Invoking a delegate calls all the functions pointed to by the delegate.
An event member of a class can signal when the event occurs by calling one or more handler functions that have been registered with the event.
A generic class is a parameterized type that is instantiated at run-time. The arguments you supply for type parameters when you instantiate a generic type can be value class types or reference class types.
The System::Collections::Generic namespace contains generic collection classes that define typesafe collections of objects of any C++/CLI type.
You can create a C++/CLI class library in a separate assembly and the class library resides in a
.dll file.
You have now gone through all of the important language features of ANSI/ISO C++ and C++/CLI. It’s important that you feel comfortable with the mechanisms for defining and deriving classes and the process of inheritance in both language versions. Windows programming with Visual C++ 2005 involves extensive use of all these concepts.
Exercises
You can download the source code for the examples in the book and the solutions to the following exercises from http://www.wrox.com.
562

Class Inheritance and Virtual Functions
1.What’s wrong with the following code?
class CBadClass
{
private: int len; char* p;
public:
CBadClass(const char* str): p(str), len(strlen(p)) {} CBadClass(){}
};
2.Suppose you have a class CBird, as follows, that you want to use as a base class for deriving a hierarchy of bird classes:
class CBird
{
protected:
int wingSpan; int eggSize;
int airSpeed; int altitude;
public:
virtual void fly() { altitude = 100; }
};
Is it reasonable to create a CHawk by deriving from CBird? How about a COstrich? Justify your answers. Derive an avian hierarchy that can cope with both of these birds.
3.Given the following class:
class CBase
{
protected:
int m_anInt;
public:
CBase(int n): m_anInt(n) { cout << “Base constructor\n”; } virtual void Print() const = 0;
};
What sort of class is CBase and why? Derive a class from CBase which sets its inherited integer value, m_anInt, when constructed, and prints it on request. Write a test program to verify that your class is correct.
4.A binary tree is a structure made up of nodes where each node contains a pointer to a “left” node and a pointer to a “right” node plus a data item, as shown in Figure 9-7.
The tree starts with a root node, and this is the starting point for accessing the nodes in the tree. Either or both pointers in a node can be null. Figure 9-7 shows an ordered binary tree, which is a tree organized so that the value of each node is always greater than or equal to the value of the left node and less than or equal to the value of the right node.
Define a native C++ class to define an ordered binary tree that stores integer values. You also need to define a Node class, but that can be an inner class to the BinaryTree class. Write a program to test the operation of your BinaryTree class by storing an arbitrary sequence of integers in it and retrieving and outputting them in ascending sequence.
Hint: Don’t be afraid to use recursion.
563

Chapter 9
|
Root Node |
||
|
Value = 120 |
|
|
|
|
|
|
|
left node |
right node |
|
|
pointer |
pointer |
|
|
|
|
|
Node |
|
Node |
Value = 43
left node |
right node |
pointer |
pointer |
Value = 437
left node |
right node |
pointer |
pointer |
Node |
|
|
|
|
|
|
Node |
|
Node |
|
Value = 17 |
|
|
Value = 57 |
|
Value = 766 |
|||||
|
|
|
|
|
|
|
|
|
||
null |
right node |
|
null |
right node |
|
null |
null |
|||
pointer |
|
pointer |
|
|||||||
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Node |
|
|
|
|
Node |
|
|
|
Value = 24 |
|
|
Value = 88 |
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
|
null |
null |
|
|
null |
null |
|
||
|
|
|
|
|
|
|
|
|
|
|
An Ordered Binary Tree
Figure 9-7
5.Implement Exercise 4 as a CLR program. If you did not manage to complete Exercise 4, look at the solution in the download and use that as a guide to doing this exercise.
6.Define a generic BinaryTree class for any type that implements the IComparable interface class and demonstrate its operation by using instances of the generic class to store and retrieve first a number of random integers, and then the elements of the following array:
array<String^>^ words = {L”Success”, L”is”, L”the”, L”ability”, L”to” , L”go” , L”from”, L”one”, L”failure”, L”to”,
L”another”, L”with”, L”no”, L”loss”, L”of”,
L”enthusiasm”};
7.Write the values retrieved from the binary tree to the command line.
564

10
Debugging Techniques
If you have been doing the exercises in the previous chapters, you have most likely been battling with bugs in your code. In this chapter you will explore how the basic debugging capabilities built into Visual C++ 2005 can help with this. You will also investigate some additional tools that you can use to find and eliminate errors from your programs, and see some of the ways in which you can equip your programs with specific code to check for errors.
In this chapter, you will learn about:
How to run your program under the control of the Visual C++ 2005 debugger
How to step through your program a statement at a time
How to monitor or change the values of variables in your programs
How to monitor the value of an expression in your program
The call stack
Assertions and how to use them to check your code
How to add debugging specific code to a program
How to detect memory leaks in a native C++ program
How to use the execution tracing facilities and generate debugging output in C++/CLI programs
Understanding Debugging
Bugs are errors in your program and debugging is the process of finding and eliminating them. You are undoubtedly aware by now that debugging is an integral part of the programming process — it goes with the territory as they say. The facts about bugs in your programs are rather depressing:

Chapter 10
Every program you write that is more than trivial will contain bugs that you need to try to expose, find, and eliminate if your program is to be reliable and effective. Note the three phases here — a program bug is not necessarily apparent; even when it is apparent you may not know where it is in your source code; and even when you know roughly where it is, it may not be easy to determine what exactly is causing the problem and thus eliminate it.
Many programs that you write will contain bugs even after you think you have fully tested them.
Program bugs can remain hidden in a program that is apparently operating correctly — sometimes for years. They generally become apparent at the most inconvenient moment.
Programs beyond a certain size and complexity always contain bugs, no matter how much time and effort you expend testing them. (The measure of size and complexity that guarantees the presence of bugs is not precisely defined, but Visual C++ 2005 and your operating system certainly come into this category!)
It is unwise to dwell on this last point if you are of a nervous disposition, especially if you fly a lot or regularly are in the vicinity of any process dependent on computers for proper operation can be damaging to your health in the event of failure.
Many potential bugs are eliminated during the compile and link phases, but there are still quite a few left even after you manage to produce an executable module for your program. Unfortunately, despite the fact that program bugs are as inevitable as death and taxes, debugging is not an exact science; however, you can still adopt a structured approach to eliminating bugs. There are four broad strategies you can adopt to make debugging as painless as possible:
Don’t re-invent the wheel. Understand and use the library facilities provided as part of Visual C++ 2005 (or other commercial software components you have access to) so that your program uses as much pre-tested code as possible.
Develop and test your code incrementally. By testing each significant class and function individually, and gradually assembling separate code components after testing them, you can make the development process much easier, with fewer obscure bugs occurring along the way.
Code defensively — which means writing code to guard against potential errors. For example, declare member functions of native C++ classes that don’t modify an object as const. Use const parameters where appropriate. Don’t use ‘magic numbers’ in your code — define const objects with the required values.
Include debugging code that checks and validates data and conditions in your program from the outset. This is something you will look at in detail later in this chapter.
Because of the importance of ending up with programs that are as bug-free as is humanly possible, Visual C++ 2005 provides you with a powerful armory of tools for finding bugs. Before you get into the detailed mechanics, however, look a little closer at how bugs arise.
566

Debugging Techniques
Program Bugs
Of course, the primary originator of bugs in your program is you and the mistakes you make. These mistakes range from simple typos — just pressing the wrong key — to getting the logic completely wrong.
I, too, find it hard to believe that I can make such silly mistakes so often, but no one has yet managed to come up with a credible alternative as to how bugs get into your code — so it must be true! Humans are creatures of habit so you will probably find yourself making some mistakes time and time again. Frustratingly, many errors are glaringly obvious to others, but invisible to you — this is just your computer’s way of teaching you a bit of humility. Broadly there are two kinds of errors you can make in your code that result in program bugs:
Syntactic errors — These are errors that you result from statements that are not of the correct form; for example, if you miss a semicolon from the end of a statement or use a colon where you should put a comma. You don’t have to worry too much about syntactic errors. The compiler recognizes all syntactic errors, and you generally get a fairly good indication of what the error is so it’s easy to fix.
Semantic errors — These are errors where the code is syntactically correct, but it does not do what you intended. The compiler cannot know what you intended to achieve with your program, so it cannot detect semantic errors; however, you will often get an indication that something is wrong because the program terminates abnormally. The debugging facilities in Visual C++ 2005 are aimed at helping you find semantic errors. Semantic errors can be very subtle and difficult to find, for example, where the program occasionally produces the wrong results or crashes infrequently. Perhaps the most difficult of such bugs arise in multi-threaded programs where concurrent paths of execution are not managed properly.
Of course, there are bugs in the system environment that you are using (Visual C++ 2005 included) but this should be the last place you suspect when your program doesn’t work. Even when you do conclude that it must be the compiler or the operating system, nine times out of ten you will be wrong. There are certainly bugs in Visual C++ 2005, however, and if you want to keep up with those identified to date, together with any fixes available, you can search the information provided on the Microsoft web site related to Visual C++ (http://msdn.microsoft.com/visualc/), or better still, if you can afford a subscription to Microsoft Developer Network, you get quarterly updates on the latest bugs and fixes.
It can be helpful to maker a checklist of bugs you find in your code for future reference. By examining new code that you write for the kinds of errors you have made in the past, you can often reduce the time needed to debug new projects.
From the nature of programming, bugs are virtually infinite in their variety, but there are some kinds that are particularly common. You may be well aware of most of these, but take a quick look at them anyway.
Common Bugs
A useful way of cataloguing bugs is to relate them to the symptoms they cause because this is how you experience them in the first instance. The following list of five common symptoms is by no means exhaustive, and you are certainly able to add to it as you gain programming experience:
567

Chapter 10
Symptom |
Possible Causes |
|
|
Data corrupted |
Failure to initialize variable |
|
Exceeding integer type range |
|
Invalid pointer |
|
Error in array index expression |
|
loop condition error |
|
Error in size of dynamically allocated array. |
|
Failing to implement class copy constructor, assignment operator, |
|
or destructor. |
Unhandled exceptions |
Invalid pointer or reference |
|
Missing catch handler |
Program hangs or crashes |
Failure to initialize variable |
|
Infinite loop |
|
Invalid pointer |
|
Freeing the same free store memory twice |
|
Failure to implement, or error in, class destructor |
Stream input data incorrect |
Reading using the extraction operator and the getline() function. |
Incorrect results |
Typographical error: -= instead of ==, or i instead of j etc. |
|
Failure to initialize a variable |
|
Exceeding the range of an integer type |
|
Invalid pointer |
|
Omitting break in a switch statement |
|
|
Look at how many different kinds of errors can be caused by invalid pointers and the myriad symptoms that bad pointers can generate. This is possibly the most frequent cause of those bugs that are hard to find, so always double-check your pointer operations. If you are conscious of the ways in which bad pointers arise, you can avoid many of the pitfalls. The common ways in which bad pointers arise are:
Failing to initialize a pointer when you declare it
Failing to set a pointer to free store memory to null when you delete the space allocated
Returning the address of a local variable from a function
Failing to implement the copy constructor and assignment operator for classes that allocate free store memory
Even if you do all this, there will still be bugs in your code, so look at the tools that Visual C++ 2005 provides to assist debugging.
Basic Debugging Operations
So far, although you have been creating debug versions of the program examples, you haven’t been using the debugger. The debugger is a program that controls the execution of your program in such a way that you can step through the source code one line at a time, or run to a particular point in the
568