Tải bản đầy đủ (.pdf) (86 trang)

thinking in c volume 1 2nd edition phần 10 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (350.97 KB, 86 trang )

772 Thinking in C++ www.BruceEckel.com
location as the existing iterator that you create it from, effectively
making a bookmark into the container. The
operator+=
and
operator-=
member functions allow you to move an iterator by a
number of spots, while respecting the boundaries of the container.
The overloaded increment and decrement operators move the
iterator by one place. The
operator+
produces a new iterator that’s
moved forward by the amount of the addend. As in the previous
example, the pointer dereference operators are used to operate on
the element the iterator is referring to, and
remove( )
destroys the
current object by calling the container’s
remove( )
.
The same kind of code as before (
a la
the Standard C++ Library
containers) is used for creating the end sentinel: a second
constructor, the container’s
end( )
member function, and
operator==
and
operator!=
for comparison.


The following example creates and tests two different kinds of
Stash
objects, one for a new class called
Int
that announces its
construction and destruction and one that holds objects of the
Standard library
string
class.
//: C16:TPStash2Test.cpp
#include "TPStash2.h"
#include " /require.h"
#include <iostream>
#include <vector>
#include <string>
using namespace std;

class Int {
int i;
public:
Int(int ii = 0) : i(ii) {
cout << ">" << i << ' ';
}
~Int() { cout << "~" << i << ' '; }
operator int() const { return i; }
friend ostream&
operator<<(ostream& os, const Int& x) {
return os << "Int: " << x.i;

16: Introduction to Templates 773

}
friend ostream&
operator<<(ostream& os, const Int* x) {
return os << "Int: " << x->i;
}
};

int main() {
{ // To force destructor call
PStash<Int> ints;
for(int i = 0; i < 30; i++)
ints.add(new Int(i));
cout << endl;
PStash<Int>::iterator it = ints.begin();
it += 5;
PStash<Int>::iterator it2 = it + 10;
for(; it != it2; it++)
delete it.remove(); // Default removal
cout << endl;
for(it = ints.begin();it != ints.end();it++)
if(*it) // Remove() causes "holes"
cout << *it << endl;
} // "ints" destructor called here
cout << "\n \n";
ifstream in("TPStash2Test.cpp");
assure(in, "TPStash2Test.cpp");
// Instantiate for String:
PStash<string> strings;
string line;
while(getline(in, line))

strings.add(new string(line));
PStash<string>::iterator sit = strings.begin();
for(; sit != strings.end(); sit++)
cout << **sit << endl;
sit = strings.begin();
int n = 26;
sit += n;
for(; sit != strings.end(); sit++)
cout << n++ << ": " << **sit << endl;
} ///:~

For convenience,
Int
has an associated
ostream operator<<
for both
an
Int&
and an
Int*
.
774 Thinking in C++ www.BruceEckel.com
The first block of code in
main( )
is surrounded by braces to force
the destruction of the
PStash<Int>
and thus the automatic cleanup
by that destructor. A range of elements is removed and deleted by
hand to show that the

PStash
cleans up the rest.
For both instances of
PStash
,

an iterator is created and used to
move through the container. Notice the elegance produced by
using these constructs; you aren’t assailed with the implementation
details of using an array. You tell the container and iterator objects
what
to do, not how. This makes the solution easier to
conceptualize, to build, and to modify.
Why iterators?
Up until now you’ve seen the mechanics of iterators, but
understanding why they are so important takes a more complex
example.
It’s common to see polymorphism, dynamic object creation, and
containers used together in a true object-oriented program.
Containers and dynamic object creation solve the problem of not
knowing how many or what type of objects you’ll need. And if the
container is configured to hold pointers to base-class objects, an
upcast occurs every time you put a derived-class pointer into the
container (with the associated code organization and extensibility
benefits). As the final code in Volume 1 of this book, this example
will also pull together various aspects of everything you’ve learned
so far – if you can follow this example, then you’re ready for
Volume 2.
Suppose you are creating a program that allows the user to edit and
produce different kinds of drawings. Each drawing is an object that

contains a collection of
Shape
objects:
//: C16:Shape.h
#ifndef SHAPE_H
#define SHAPE_H

