📄 appendixb.html
字号:
interface.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Watch for
<B>switch</B> statements or chained <B>if-else</B> clauses. This is typically an
indicator of <I>type-check coding</I>, 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.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">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
<I>www.BruceEckel.com</I>.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Watch
out for <I>variance</I>. Two semantically different objects may have identical
actions, or responsibilities, and there is a natural temptation to try to make
one a subclass of the other just to benefit from inheritance. This is called
variance, but there’s no real justification to force a superclass/subclass
relationship where it doesn’t exist. A better solution is to create a
general base class that produces an interface for both as derived classes
– it requires a bit more space, but you still benefit from inheritance and
will probably make an important discovery about the
design.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Watch out
for <I>limitation</I> during inheritance. The clearest designs add new
capabilities to inherited ones. A suspicious design removes old capabilities
during inheritance without adding new ones. But rules are made to be broken, and
if you are working from an old class library, it may be more efficient to
restrict an existing class in its subclass than it would be to restructure the
hierarchy so your new class fits in where it should, above the old
class.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Don’t
extend fundamental functionality by subclassing. If an interface element is
essential to a class it should be in the base class, not added during
derivation. If you’re adding member functions by inheriting, perhaps you
should rethink the
design.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Less is
more. Start with a minimal interface to a class, as small and simple as you need
to solve the problem at hand, but don’t try to anticipate all the ways
that your class <I>might</I> 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.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">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.”</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">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.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Sometimes
you need to inherit in order to access <B>protected</B> 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 make that new class a member object inside any class that needs to
use it, rather than
inheriting.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Typically,
a base class will be used primarily to create an interface to classes derived
from it. Thus, when you create a base class, default to making the member
functions pure virtual. The destructor can also be pure virtual (to force
inheritors to explicitly override it), but remember to give the destructor a
function body, because all destructors in a hierarchy are always
called.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">When you put
a <B>virtual</B> function in a class, make all functions in that class
<B>virtual</B>, and put in a <B>virtual</B> destructor. This approach prevents
surprises in the behavior of the interface. Only start removing the
<B>virtual</B> keyword when you’re tuning for efficiency and your profiler
has pointed you in this
direction.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Use data
members for variation in value and <B>virtual</B> functions for variation in
behavior. That is, if you find a class that uses state variables along with
member functions that switch behavior based on those variables, you should
probably redesign it to express the differences in behavior within subclasses
and overridden <B>virtual</B>
functions.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">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.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">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.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Don’t
use <B>private</B> inheritance. Although it’s in the language and seems to
have occasional functionality, it introduces significant ambiguities when
combined with run-time type identification. Create a private member object
instead of using private
inheritance.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">If two
classes are associated with each other in some functional way (such as
containers and iterators), try to make one a <B>public </B>nested <B>friend</B>
class of the other, as the Standard C++ Library does with iterators inside
containers (examples of this are shown in the latter part of Chapter 16). This
not only emphasizes the association between the classes, but it allows the class
name to be reused by nesting it within another class. The Standard C++ Library
does this by defining a nested <B>iterator</B> class inside each container
class, thereby providing the containers with a common interface. The other
reason you’ll want to nest a class is as part of the <B>private
</B>implementation. Here, nesting is beneficial for implementation hiding rather
than the class association and prevention of namespace pollution noted
above.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Operator
overloading is only “syntactic sugar:” a different way to make a
function call. If overloading an operator doesn’t make the class interface
clearer and easier to use, don’t do it. Create only one automatic type
conversion operator for a class. In general, follow the guidelines and format
given in Chapter 12 when overloading
operators.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Don’t
fall prey to premature optimization. That way lies madness. In particular,
don’t worry about writing (or avoiding) <B>inline</B> functions, making
some functions non<B>virtual</B>, or tweaking code to be efficient when you are
first constructing the system. Your primary goal should be to prove the design,
unless the design requires a certain
efficiency.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Normally,
don’t let the compiler create the constructors, destructors, or the
<B>operator=</B> for you. Class designers should always say exactly what the
class should do and keep the class entirely under control. If you don’t
want a copy-constructor or <B>operator=</B>, declare them as <B>private</B>.<B>
</B>Remember that if you create any constructor, it prevents the default
constructor from being
synthesized.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">If your
class contains pointers, you must create the copy-constructor, <B>operator=</B>,
and destructor for the class to work
properly.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">When you
write a copy-constructor for a derived class, remember to call the base-class
copy-constructor explicitly (also the member-object versions). (See Chapter 14.)
If you don’t, the default constructor will be called for the base class
(or member object) and that probably isn’t what you want. To call the
base-class copy-constructor, pass it the derived object you’re copying
from:</FONT><BR><TT><FONT FACE="Courier New"><B>Derived(const Derived& d) :
Base(d) { //
...</B></FONT></TT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">When
you write an assignment operator for a derived class, remember to call the
base-class version of the assignment operator explicitly. (See Chapter 14.) If
you don’t, then nothing will happen (the same is true for the member
objects). To call the base-class assignment operator, use the base-class name
and scope resolution:</FONT><BR><TT><FONT FACE="Courier New"><B>Derived&
operator=(const Derived& d)
{</B></FONT></TT><BR><TT><FONT FACE="Courier New"><B>
Base::operator=(d);</B></FONT></TT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">If
you need to minimize recompiles during development of a large project, use the
handle class/Cheshire cat technique demonstrated in Chapter 5, and remove it
only if runtime efficiency is a
problem.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Avoid the
preprocessor. Always use <B>const</B> for value substitution and <B>inline</B>s
for macros.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Keep
scopes as small as possible so the visibility and lifetime of your objects are
as small as possible. This reduces the chance of using an object in the wrong
context and hiding a difficult-to-find bug. For example, suppose you have a
container and a piece of code that iterates through it. If you copy that code to
use with a new container, you may accidentally end up using the size of the old
container as the upper bound of the new one. If, however, the old container is
out of scope, the error will be caught at compile
time.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">Avoid global
variables. Always strive to put data inside classes. Global functions are more
likely to occur naturally than global variables, although you may later discover
that a global function may fit better as a <B>static </B>member of a
class.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">If you need
to declare a class or function from a library, always do so by including a
header file. For example, if you want to create a function to write to an
<B>ostream</B>, never declare <B>ostream</B> yourself using an incomplete type
specification like this,</FONT><BR><TT><FONT FACE="Courier New"><B>class
ostream;</B></FONT></TT><BR><FONT FACE="Georgia">This approach leaves your code
vulnerable to changes in representation. (For example, <B>ostream</B> could
actually be a <B>typedef</B>.) Instead, always use the header
file:</FONT><BR><TT><FONT FACE="Courier New"><B>#include
<iostream></B></FONT></TT><BR><FONT FACE="Georgia">When creating your own
classes, if a library is big, provide your users an abbreviated form of the
header file with incomplete type specifications (that is, class name
declarations) for cases in which they need to use only pointers. (It can speed
compilations.)</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">When
choosing the return type of an overloaded operator, consider what will happen if
expressions are chained together. Return a copy or reference to the lvalue
(<B>return *this</B>) so it can be used in a chained expression (<B>A = B =
C</B>). When defining <B>operator=</B>, remember
<B>x=x</B>.</FONT><LI><FONT FACE="Verdana"> </FONT><FONT FACE="Georgia">When
writing a function, pass arguments by <B>const</B> reference as your first
choice. As long as you don’t need to modify the object being passed, this
practice is best because it has the simplicity of pass-by-value syntax but
doesn’t require expensive constructions and destructions to create a local
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -