780 Thinking in C++ www.BruceEckel.com
much further. A complicated container-class library may cover all
sorts of additional issues, including multithreading, persistence
and garbage collection.
Exercises
Solutions to selected exercises can be found in the electronic document
The Thinking in C++ Annotated
Solution Guide
, available for a small fee from www.BruceEckel.com.
1. Implement the inheritance hierarchy in the
OShape
diagram in this chapter.
2. Modify the result of Exercise 1 from Chapter 15 to use the
Stack
and
iterator
in
TStack2.h
instead of an array of
Shape
pointers. Add destructors to the class hierarchy so
you can see that the
Shape
objects are destroyed when
the
Stack
goes out of scope.
3. Modify
TPStash.h
vector
as an underlying
implementation, create a
Set
template class that accepts
only one of each type of object that you put into it. Make
a nested
iterator
class that supports the “end sentinel”
concept in this chapter. Write test code for your
Set
in
main( )
, and then substitute the Standard C++ Library
set
template to verify that the behavior is correct.
16: Introduction to Templates 781
7. Modify
AutoCounter.h
so that it can be used as a
member object inside any class whose creation and
destruction you want to trace. Add a
string
member to
hold the name of the class. Test this tool inside a class of
your own.
8. Create a version of
OwnerStack.h
vector
instead of a
Stack
in
main( )
. Notice
the run-time behavior: Does the
vector
automatically
create a bunch of default objects when it is created?
12. Modify
TStack2.h
so that it uses a Standard C++ Library
vector
as its underlying implementation. Make sure that
you don’t change the interface, so that
TStack2Test.cpp
works unchanged.
13. Repeat Exercise 12 using a Standard C++ Library
stack
instead of a
vector
(you may need to look up information
about the
stack
, or hunt through the
<stack>
header file).
, instantiate
Stack
and
PStash
containers for
Shape*
, fill them each
with an assortment of upcast
Shape
pointers, then use
iterators to move through each container and call
draw( )
for each object.
17. Templatize the
Int
class in
TPStash2Test.cpp
so that it
holds any type of object (feel free to change the name of
the class to something more appropriate).
18. Templatize the
IntArray
class in
IostreamOperatorOverloading.cpp
from Chapter 12,
templatizing both the type of object that is contained and
the size of the internal array.
19. Turn
ObjContainer
redefine the
push_back( )
and
operator[]
member
functions to accept and produce only
string*
(and
perform the proper casting). Now create a template that
will automatically make a container class to do the same
thing for pointers to any type. This technique is often
used to reduce code bloat from too many template
instantiations.
23. In
TPStash2.h
, add and test an
operator-
to
PStash::iterator
, following the logic of
operator+
.
24. In
Drawing.cpp
, add and test a function template to call
erase( )
member functions.
16: Introduction to Templates 783
25. (Advanced) Modify the
A: Coding Style
This appendix is not about indenting and placement of
parentheses and curly braces, although that will be
mentioned. It is about the general guidelines used in
this book for organizing the code listings.
786 Thinking in C++ www.BruceEckel.com
Although many of these issues have been introduced throughout
the book, this appendix appears at the end so it can be assumed
that every topic is fair game, and if you don’t understand
something you can look it up in the appropriate section.
All the decisions about coding style in this book have been
deliberately considered and made, sometimes over a period of
years. Of course, everyone has their reasons for organizing code the
way they do, and I’m just trying to tell you how I arrived at mine
and the constraints and environmental factors that brought me to
those decisions.
General
In the text of this book, identifiers (function, variable, and class
names) are set in
bold
. Most keywords will also be set in bold,
except for those keywords that are used so much that the bolding
cause compile-time error messages are commented out with the
comment
//!
so they can be easily discovered and tested using
automatic means. Errors discovered and reported to the author will
appear first in the electronic version of the book (at
www.BruceEckel.com
) and later in updates of the book.
One of the standards in this book is that all programs will compile
and link without errors (although they will sometimes cause
warnings). To this end, some of the programs, which demonstrate
only a coding example and don’t represent stand-alone programs,
will have empty
main( )
functions, like this
int main() {}
This allows the linker to complete without an error.
The standard for
main( )
is to return an
int
, but Standard C++
states that if there is no
return
statement inside
main( )
, the
compiler will automatically generate code to
return 0
hxx
and
cxx
for header files and implementation files,
respectively, or
hpp
and
cpp
. Later, someone figured out that the
only reason you needed a different extension for a file was so the
compiler could determine whether to compile it as a C or C++ file.
Because the compiler never compiled header files directly, only the
implementation file extension needed to be changed. The custom,
across virtually all systems, has now become to use
cpp
for
implementation files and
h
for header files. Note that when
including Standard C++ header files, the option of having no file
name extension is used, i.e.:
#include <iostream>
.
Begin and end comment tags
A very important issue with this book is that all code that you see
in the book must be verified to be correct (with at least one
compiler). This is accomplished by automatically extracting the
files from the book. To facilitate this, all code listings that are meant
to be compiled (as opposed to code fragments, of which there are
few) have comment tags at the beginning and end. These tags are
to be linked with something else, then it has an
{O}
after the file
name. If this listing is meant to be the main program but needs to
be linked with other components, there’s a separate line that begins
with
//{L}
and continues with all the files that need to be linked
(without extensions, since those can vary from platform to
platform).
You can find examples throughout the book.
If a file should be extracted but the begin- and end-tags should not
be included in the extracted file (for example, if it’s a file of test
data) then the begin-tag is immediately followed by a ‘
!
’.
Parentheses, braces, and indentation
You may notice the formatting style in this book is different from
many traditional C styles. Of course, everyone thinks their own
style is the most rational. However, the style used here has a simple
logic behind it, which will be presented here mixed in with ideas on
why some of the other styles developed.
The formatting style is motivated by one thing: presentation, both
in print and in live seminars. You may feel your needs are different
because you don’t make a lot of presentations. However, working
code is read much more than it is written, and so it should be easy
for the reader to perceive. My two most important criteria are
“scannability” (how easy it is for the reader to grasp the meaning of
a single line) and the number of lines that can fit on a page. This
latter may sound funny, but when you are giving a live
int b = a + 1;
return b * 2;
}
and for a single-line definition that is often used for inlines:
int func(int a) { return (a + 1) * 2; }
Similarly, for a class:
A: Coding Style 791
class Thing;
is a class name declaration, and
class Thing {
is a class definition. You can tell by looking at the single line in all
cases whether it’s a declaration or definition. And of course,
putting the opening brace on the same line, instead of a line by
itself, allows you to fit more lines on a page.
So why do we have so many other styles? In particular, you’ll
notice that most people create classes following the style above
(which Stroustrup uses in all editions of his book
The C++
Programming Language
from Addison-Wesley) but create function
definitions by putting the opening brace on a single line by itself
(which also engenders many different indentation styles).
Stroustrup does this except for short inline functions. With the
approach I describe here, everything is consistent – you name
whatever it is (
will take the user to the next occurrence of ‘{‘ (or ^L) in column
0. This feature is extremely useful in navigating code (jumping
to the next function or class definition). [My comment: when I
was initially working under Unix, GNU Emacs was just
appearing and I became enmeshed in that. As a result, ‘vi’ has
never made sense to me, and thus I do not think in terms of
“column 0 locations.” However, there is a fair contingent of ‘vi’
users out there, and they are affected by this issue.]
Placing the ‘{‘ on the next line eliminates some confusing code
in complex conditionals, aiding in the scannability. Example:
if(cond1
&& cond2
&& cond3) {
statement;
}
The above [asserts the reader] has poor scannability. However,
if (cond1
&& cond2
&& cond3)
{
statement;
}
breaks up the ‘if’ from the body, resulting in better readability.
[Your opinions on whether this is true will vary depending on
what you’re used to.]
A: Coding Style 793
Finally, it’s much easier to visually align braces when they are
C Style: Standards and Guidelines
, by David
Straker (Prentice-Hall 1992).
The other constraint I must work under is the line width, since the
book has a limitation of 50 characters. What happens when
something is too long to fit on one line? Well, again I strive to have
a consistent policy for the way lines are broken up, so they can be
easily viewed. As long as something is part of a single definition,
794 Thinking in C++ www.BruceEckel.com
argument list, etc., continuation lines should be indented one level
in from the beginning of that definition, argument list, etc.
Identifier names
Those familiar with Java will notice that I have switched to using
the standard Java style for all identifier names. However, I cannot
be completely consistent here because identifiers in the Standard C
and C++ libraries do not follow this style.
The style is quite straightforward. The first letter of an identifier is
only capitalized if that identifier is a class. If it is a function or
variable, then the first letter is lowercase. The rest of the identifier
consists of one or more words, run together but distinguished by
capitalizing each word. So a class looks like this:
class FrenchVanilla : public IceCream {
an object identifier looks like this:
FrenchVanilla myIceCreamCone(3);
and a function looks like this:
void eatIceCreamCone();
(for either a member function or a regular function).
interface of the component is missing from the .h file (or, if there is,
that you will find out about it as soon as you try to compile the .c file).
If the order of header inclusion goes “from most specific to most
general,” then it’s more likely that if your header doesn’t parse by
itself, you’ll find out about it sooner and prevent annoyances down
the road.
Include guards on header files
Include guards
are always used inside header files to prevent
multiple inclusion of a header file during the compilation of a
single
.cpp
file. The include guards are implemented using a
preprocessor
#define
and checking to see that a name hasn’t
already been defined. The name used for the guard is based on the
name of the header file, with all letters of the file name uppercase
and replacing the ‘
.
’ with an underscore. For example:
// IncludeGuard.h
#ifndef INCLUDEGUARD_H
#define INCLUDEGUARD_H
// Body of header file here
#endif // INCLUDEGUARD_H
796 Thinking in C++ www.BruceEckel.com
The identifier on the last line is included for clarity. Although some
preprocessors ignored any characters after an
properly report problems. If you are familiar with the concepts of
preconditions
and
postconditions
(introduced by Bertrand Meyer) you
will recognize that the use of
require( )
and
assure( )
more or less
provide preconditions (usually) and postconditions (occasionally).
Thus, at the beginning of a function, before any of the “core” of the
function is executed, the preconditions are checked to make sure
everything is proper and that all of the necessary conditions are
correct. Then the “core” of the function is executed, and sometimes
some postconditions are checked to make sure that the new state of
the data is within defined parameters. You’ll notice that the
postcondition checks are rare in this book, and
assure( )
is
primarily used to make sure that files were opened successfully.
797 B: Programming Guidelines
This appendix is a collection of suggestions for C++
programming. They’ve been assembled over the course
of my teaching and programming experience and
798 Thinking in C++ www.BruceEckel.com
also from the insights of friends including Dan Saks (co-author with
Tom Plum of
C++ Programming Guidelines
, Plum Hall, 1991), Scott
Meyers (author of
Effective C++
, 2
nd
edition, Addison-Wesley, 1998),
and Rob Murray (author of
C++ Strategies & Tactics
, Addison-Wesley,
1993). Also, many of the tips are summarized from the pages of
Thinking in C++
C in C++ is a
valuable activity because it may reveal hidden bugs.
B: Programming Guidelines 799
However, taking C code that works fine and rewriting it in
C++ may not be the best use of your time, unless the C++
version will provide a lot of opportunities for reuse as a class.
5. If you do have a large body of C code that needs changing,
first isolate the parts of the code that will not be modified,
possibly wrapping those functions in an “API class” as static
member functions. Then focus on the code that will be
changed, refactoring it into classes to facilitate easy
modifications as your maintenance proceeds.
6. Separate the class creator from the class user (
client
programmer
). The class user is the “customer” and doesn’t
need or want to know what’s going on behind the scenes of
the class. The class creator must be the expert in class design
and write the class so that it can be used by the most novice
programmer possible, yet still work robustly in the
application. Library use will be easy only if it’s transparent.
7. When you create a class, make your names as clear as
possible. Your goal should be to make the client
programmer’s interface conceptually simple. Attempt to
make your names so clear that comments are unnecessary. To
this end, use function overloading and default arguments to
create an intuitive, easy-to-use interface.
8. Access control allows you (the class creator) to change as
much as possible in the future without damaging client code
will cost you. Members of development teams tend not to
maintain anything that does not contribute to their
productivity; this is a fact of life that many design methods
don’t account for.
11. Write the test code first (before you write the class), and keep
it with the class. Automate the running of your tests through
a makefile or similar tool. This way, any changes can be
automatically verified by running the test code, and you’ll
immediately discover errors. Because you know that you
have the safety net of your test framework, you will be bolder
about making sweeping changes when you discover the
need. Remember that the greatest improvements in
languages come from the built-in testing that type checking,
exception handling, etc., provide, but those features take you
only so far. You must go the rest of the way in creating a
robust system by filling in the tests that verify features that
are specific to your class or program.
12. Write the test code first (before you write the class) in order
to verify that your class design is complete. If you can’t write
test code, you don’t know what your class looks like. In
addition, the act of writing the test code will often flush out
additional features or constraints that you need in the class –
B: Programming Guidelines 801
these features or constraints don’t always appear during
analysis and design.
13. Remember a fundamental rule of software engineering
1
:
All
1
Explained to me by Andrew Koenig.
802 Thinking in C++ www.BruceEckel.com
18. Watch for
switch
statements or chained
if-else
clauses. This
is typically an indicator of
type-check coding
, which means you
are choosing what code to execute based on some kind of
type information (the exact type may not be obvious at first).
You can usually replace this kind of code with inheritance
and polymorphism; a polymorphic function call will perform
the type checking for you, and allow for more reliable and
easier extensibility.
19. From a design standpoint, look for and separate things that
change from things that stay the same. That is, search for the
elements in a system that you might want to change without
forcing a redesign, then encapsulate those elements in
classes. You can learn significantly more about this concept in
the Design Patterns chapter in Volume 2 of this book,
available at
www.BruceEckel.com
.
20. Watch out for
variance
. Two semantically different objects
may have identical actions, or responsibilities, and there is a
be used. As the class is used, you’ll discover ways you must
expand the interface. However, once a class is in use you
cannot shrink the interface without disturbing client code. If
you need to add more functions, that’s fine; it won’t disturb
code, other than forcing recompiles. But even if new member
functions replace the functionality of old ones, leave the
existing interface alone (you can combine the functionality in
the underlying implementation if you want). If you need to
expand the interface of an existing function by adding more
arguments, leave the existing arguments in their current
order, and put default values on all of the new arguments;
this way you won’t disturb any existing calls to that function.
24. Read your classes aloud to make sure they’re logical,
referring to the relationship between a base class and derived
class as “is-a” and member objects as “has-a.”
25. When deciding between inheritance and composition, ask if
you need to upcast to the base type. If not, prefer
composition (member objects) to inheritance. This can
eliminate the perceived need for multiple inheritance. If you
inherit, users will think they are supposed to upcast.
26. Sometimes you need to inherit in order to access
protected
members of the base class. This can lead to a perceived need
for multiple inheritance. If you don’t need to upcast, first
derive a new class to perform the protected access. Then
804 Thinking in C++ www.BruceEckel.com
make that new class a member object inside any class that
needs to use it, rather than inheriting.
27. Typically, a base class will be used primarily to create an
30. If you must do something nonportable, make an abstraction
for that service and localize it within a class. This extra level
of indirection prevents the non-portability from being
distributed throughout your program.
31. Avoid multiple inheritance. It’s for getting you out of bad
situations, especially repairing class interfaces in which you
don’t have control of the broken class (see Volume 2). You
should be an experienced programmer before designing
multiple inheritance into your system.
32. Don’t use
private
inheritance. Although it’s in the language
and seems to have occasional functionality, it introduces