16: Introduction to Templates 775
#include <iostream>
#include <string>

class Shape {
public:
virtual void draw() = 0;
virtual void erase() = 0;
virtual ~Shape() {}
};

class Circle : public Shape {
public:
Circle() {}
~Circle() { std::cout << "Circle::~Circle\n"; }
void draw() { std::cout << "Circle::draw\n";}
void erase() { std::cout << "Circle::erase\n";}
};

class Square : public Shape {
public:
Square() {}
~Square() { std::cout << "Square::~Square\n"; }

void draw() { std::cout << "Square::draw\n";}
void erase() { std::cout << "Square::erase\n";}
};

class Line : public Shape {
public:
Line() {}
~Line() { std::cout << "Line::~Line\n"; }
void draw() { std::cout << "Line::draw\n";}
void erase() { std::cout << "Line::erase\n";}
};
#endif // SHAPE_H ///:~

This uses the classic structure of virtual functions in the base class
that are overridden in the derived class. Notice that the
Shape
class
includes a
virtual
destructor, something you should automatically
add to any class with
virtual
functions. If a container holds pointers
or references to
Shape
objects, then when the
virtual
destructors
are called for those objects everything will be properly cleaned up.
776 Thinking in C++ www.BruceEckel.com

Each different type of drawing in the following example makes use
of a different kind of templatized container class: the
PStash
and
Stack
that have been defined in this chapter, and the
vector
class
from the Standard C++ Library. The “use”’ of the containers is
extremely simple, and in general inheritance might not be the best
approach (composition could make more sense), but in this case
inheritance is a simple approach and it doesn’t detract from the
point made in the example.
//: C16:Drawing.cpp
#include <vector> // Uses Standard vector too!
#include "TPStash2.h"
#include "TStack2.h"
#include "Shape.h"
using namespace std;

// A Drawing is primarily a container of Shapes:
class Drawing : public PStash<Shape> {
public:
~Drawing() { cout << "~Drawing" << endl; }
};

// A Plan is a different container of Shapes:
class Plan : public Stack<Shape> {
public:
~Plan() { cout << "~Plan" << endl; }

};

// A Schematic is a different container of Shapes:
class Schematic : public vector<Shape*> {
public:
~Schematic() { cout << "~Schematic" << endl; }
};

// A function template:
template<class Iter>
void drawAll(Iter start, Iter end) {
while(start != end) {
(*start)->draw();
start++;
}
}


16: Introduction to Templates 777
int main() {
// Each type of container has
// a different interface:
Drawing d;
d.add(new Circle);
d.add(new Square);
d.add(new Line);
Plan p;
p.push(new Line);
p.push(new Square);
p.push(new Circle);

Schematic s;
s.push_back(new Square);
s.push_back(new Circle);
s.push_back(new Line);
Shape* sarray[] = {
new Circle, new Square, new Line
};
// The iterators and the template function
// allow them to be treated generically:
cout << "Drawing d:" << endl;
drawAll(d.begin(), d.end());
cout << "Plan p:" << endl;
drawAll(p.begin(), p.end());
cout << "Schematic s:" << endl;
drawAll(s.begin(), s.end());
cout << "Array sarray:" << endl;
// Even works with array pointers:
drawAll(sarray,
sarray + sizeof(sarray)/sizeof(*sarray));
cout << "End of main" << endl;
} ///:~

The different types of containers all hold pointers to
Shape
and
pointers to upcast objects of classes derived from
Shape
. However,
because of polymorphism, the proper behavior still occurs when
the virtual functions are called.

Note that
sarray
,

the array of
Shape*
, can also be thought of as a
container.
778 Thinking in C++ www.BruceEckel.com
Function templates
In
drawAll( )
you see something new. So far in this chapter, we
have been using only
class templates
, which instantiate new classes
based on one or more type parameters. However, you can as easily
create
function templates
, which create new functions based on type
parameters. The reason you create a function template is the same
reason you use for a class template: You’re trying to create generic
code, and you do this by delaying the specification of one or more
types. You just want to say that these type parameters support
certain operations, not exactly what types they are.
The function template
drawAll( )
can be thought of as an
algorithm


