Linux
by Rick McMullin
IN THIS CHAPTER
- What Is C++?
- Debugging C++ Applications 517
n GNU C++ Class Libraries 520 Chapter 27, "Programming in C," introduced
you to the C programming environment and C programming tools that come with Linux.
This chapter describes the same kinds of information for C++. This chapter covers
the following topics:
- What C++ is
- Why to use C++
- The GNU C++ compiler
- Debugging C++ applications
In addition to these topics, this chapter also looks at some of the C++ programming
tools and class libraries that are included on the Linux CD-ROM.
C++ is an object-oriented extension to the C programming language. It was developed
at Bell Labs in the early 1980s and is quickly becoming the language of choice in
the computer industry. Dozens of C++ compilers are available on the market today.
The most common of these for PC-based systems are Borland C++, Microsoft's Visual
C++, Zortech C++, and Watcom C++. These compilers compile MS DOS and MS Windows applications,
and some of them compile code to run on OS/2 and Windows NT as well. In addition
to the number of C++ compilers that are available on DOS-based machines, a great
number are also based on other hardware architectures.
Most UNIX systems have C++ compilers available from the system vendor. Linux also
comes with a C++ compiler. This is the GNU C++ compiler. The GNU C++ compiler is
very closely related to the GNU C compiler (GCC). In fact, since Release 2.0 of GCC,
the GNU C++ compiler has been integrated with GCC. Previous to Release 2.0 of GCC,
the GNU C++ compiler was a separate program known as g++. One of the major
enhancements in Release 2.0 of GCC was merging these two compilers.
GCC now incorporates a C compiler, a C++ compiler, and an Objective C compiler.
You will still find the g++ executable on your system, but it is now a script file
that calls GCC with all the standard C++ options.
C++ and object-oriented programming (OOP) did not just happen. There were many
funda- mental reasons for the shift from structured programming to OOP. In the early
days of computer programming, back when PDP-8s still roamed the earth in great numbers,
there was a shift from machine language coding to assembler language coding. This
was done because the computers of the day were a little more powerful than their
predecessors. Programmers wanted to make their lives easier by moving some of the
burden of programming onto the computer.
As the years went by and computers got even more powerful, new, higher-level languages
started to appear. Examples of these languages are FORTRAN, COBOL, Pascal, and C.
With these languages came a programming methodology known as structured programming.
Structured programming helped to simplify the systems being designed by enabling
programmers to break the problem into small pieces and then implement these pieces
as functions or procedures in whatever language was being used.
The structured programming approach worked well for small to medium-sized software
applications, but it started to fall apart as systems reached a certain size. OOP
tried to solve some of the problems that structured programming was causing. It did
this by extending some of the structured programming concepts and by introducing
some of its own.
The main concepts that OOP focuses on are the following:
- Data encapsulation
- Inheritance
- Polymorphism
Data Encapsulation In structured programming, problems often arose where there
was a data structure that was common to several different pieces of code. One piece
of code could access that data without the other piece of code being aware that anything
was happening.
Data encapsulation is a process of grouping common data together, storing it into
a data type, and providing a consistent interface to that data. This ensures that
no one can access that data without going through the user interface that has been
defined for that data.
The biggest benefit that this kind of mechanism provides is that it protects code
outside the code that is directly managing this data from being affected if the structure
of the data changes. This greatly reduces the complexity of large software systems.
C++ implements data encapsulation through the use of classes. Inheritance Inheritance
is a form of code reuse in which you can inherit or use the data and behavior of
other pieces of code. Inheritance is typically used only when a piece of software
logically has many of the same characteristics as another piece of software, such
as when one object is a specialization of another object.
Inheritance is implemented in C++ by allowing objects to be subclassed by other
objects. Polymorphism Polymorphism occurs when a language allows you to define functions
that perform different operations on objects depending on their type. The true power
of this lies in the fact that you can send a message to a base class and that message
can be passed down to each of its subclasses and mean different things to each of
them.
Polymorphism is implemented in C++ using virtual functions.
In C++, classes can be thought of as C structures that contain not only the data
fields but also operations that can be performed on those data fields. A simple example
of this concept is a geometric shape. A geometric shape can be many things, such
as a rectangle, a triangle, or a circle. All geometric shapes have certain attributes
in common, including area and volume. You could define a structure in C called shape
in the following way:
struct shape{
float area;
float volume;
}
If you added some common behavior to this structure, you would have the equivalent
of a C++ class. This would be written as follows:
class shape {
public:
float area;
float volume;
float calc_area();
float calc_volume():
};
You have now defined a C++ class. The calc_area and calc_volume
items are known as methods of the class (instead of functions, as in C). If you were
to define a variable that was of type shape as
shape circle;
you would have created a circle object. An object is an instance of a class, or
a variable that is defined to be of the type of a class.
This section describes some of the GCC command-line options that are most commonly
used. I will first talk about some of the options that can be used both with C and
C++ and then talk about C++ specific options. Any of the compiler options that you
use with C you can use with C++ as well, but some of them may not make any sense
in the context of a C++ compile. If you specify options that don't make sense, the
compiler just ignores them.
-
NOTE: When you are compiling
C++ programs, it is easiest to use the g++ script. This sets all the default
C++ options so you don't have to.
A great number of compiler options can be passed to GCC. Many of these options
are specific to a certain hardware platform or are for making fine-tuning adjustments
to the code that is produced. You will probably never use any of these kinds of options.
The options covered in this chapter are those that you will use on a regular basis.
Many of the GCC options consist of more than one character. For this reason, you
must specify each option with its own hyphen and not group options after a single
hyphen as you can with most Linux commands.
When you compile a program using GCC without any command-line options, it creates
an executable file (assuming that the compile was successful) and calls it a.out.
For example, the following command would create a file named a.out in the
current directory:
gcc test.C
To specify a name other than a.out for the executable file, you can use
the -o compiler option. For example, to compile a C++ program file named
count.C (the capital C is used to show C++ code, as opposed to
a small c for C code) into an executable file named count, you
would type the following command:
gcc -o count count.C
-
NOTE: When you are using
the -o option, the executable filename must occur directly after the -o
on the command line.
Other compiler options allow you to specify how far you want the compile to proceed.
The -c option tells GCC to compile the code into object code and skip the
assembly and linking stages of the compile. This option is used quite often because
it makes the compilation of multifile C++ programs faster and easier to manage. Object
code files created by GCC have an .o extension by default.
The -S compiler option tells GCC to stop the compile after it has generated
the assembler files for the C code. Assembler files generated by GCC have an .s
extension by default. The -E option instructs the compiler to perform only
the preprocessing compiler stage on the input files. When this option is used, the
output from the preprocessor is sent to the standard output rather than being stored
in a file.
GCC supports several debugging and profiling options. Of these options, the two
that you are most likely to use for C++ programs are the -gstabs+ option
and the -pg option.
The -gstabs+ option tells GCC to produce stabs format debugging information
that the GNU debugger (gdb) can use to help you debug your program. For
more information on debugging your C++ programs, refer to the "Debugging C++
Applications" section on the next page.
The -pg option tells GCC to add extra code to your program that will,
when executed, generate profile information that can be used by the gprof
program to display timing information about your program. For additional information
on gprof, refer to the "gprof" section in Chapter 27.
The GCC options that control how a C++ program is compiled are listed in Table
28.1.
Table 28.1. GCC options.
Option |
Meaning |
-fall-virtual |
Treats all possible member functions as virtual. This applies to all functions except
for constructor functions and new or deleted member functions. |
-fdollars-in-identifiers |
Accepts $ in identifiers. You can also prohibit the use of $ in identifiers by using
the -fno-dollars-in-identifiers option. |
-felide-constructors |
Tells the compiler to leave out constructors whenever possible. |
-fenum-int-equiv |
Permits implicit conversion of int to enumeration types. |
-fexternal-templates |
Produces smaller code for template declarations. This is done by having the compiler
generate only a single copy of each template function where it is defined. |
-fmemorize-lookups |
Uses heuristics to compile faster. These heuristics are not enabled by default because
they are effective only for certain input files. |
-fno-strict-prototype |
Treats a function declaration with no arguments the same way that C would treat it.
This means that the compiler treats a function prototype that has no arguments as
a function that will accept an unknown number of arguments. |
-fno-null-objects |
Assumes that objects reached through references are not null. |
-fsave-memorized |
Same as -fmemorize-lookups. |
-fthis-is-variable |
Permits assignment to "this." |
-nostdinc++ |
Does not search for header files in the standard directories specific to C++. |
-traditional |
This option has the same effect as -fthis-is-variable. |
-fno-default-inline |
Does not assume that functions defined within a class scope are inline functions. |
-wenum-clash |
Warns about conversion between different enumeration types. |
-woverloaded-virtual |
Warns when derived class function declaration may be an error in defining a virtual
function. When you define a virtual function in a derived class, it must have the
same signature as the function in the base class. This option tells the compiler
to warn you if you have defined a function that has the same name and a different
signature as a function that is defined in one of the base classes. |
-wtemplate-debugging |
If you are using templates, this option warns you if debugging is not yet available. |
+eN |
Controls how virtual function definitions are used. |
-gstabs+ |
Tells the compiler to generate debugging information in stabs format, using GNU extensions
understood only by the GNU debugger. The extra information produced by this option
is necessary to ensure that gdb handles C++ programs properly. |
A very important part of developing C++ programs is being able to debug them efficiently.
The GNU debug application that was introduced in Chapter 27 can also be used to debug
C++ applications. This section describes some of the differences between debugging
C applications and debugging C++ applications.
The basic gdb commands that were introduced in Chapter 27 are listed
again for your convenience in Table 28.2.
Table 28.2. Basic gdb commands.
Command |
Description |
file |
Loads the executable file that is to be debugged. |
kill |
Terminates the program you are currently debugging. |
list |
Lists sections of the source code used to generate the executable file. |
next |
Advances one line of source code in the current function, without stepping into other
functions. |
step |
Advances one line of source code in the current function, and does step into other
functions. |
run |
Executes the program that is currently being debugged. |
quit |
Terminates gdb. |
watch |
Enables you to examine the value of a program variable whenever the value changes. |
break |
Sets a breakpoint in the code; this causes the execution of the program to be suspended
whenever this point is reached. |
make |
Enables you to remake the executable program without quitting gdb or using
another window. |
shell |
Enables you to execute UNIX shell commands without leaving gdb. |
From the programmer's perspective, you have more details to be aware of when debugging
C++ code than when you are debugging C code. This is because of the C++ features
such as virtual functions and exception handling. gdb has added features
to support debugging both of these C++ specific features.
As described in the "Polymorphism" section, earlier in this chapter,
virtual functions are C++'s way of implementing polymorphism. This means that there
may be more than one function in a program with the same name. The only way to tell
these functions apart is by their signatures. The signature of a function is composed
of the types of all the arguments to the function. For example, a function with the
prototype
void func(int, real);
has a signature of int,real.
You can see how this could cause the gdb a few problems. For example,
if you had defined a class that had a virtual function called calculate,
and two objects with different definitions for this function were created, how would
you set a breakpoint to trigger on this function? You set breakpoints in C by specifying
the function name as an argument to the gdb break command, as follows:
(gdb) break calculate
This does not work in the case of a virtual function because the debugger would
not be able to tell which calculate you wanted the breakpoint to be set
on. gdb was extended in a few ways so that it could handle virtual functions.
The first way to solve the problem is to enter the function name by specifying its
prototype as well. This would be done in the following way:
break `calculate (float)'
This would give gdb enough information to determine which function the
breakpoint was meant for. A second solution that gdb supports is using a
breakpoint menu. Breakpoint menus allow you to specify the function name of a function.
If there is more than one function definition for that function, it gives you a menu
of choices. The first choice in the menu is to abort the break command.
The second choice is to set a breakpoint on all the functions that the break
command matches. The remaining choices correspond to each function that matches the
break command. The following code shows an example of a breakpoint menu:
(gdb) break shape::calculate
[0] cancel
[1] all
[2] file: shapes.C: line number: 153
[3] file: shapes.C: line number: 207
[4] file: shapes.C: line number: 247
> 2 3
Breakpoint 1 at 0xb234: file shapes.C, line 153
Breakpoint 2 at 0xa435: file shapes.C, line 207
Multiple breakpoints were set
Use the "delete" command to delete unwanted breakpoints
(gdb)
Exceptions are errors that occur within your program. Exception handlers are pieces
of code that are written to handle errors and potential errors. For example, if you
were writing a C program and calling the malloc function to get a block
of memory, you would typically check malloc's return code to make sure the
memory allocation was successful. If C supported exception handling, you could specify
a function that would receive or catch exceptions, and the malloc function
would send or throw an exception to your function if one occurred.
The gdb added two new commands to support C++ exception handling: the
catch command and the catch info command. The catch command
is used to set a breakpoint in active exception handlers. The syntax of this command
is as follows:
catch exceptions
exceptions is a list of the exceptions to catch.
The catch info command is used to display all the active exception handlers.
In addition to the gdb commands that have been added to support some
of the new language features contained in C++, there are also some new set
and show options. These options are listed in Table 28.3.
Table 28.3. gdb's C++ set and show options.
Command |
Description |
set print demangle |
Prints C++ names in their source form rather than in the encoded or mangled form
that is passed to the assembler. |
show print demangle |
Shows whether print demangle is on or off. |
set demangle-style |
Sets the style of demangled output. The options are auto, gnu,
lucid, and arm. |
show demangle-style |
Shows which demangle style is being used. |
set print object |
When displaying a pointer to an object, identifies the actual type of the object. |
show print object |
Shows whether print object is turned on or off. |
set print vtbl |
Pretty prints C++ virtual function tables. |
show print vtbl |
Shows whether print vtbl is turned on or off. |
GNU C++ comes packaged with an extensive class library. A class library is a reusable
set of classes that can be used to perform a specified set of functions. Some typical
examples of class libraries are class libraries that handle database access, class
libraries that handle graphical user interface programming, and class libraries that
implement data structures.
Examples of graphical user interface class libraries include the Microsoft Foundation
Classes and Borland's Object Windows Library, both of which are class libraries that
are used for developing Windows applications.
This section introduces several of the features that are offered by the GNU C++
class library.
The GNU iostream library, called libio, implements GNU C++'s standard
input and output facilities. This library is similar to the I/O libraries that are
supplied by other C++ compilers. The main parts of the iostream library are the input,
output, and error streams. These correspond to the standard input, output, and error
streams that are found in C and are called cin, cout, and cerr,
respectively. The streams can be written to and read from using the <<
operator for output and the >> operator for input.
The following program uses the iostream library to perform its input and output:
#include <iostream.h>
int maim ()
{
char name[10];
cout << "Please enter your name.\n";
cin >> name;
cout << "Hello " << name << " how is it going?\n";
}
The GNU string class extends GNU C++'s string manipulation capabilities. The string
class essentially replaces the character array definitions that existed in C and
all the string functions that go along with the character arrays.
The string class adds UNIX shell type string operators to the C++ language, as
well as a large number of additional operators. Table 28.4 lists many of the operators
that are available with the string class.
Table 28.4. String class operators.
Operator |
Meaning |
str1 == str2 |
Returns TRUE if str1 is equal to str2 |
str1 != str2 |
Returns TRUE if str1 is not equal to str2 |
str1 < str2 |
Returns TRUE if str1 is less than str2 |
str1 <= str2 |
Returns TRUE if str1 is less than or equal to str2 |
str1 > str2 |
Returns TRUE if str1 is greater than str2 |
str1 >= str2 |
Returns TRUE if str1 is greater than or equal to str2 |
compare(str1,str2) |
Compares str1 to str2 without considering the case of the
characters |
str3 = str1 + str2 |
Stores the result of str1 concatenated with str2 into str3 |
A number of other operators are available in the string class for performing different
types of string comparisons, concatenations, and substring extraction and manipulation.
Classes are provided in the GCC C++ class library that allow you to generate several
different kinds of random numbers. The classes used to generate these numbers are
the Random class and the RNG class.
The class library provides two different classes that perform data collection
and analysis functions. The two classes are SampleStatistic and SampleHistogram.
The SampleStatistic class provides a way of collecting samples and also
provides numerous statistical functions that can perform calculations on the collected
data. Some of the calculations that can be performed are mean, variance, standard
deviation, minimum, and maximum.
The SampleHistogram class is derived from the SampleStatistic
class and supports the collection and display of samples in bucketed intervals.
The GNU C++ library supports two kinds of linked lists: single linked lists, implemented
by the SLList class, and doubly linked lists, implemented by the DLList
class. Both of these types of lists support all the standard linked list operations.
A summary of the operations that these classes support is shown in Table 28.5.
Table 28.5. List operators.
Operator |
Description |
list.empty() |
Returns TRUE if list is empty |
list.length() |
Returns the number of elements in list |
list.prepend(a) |
Places a at the front of list |
list.append(a) |
Places a at the end of list |
list.join(list2) |
Appends list2 to list, destroying list2 in the process |
a = list.front() |
Returns a pointer to the element that is stored at the head of the list |
a = list.rear() |
Returns a pointer to the element that is stored at the end of the list |
a = list.remove_front() |
Deletes and returns the element that is stored at the front of the list |
list.del_front() |
Deletes the first element without returning it |
list.clear() |
Deletes all items from list |
list.ins_after(i, a) |
Inserts a after position i in the list |
list.del_after(i) |
Deletes the element following position i in the list |
Doubly linked lists also support the operations listed in Table 28.6.
Table 28.6. Doubly linked list operators.
Operator |
Description |
a = list.remove_rear() |
Deletes and returns the element stored at the end of the list |
list.del_real() |
Deletes the last element, without returning it |
list.ins_before(i, a) |
Inserts a before position i in the list |
list.del(i, dir) |
Deletes the element at the current position and then moves forward one position if
dir is positive and backward one position if dir is 0 or negative |
Plex classes are classes that behave like arrays but are much more powerful. Plex
classes have the following properties:
- They have arbitrary upper and lower index bounds.
- They can dynamically expand in both the lower and upper bound directions.
- Elements may be accessed by indices. Unlike typical arrays, bounds checking is
performed at runtime.
- Only elements that have been specifically initialized or added can be accessed.
Four different types of Plexes are defined: the FPlex, the XPlex,
the RPlex, and the MPlex. The FPlex is a Plex that can
grow or shrink only within declared bounds. An XPlex can dynamically grow
in any direction without any restrictions. An RPlex is almost identical
to an XPlex, but it has better indexing capabilities. Finally, the MPlex
is the same as an RPlex except that it allows elements to be logically deleted
and restored.
Table 28.7 lists some of the operations that are valid on all four of the Plexes.
Table 28.7. Operations defined for Plexes.
Operation |
Description |
Plex b(a) |
Assigns a copy of Plex a to Plex b |
b = a |
Copies Plex a into b |
a.length() |
Returns the number of elements in a |
a.empty() |
Returns TRUE if a has no elements |
a.full() |
Returns TRUE if a is full |
a.clear() |
Removes all the elements from a |
a.append |
Appends Plex b to the high part of a |
a.prepend |
Prepends Plex b to the low part of a |
a.fill(z) |
Sets all elements of a equal to z |
a.valid(i) |
Returns TRUE if i is a valid index into a |
a.low_element() |
Returns a pointer to the element in the lowest position in a |
a.high_element() |
Returns a pointer to the element in the highest position in a |
Plexes are a very useful class on which many of the other classes in the GNU C++
class library are based. Some of the Stack, Queue, and Linked list types are built
on top of the Plex class.
The Stack class implements the standard version of a last-in-first-out (LIFO)
stack. Three different implementations of stacks are offered by the GNU C++ class
library: the VStack, the XPStack, and the SLStack. The
VStack is a fixed-size stack, meaning that you must specify an upper bound
on the size of the stack when you first create it. The XPStack and the SLStack
are both dynamically sized stacks that are implemented in a slightly different way.
Table 28.8 lists the operations that can be performed on the Stack classes.
Table 28.8. Stack class operators.
Operator |
Description |
Stack st |
Declares st to be a stack |
Stack st(sz) |
Declares st to be a stack of size sz |
st.empty() |
Returns TRUE if stack is empty |
st.full() |
Returns TRUE if stack is full |
st.length() |
Returns the number of elements in stack |
st.push(x) |
Puts element x onto the top of the stack |
x = st.pop() |
Removes and returns the top element from the stack |
st.top() |
Returns a pointer to the top element in the stack |
st.del_top() |
Deletes the top element from the stack without returning it |
st.clear() |
Deletes all elements from stack |
The Queue class implements a standard version of a first-in-first-out (FIFO) queue.
Three different kinds of queue are provided by the GNU C++ class library: the VQueue,
the XPQueue, and the SLQueue. The VQueue is a fixed-size
queue, so you must specify an upper bound on the size of this kind of queue when
you first create it. The XPQueue and the SLQueue are both dynamically
sized queues, so no upper bound is required. The operations supported by the Queue
classes are listed in Table 28.9.
Table 28.9. Queue class operators.
Operator |
Description |
Queue q |
Declares q to be a queue |
Queue q(sz) |
Declares q to be a queue of size sz |
q.empty() |
Returns TRUE if q is empty |
q.full() |
Returns TRUE if q is full |
q.length() |
Returns the number of elements in q |
q.enq(x) |
Adds the x element to q |
x = q.deq() |
Removes and returns an element from q |
q.front() |
Returns a pointer to the front of q |
q.del_front() |
Removes an element from q and does not return the result |
q.clear |
Removes all elements from the queue |
In addition to the normal kind of queue that is discussed in this section, the
GNU C++ class library also supports double-ended queues and priority queues. Both
of these types of queues have similar behavior to the regular queue. The double-ended
queue adds operators for returning a pointer to the rear of the queue and deleting
elements from the rear of the queue. The priority queues are arranged so that a user
has fast access to the least element in the queue. They support additional operators
that allow for searching for elements in the queue.
The Set class is used to store groups of information. The only restriction on
this information is that no duplicate elements are allowed. The class library supports
several different implementations of sets. All of the implementations support the
same operators. These operators are shown in Table 28.10.
Table 28.10. Set operators.
Operator |
Description |
Set s |
Declares a set named s that is initially empty |
Set s(sz) |
Declares a set named s that is initially empty and has a set maximum size
of sz |
s.empty() |
Returns TRUE if s is empty |
s.length() |
Returns the number of elements in s |
i = s.add(z) |
Adds z to s, returning its index value |
s.del(z) |
Deletes z from s |
s.clear() |
Removes all elements from s |
s.contains(z) |
Returns TRUE if z is in s |
s.(i) |
Returns a pointer to the element indexed by i |
i = a.first() |
Returns the index of the first item in the set |
s.next(i) |
Makes i equal to the index of the next element in s |
i = s.seek(z) |
Sets i to the index of z if z is in s, and 0
otherwise |
set1 == set2 |
Returns TRUE if set1 contains all the same elements as set2 |
set1 != set2 |
Returns TRUE if set1 does not contain all the same elements as
set2 |
set1 <= set2 |
Returns TRUE if set1 is a subset of set2 |
set1 |= set2 |
Adds all elements of set2 to set1 |
set1 -= set2 |
Deletes all the elements that are contained in set2 from set1 |
set1 &= set2 |
Deletes all elements from set1 that occur in set1 and not in set2 |
The class library contains another class that is similar to sets. This class is
known as the bag. A bag is a group of elements that can be in any order (just as
is the case with sets) but in which there can also be duplicates. Bags use all the
operators that sets use except for the ==, !=, |=, <=,
|=, -=, and &= operators. In addition, bags add two
new operators for dealing with elements that are in the bag more than once. These
new operators are shown in Table 28.11.
Table 28.11. Additional operators for bags.
Operator |
Description |
b.remove(z) |
Deletes all occurrences of z from b |
b.nof(z) |
Returns the number of occurrences of z that are in b |
Many other classes available in the GNU C++ class library provide functions other
than those listed here. In addition to what comes with the compiler, many other freely
available class libraries can be useful as well.
C++ offers many advantages over C. Some of these advantages come from the concepts
of object-oriented programming, and others come from the highly flexible class libraries
that are available to C++ programmers. This chapter gave a brief introduction to
object-oriented programming and also talked about the C++ features that exist in
the GNU C compiler and the GNU debugger.
One problem that has existed with C++ for quite some time is the lack of freely
available C++ development tools. You may notice that the number of free tools available
for C++ programming is much smaller than the number available for C, but the tide
is turning. As more and more people choose C++ over C, the number of tools and class
libraries available keeps increasing. The tool support has reached the stage where
learning C++ in the Linux environment is something that you can enjoy rather than
avoid.
Contact
[email protected] with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.