1. Trang chủ
  2. » Công Nghệ Thông Tin

Thinking in C plus plus(P17) pptx

50 417 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Tiêu đề Thinking in C Plus Plus (P17)
Trường học BruceEckel.com
Chuyên ngành Programming
Thể loại Lecture Notes
Định dạng
Số trang 50
Dung lượng 183,03 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Write test code for your Set in main , and then substitute the Standard C++ Library set template to verify that the behavior is correct... Modify AutoCounter.h so that it can be used as

Trang 1

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

Trang 2

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

Trang 3

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

Trang 4

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

Trang 6

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

Trang 7

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

Trang 8

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

Trang 9

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

Trang 10

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

Trang 11

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:

Trang 12

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):

Trang 13

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:

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.]

Trang 14

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,

Trang 15

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

Trang 16

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:

Trang 17

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

Trang 18

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

Trang 19

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++, 2nd 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++

1 First make it work, then make it fast This is true even if you

are certain that a piece of code is really important and that it

will be a principal bottleneck in your system Don’t do it Get

the system going first with as simple a design as possible

Then if it isn’t going fast enough, profile it You’ll almost

always discover that “your” bottleneck isn’t the problem

Save your time for the really important stuff

2 Elegance always pays off It’s not a frivolous pursuit Not

only does it give you a program that’s easier to build and

debug, but it’s also easier to understand and maintain, and

that’s where the financial value lies This point can take some

experience to believe, because it can seem that while you’re

making a piece of code elegant, you’re not being productive

The productivity comes when the code seamlessly integrates

into your system, and even more so when the code or system

is modified

3 Remember the “divide and conquer” principle If the

problem you’re looking at is too confusing, try to imagine

what the basic operation of the program would be, given the

existence of a magic “piece” that handles the hard parts That

“piece” is an object – write the code that uses the object, then

look at the object and encapsulate its hard parts into other

objects, etc

4 Don’t automatically rewrite all your existing C code in C++

unless you need to significantly change its functionality (that

is, don’t fix it if it isn’t broken) Recompiling C in C++ is a

valuable activity because it may reveal hidden bugs

Trang 20

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

in which the class is used In this light, keep everything as

private as possible, and make only the class interface public, always using functions rather than data Make data public

only when forced If class users don’t need to access a

function, make it private If a part of your class must be

exposed to inheritors as protected, provide a function

interface rather than expose the actual data In this way, implementation changes will have minimal impact on

derived classes

Trang 21

9 Don’t fall into analysis paralysis There are some things that

you don’t learn until you start coding and get some kind of

system working C++ has built-in firewalls; let them work for

you Your mistakes in a class or set of classes won’t destroy

the integrity of the whole system

10 Your analysis and design must produce, at minimum, the

classes in your system, their public interfaces, and their

relationships to other classes, especially base classes If your

design methodology produces more than that, ask yourself if

all the pieces produced by that methodology have value over

the lifetime of the program If they do not, maintaining them

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 –

Trang 22

these features or constraints don’t always appear during analysis and design

13 Remember a fundamental rule of software engineering1: All software design problems can be simplified by introducing an extra level of conceptual indirection This one idea is the basis of

abstraction, the primary feature of object-oriented

programming

14 Make classes as atomic as possible; that is, give each class a single, clear purpose If your classes or your system design grows too complicated, break complex classes into simpler ones The most obvious indicator of this is sheer size: if a class is big, chances are it’s doing too much and should be broken up

15 Watch for long member function definitions A function that

is long and complicated is difficult and expensive to

maintain, and is probably trying to do too much all by itself

If you see such a function, it indicates that, at the least, it should be broken up into multiple functions It may also suggest the creation of a new class

16 Watch for long argument lists Function calls then become difficult to write, read and maintain Instead, try to move the member function to a class where it is (more) appropriate, and/or pass objects in as arguments

17 Don’t repeat yourself If a piece of code is recurring in many functions in derived classes, put that code into a single

function in the base class and call it from the derived-class functions Not only do you save code space, you provide for easy propagation of changes You can use an inline function for efficiency Sometimes the discovery of this common code will add valuable functionality to your interface

1 Explained to me by Andrew Koenig

Trang 23

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

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

21 Watch out for limitation 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

Trang 24

22 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

23 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 might

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

Trang 25

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

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

28 When you put a virtual function in a class, make all functions

in that class virtual, and put in a virtual destructor This

approach prevents surprises in the behavior of the interface

Only start removing the virtual keyword when you’re tuning

for efficiency and your profiler has pointed you in this

direction

29 Use data members for variation in value and virtual

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 virtual functions

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

Ngày đăng: 05/07/2014, 19:20

TỪ KHÓA LIÊN QUAN