(and this is what most of the function templates in the Standard
C++ Library are called). It just says how to do something given
iterators describing a range of elements, as long as these iterators
can be dereferenced, incremented, and compared. These are exactly
the kind of iterators we have been developing in this chapter, and
also – not coincidentally – the kind of iterators that are produced by
the containers in the Standard C++ Library, evidenced by the use of
vector
in this example.
We’d also like
drawAll( )
to be a
generic algorithm
, so that the
containers can be any type at all and we don’t have to write a new
version of the algorithm for each different type of container. Here’s
where function templates are essential, because they automatically
generate the specific code for each different type of container. But
without the extra indirection provided by the iterators, this
genericness wouldn’t be possible. That’s why iterators are
important; they allow you to write general-purpose code that
involves containers without knowing the underlying structure of
the container. (Notice that, in C++, iterators and generic algorithms
require function templates in order to work.)
You can see the proof of this in
main( )
, since
drawAll( )
works
unchanged with each different type of container. And even more

interesting,
drawAll( )
also works with pointers to the beginning

16: Introduction to Templates 779
and end of the array
sarray
. This ability to treat arrays as containers
is integral to the design of the Standard C++ Library, whose
algorithms look much like
drawAll( )
.
Because container class templates are rarely subject to the
inheritance and upcasting you see with “ordinary” classes, you’ll
almost never see
virtual
functions in container classes. Container
class reuse is implemented with templates, not with inheritance.
Summary
Container classes are an essential part of object-oriented
programming. They are another way to simplify and hide the
details of a program and to speed the process of program
development. In addition, they provide a great deal of safety and
flexibility by replacing the primitive arrays and relatively crude
data structure techniques found in C.
Because the client programmer needs containers, it’s essential that
they be easy to use. This is where the
template
comes in. With
templates the syntax for source-code reuse (as opposed to object-

code reuse provided by inheritance and composition) becomes
trivial enough for the novice user. In fact, reusing code with
templates is notably easier than inheritance and composition.
Although you’ve learned about creating container and iterator
classes in this book, in practice it’s much more expedient to learn
the containers and iterators in the Standard C++ Library, since you
can expect them to be available with every compiler. As you will
see in Volume 2 of this book (downloadable from
www.BruceEckel.com
), the containers and algorithms in the Standard
C++ Library will virtually always fulfill your needs so you don’t
have to create new ones yourself.
The issues involved with container-class design have been touched
upon in this chapter, but you may have gathered that they can go
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
so that the increment value used by
inflate( )
can be changed throughout the lifetime of a
particular container object.
4. Modify
TPStash.h
so that the increment value used by
inflate( )
automatically resizes itself to reduce the
number of times it needs to be called. For example, each
time it is called it could double the increment value for
use in the next call. Demonstrate this functionality by
reporting whenever an
inflate( )
is called, and write test

code in
main( )
.
5. Templatize the
fibonacci( )
function on the type of value
that it produces (so it can produce
long
,
float
, etc. instead
of just
int
).
6. Using the Standard C++ Library
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
that uses a Standard
C++ Library
vector
as its underlying implementation.
You may need to look up some of the member functions
of
vector
in order to do this (or just look at the
<vector>

header file).
9. Modify
ValueStack.h
so that it dynamically expands as
you

push( )
more objects and it runs out of space. Change
ValueStackTest.cpp
to test the new functionality.
10. Repeat Exercise 9 but use a Standard C++ Library
vector

as the internal implementation of the
ValueStack
. Notice
how much easier this is.
11. Modify
ValueStackTest.cpp
so that it uses a Standard
C++ Library
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).
14. Modify
TPStash2.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
TPStash2Test.cpp
works unchanged.
15. In
IterIntStack.cpp
, modify
IntStackIter
to give it an
“end sentinel” constructor, and add

operator==
and
operator!=
. In
main( )
, use an iterator to move through
782 Thinking in C++ www.BruceEckel.com
the elements of the container until you reach the end
sentinel.
16. Using
TStack2.h
,
TPStash2.h
, and
Shape.h
, 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
in
NestedSmartPointer.cpp
from
Chapter 12 into a template. Test it with two different
classes.
20. Modify
C15:OStack.h
and
C15:OStackTest.cpp
by
templatizing
class Stack
so that it automatically multiply
inherits from the contained class and from
Object

. The
generated
Stack
should accept and produce only pointers
of the contained type.
21. Repeat Exercise 20 using
vector
instead of
Stack
.
22. Inherit a class
StringVector
from
vector<void*>
and
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
Stack
class in
TStack2.h
to allow
full granularity of ownership: Add a flag to each link
indicating whether that link owns the object it points to,
and support this information in the
push( )
function and
destructor. Add member functions to read and change
the ownership for each link.
26. (Advanced) Modify
PointerToMemberOperator.cpp

from Chapter 12 so that the

FunctionObject
and
operator->*
are templatized to work with any return type
(for
operator->*
, you’ll have to use
member templates
,
described in Volume 2). Add and test support for zero,
one and two arguments in
Dog
member functions.

785








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
can become tedious, such as “class” and “virtual.”
I use a particular coding style for the examples in this book. It was
developed over a number of years, and was partially inspired by
Bjarne Stroustrup’s style in his original
The C++ Programming
Language
.
1
The subject of formatting style is good for hours of hot
debate, so I’ll just say I’m not trying to dictate correct style via my
examples; I have my own motivation for using the style that I do.
Because C++ is a free-form programming language, you can
continue to use whatever style you’re comfortable with.
That said, I will note that it is important to have a consistent
formatting style within a project. If you search the Internet, you will

find a number of tools that can be used to reformat all the code in
your project to achieve this valuable consistency.

1
Ibid.

A: Coding Style 787
The programs in this book are files that are automatically extracted
from the text of the book, which allows them to be tested to ensure
that they work correctly. Thus, the code files printed in the book
should all work without compile-time errors when compiled with
an implementation that conforms to Standard C++ (note that not all
compilers support all language features). The errors that
should

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
. This option
(no
return
statement in
main( )
)

will be used in this book (some
compilers may still generate warnings for this, but those are not
compliant with Standard C++).
File names
In C, it has been traditional to name header files (containing
declarations) with an extension of
.h
and implementation files (that

cause storage to be allocated and code to be generated) with an
extension of
.c
. C++ went through an evolution. It was first
developed on Unix, where the operating system was aware of
upper and lower case in file names. The original file names were
788 Thinking in C++ www.BruceEckel.com
simply capitalized versions of the C extensions:
.H
and
.C
. This of
course didn’t work for operating systems that didn’t distinguish
upper and lower case, such as DOS. DOS C++ vendors used
extensions of
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
used by the code-extraction tool
ExtractCode.cpp
in Volume 2 of
this book (which you can find on the Web site
www.BruceEckel.com
)
to pull each code listing out of the plain-ASCII text version of this
book.
The end-listing tag simply tells
ExtractCode.cpp
that it’s the end of
the listing, but the begin-listing tag is followed by information
about what subdirectory the file belongs in (generally organized by
chapters, so a file that belongs in Chapter 8 would have a tag of
C08

), followed by a colon and the name of the listing file.
Because
ExtractCode.cpp
also creates a
makefile
for each
subdirectory, information about how a program is made and the
command-line used to test it is also incorporated into the listings. If

A: Coding Style 789
a program is stand-alone (it doesn’t need to be linked with
anything else) it has no extra information. This is also true for
header files. However, if it doesn’t contain a
main( )
and is meant
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
presentation, it’s very distracting for the audience if the presenter
must shuffle back and forth between slides, and a few wasted lines
can cause this.
790 Thinking in C++ www.BruceEckel.com
Everyone seems to agree that code inside braces should be
indented. What people don’t agree on – and the place where there’s
the most inconsistency within formatting styles – is this: Where
does the opening brace go? This one question, I think, is what
causes such variations among coding styles (For an enumeration of
coding styles, see C++ Programming Guidelines, by Tom Plum and
Dan Saks, Plum Hall 1991.) I’ll try to convince you that many of
today’s coding styles come from pre-Standard C constraints (before
function prototypes) and are thus inappropriate now.
First, my answer to that key question: the opening brace should
always go on the same line as the “precursor” (by which I mean

“whatever the body is about: a class, function, object definition, if
statement, etc.”). This is a single, consistent rule I apply to all of the
code I write, and it makes formatting much simpler. It makes the
“scannability” easier – when you look at this line:
int func(int a);

you know, by the semicolon at the end of the line, that this is a
declaration and it goes no further, but when you see the line:
int func(int a) {

you immediately know it’s a definition because the line finishes
with an opening brace, not a semicolon. By using this approach,
there’s no difference in where you place the opening parenthesis
for a multi-line definition:
int func(int a) {
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 (
class
, function,
enum
, etc.) and on that same line
you put the opening brace to indicate that the body for this thing is
about to follow. Also, the opening brace is the same for short
inlines and ordinary function definitions.
I assert that the style of function definition used by many folks
comes from pre-function-prototyping C, in which you didn’t
declare the arguments inside the parentheses, but instead between
the closing parenthesis and the opening curly brace (this shows C’s
assembly-language roots):
void bar()
int x;
float y;

{
/* body here */
}

Here, it would be quite ungainly to put the opening brace on the
same line, so no one did it. However, they did make various
792 Thinking in C++ www.BruceEckel.com
decisions about whether the braces should be indented with the
body of the code or whether they should be at the level of the
“precursor.” Thus, we got many different formatting styles.
There are other arguments for placing the brace on the line
immediately following the declaration (of a class, struct, function,
etc.). The following came from a reader, and is presented here so
you know what the issues are:
Experienced ‘vi’ (vim) users know that typing the ‘]’ key twice
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
aligned in the same column. They visually "stick out" much
better. [End of reader comment]
The issue of where to put the opening curly brace is probably the
most discordant issue. I’ve learned to scan both forms, and in the
end it comes down to what you’ve grown comfortable with.
However, I note that the official Java coding standard (found on
Sun’s Java Web site) is effectively the same as the one I present here
– since more folks are beginning to program in both languages, the
consistency between coding styles may be helpful.
The approach I use removes all the exceptions and special cases,
and logically produces a single style of indentation as well. Even
within a function body, the consistency holds, as in:
for(int i = 0; i < 100; i++) {
cout << i << endl;
cout << x * i << endl;

}

The style is easy to teach and to remember – you use a single,
consistent rule for all your formatting, not one for classes, two for
functions (one-line inlines vs. multi-line), and possibly others for
for
loops,
if
statements, etc. The consistency alone, I think, makes it
worthy of consideration. Above all, C++ is a newer language than
C, and although we must make many concessions to C, we
shouldn’t be carrying too many artifacts with us that cause
problems in the future. Small problems multiplied by many lines of
code become big problems. For a thorough examination of the
subject, albeit in C, see
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).
The one exception is for compile-time constants (
const
or
#define
),
in which all of the letters in the identifier are uppercase.
The value of the style is that capitalization has meaning – you can
see from the first letter whether you’re talking about a class or an
object/method. This is especially useful when
static
class members
are accessed.

A: Coding Style 795
Order of header inclusion

Headers are included in order from “the most specific to the most
general.” That is, any header files in the local directory are included
first, then any of my own “tool” headers, such as
require.h
, then
any third-party library headers, then the Standard C++ Library
headers, and finally the C library headers.
The justification for this comes from John Lakos in
Large-Scale C++
Software Design
(Addison-Wesley, 1996):
Latent usage errors can be avoided by ensuring that the .h file of a
component parses by itself – without externally-provided declarations
or definitions Including the .h file as the very first line of the .c file
ensures that no critical piece of information intrinsic to the physical
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
#endif
, that isn’t
standard behavior and so the identifier is commented.
Use of namespaces
In header files, any “pollution” of the namespace in which the
header is included must be scrupulously avoided. That is, if you
change the namespace outside of a function or class, you will cause
that change to occur for any file that includes your header,
resulting in all kinds of problems. No
using
declarations of any
kind are allowed outside of function definitions, and no global
using
directives are allowed in header files.
In

cpp
files, any global
using
directives will only affect that file,
and so in this book they are generally used to produce more easily-
readable code, especially in small programs.
Use of require( ) and assure( )
The
require( )
and
assure( )
functions defined in
require.h
are used
consistently throughout most of the book, so that they may
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.

×