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

Effective more effective c++

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 (3.23 MB, 670 trang )

BacktoDedication
ContinuetoAcknowledgments

Preface
ThisbookisadirectoutgrowthofmyexperiencesteachingC++toprofessional
programmers.I'vefoundthatmoststudents,afteraweekofintensiveinstruction,
feelcomfortablewiththebasicconstructsofthelanguage,buttheytendtobe
lesssanguineabouttheirabilitytoputtheconstructstogetherinaneffective
manner.Thusbeganmyattempttoformulateshort,specific,easy-to-remember
guidelinesforeffectivesoftwaredevelopmentinC++:asummaryofthethings
experiencedC++programmersalmostalwaysdooralmostalwaysavoiddoing.
Iwasoriginallyinterestedinrulesthatcouldbeenforcedbysomekindoflintlikeprogram.Tothatend,Iledresearchintothedevelopmentoftoolsto
examineC++sourcecodeforviolationsofuser-specifiedconditions.1
Unfortunately,theresearchendedbeforeacompleteprototypecouldbe
developed.Fortunately,severalcommercialC++-checkingproductsarenow
available.(You'llfindanoverviewofsuchproductsinthearticleonstatic
analysistoolsbymeandMartinKlaus.)
Thoughmyinitialinterestwasinprogrammingrulesthatcouldbeautomatically
enforced,Isoonrealizedthelimitationsofthatapproach.Themajorityof
guidelinesusedbygoodC++programmersaretoodifficulttoformalizeorhave
toomanyimportantexceptionstobeblindlyenforcedbyaprogram.Iwasthus
ledtothenotionofsomethinglessprecisethanacomputerprogram,butstill
morefocusedandto-the-pointthanageneralC++textbook.Theresultyounow
holdinyourhands:abookcontaining50specificsuggestionsonhowto
improveyourC++programsanddesigns.
Inthisbook,you'llfindadviceonwhatyoushoulddo,andwhy,andwhatyou
shouldnotdo,andwhynot.Fundamentally,ofcourse,thewhysaremore
importantthanthewhats,butit'salotmoreconvenienttorefertoalistof
guidelinesthantomemorizeatextbookortwo.
UnlikemostbooksonC++,mypresentationhereisnotorganizedaround
particularlanguagefeatures.Thatis,Idon'ttalkaboutconstructorsinoneplace,


aboutvirtualfunctionsinanother,aboutinheritanceinathird,etc.Instead,each
discussioninthebookistailoredtotheguidelineitaccompanies,andmy


coverageofthevariousaspectsofaparticularlanguagefeaturemaybedispersed
throughoutthebook.
Theadvantageofthisapproachisthatitbetterreflectsthecomplexityofthe
softwaresystemsforwhichC++isoftenchosen,systemsinwhich
understandingindividuallanguagefeaturesisnotenough.Forexample,
experiencedC++developersknowthatunderstandinginlinefunctionsand
understandingvirtualdestructorsdoesnotnecessarilymeanyouunderstand
inlinevirtualdestructors.Suchbattle-scarreddevelopersrecognizethat
comprehendingtheinteractionsbetweenthefeaturesinC++isofthegreatest
possibleimportanceinusingthelanguageeffectively.Theorganizationofthis
bookreflectsthatfundamentaltruth.
Thedisadvantageofthisdesignisthatyoumayhavetolookinmorethanone
placetofindeverythingIhavetosayaboutaparticularC++construct.To
minimizetheinconvenienceofthisapproach,Ihavesprinkledcross-references
liberallythroughoutthetext,andacomprehensiveindexisprovidedattheend
ofthebook.
Inpreparingthissecondedition,myambitiontoimprovethebookhasbeen
temperedbyfear.Tensofthousandsofprogrammersembracedthefirstedition
ofEffectiveC++,andIdidn'twanttodestroywhatevercharacteristicsattracted
themtoit.However,inthesixyearssinceIwrotethebook,C++haschanged,
theC++libraryhaschanged(seeItem49),myunderstandingofC++has
changed,andacceptedusageofC++haschanged.That'salotofchange,andit
wasimportanttomethatthetechnicalmaterialinEffectiveC++berevisedto
reflectthosechanges.I'ddonewhatIcouldbyupdatingindividualpages
betweenprintings,butbooksandsoftwarearefrighteninglysimilar—there
comesatimewhenlocalizedenhancementsfailtosuffice,andtheonlyrecourse

isasystem-widerewrite.Thisbookistheresultofthatrewrite:EffectiveC++,
Version2.0.
ThosefamiliarwiththefirsteditionmaybeinterestedtoknowthateveryItemin
thebookhasbeenreworked.Ibelievetheoverallstructureofthebookremains
sound,however,solittletherehaschanged.Ofthe50originalItems,Iretained
48,thoughItinkeredwiththewordingofafewItemtitles(inadditionto
revisingtheaccompanyingdiscussions).TheretiredItems(i.e.,thosereplaced
withcompletelynewmaterial)arenumbers32and49,thoughmuchofthe
informationthatusedtobeinItem32somehowfounditswayintotherevamped


Item1.IswappedtheorderofItems41and42,becausethatmadeiteasierto
presenttherevisedmaterialtheycontain.Finally,Ireversedthedirectionofmy
inheritancearrows.Theynowfollowthealmost-universalconventionof
pointingfromderivedclassestobaseclasses.ThisisthesameconventionI
followedinmy1996book,MoreEffectiveC++.
Thesetofguidelinesinthisbookisfarfromexhaustive,butcomingupwith
goodrules—onesthatareapplicabletoalmostallapplicationsalmostallthe
time—isharderthanitlooks.Perhapsyouknowofadditionalguidelines,of
morewaysinwhichtoprogrameffectivelyinC++.Ifso,Iwouldbedelightedto
hearaboutthem.
Ontheotherhand,youmayfeelthatsomeoftheItemsinthisbookare
inappropriateasgeneraladvice;thatthereisabetterwaytoaccomplishatask
examinedinthebook;orthatoneormoreofthetechnicaldiscussionsisunclear,
incomplete,ormisleading.Iencourageyoutoletmeknowaboutthesethings,
too.
DonaldKnuthhasalonghistoryofofferingasmallrewardtopeoplewhonotify
himoferrorsinhisbooks.Thequestforaperfectbookislaudableinanycase,
butinviewofthenumberofbug-riddenC++booksthathavebeenrushedto
market,IfeelespeciallystronglycompelledtofollowKnuth'sexample.

Therefore,foreacherrorinthisbookthatisreportedtome—beittechnical,
grammatical,typographical,orotherwise—Iwill,infutureprintings,gladly
addtotheacknowledgmentsthenameofthefirstpersontobringthaterrorto
myattention.
°

Sendyoursuggestedguidelines,yourcomments,yourcriticisms,and—sigh—
yourbugreportsto:
ScottMeyers
c/oPublisher,CorporateandProfessionalPublishing
AddisonWesleyLongman,Inc.
1JacobWay
Reading,MA01867
U.S.A.
Alternatively,youmaysendelectronicmailto
Imaintainalistofchangestothisbooksinceitsfirstprinting,includingbug-


fixes,clarifications,andtechnicalupdates.Thislistisavailableatthe
EffectiveC++WorldWideWebsite.Ifyouwouldlikeacopyofthislist,but
youlackaccesstotheWorldWideWeb,pleasesendarequesttooneofthe
addressesabove,andIwillseethatthelistissenttoyou.
°

ScottDouglasMeyers

Stafford,Oregon
July1997

°


BacktoDedication
ContinuetoAcknowledgments
1Youcanfindanoverviewoftheresearchatthe°EffectiveC++WorldWide

Website.
Return


Dedication
ForNancy,withoutwhomnothingwouldbemuchworthdoing.
ContinuetoPreface


BacktoIntroduction
ContinuetoItem1:Preferconstandinlineto#define.

ShiftingfromCtoC++
GettingusedtoC++takesalittlewhileforeveryone,butforgrizzledC
programmers,theprocesscanbeespeciallyunnerving.BecauseCiseffectively
asubsetofC++,alltheoldCtrickscontinuetowork,butmanyofthemareno
longerappropriate.ToC++programmers,forexample,apointertoapointer
looksalittlefunny.Why,wewonder,wasn'tareferencetoapointerused
instead?
Cisafairlysimplelanguage.Allitreallyoffersismacros,pointers,structs,
arrays,andfunctions.Nomatterwhattheproblemis,thesolutionwillalways
boildowntomacros,pointers,structs,arrays,andfunctions.NotsoinC++.The
macros,pointers,structs,arraysandfunctionsarestillthere,ofcourse,butsoare
privateandprotectedmembers,functionoverloading,defaultparameters,
constructorsanddestructors,user-definedoperators,inlinefunctions,references,

friends,templates,exceptions,namespaces,andmore.Thedesignspaceismuch
richerinC++thanitisinC:therearejustalotmoreoptionstoconsider.
Whenfacedwithsuchavarietyofchoices,manyCprogrammershunkerdown
andholdtighttowhatthey'reusedto.Forthemostpart,that'snogreatsin,but
someChabitsruncontrarytothespiritofC++.Thosearetheonesthathave
simplygottogo.
BacktoIntroduction
ContinuetoItem1:Preferconstandinlineto#define.


BacktoShiftingfromCtoC++
ContinuetoItem2:Prefer<iostream>to<stdio.h>.

Item1:Preferconstandinlineto#define.
ThisItemmightbetterbecalled"preferthecompilertothepreprocessor,"
because#defineisoftentreatedasifit'snotpartofthelanguageperse.That's
oneofitsproblems.Whenyoudosomethinglikethis,
#defineASPECT_RATIO1.653

thesymbolicnameASPECT_RATIOmayneverbeseenbycompilers;itmaybe
removedbythepreprocessorbeforethesourcecodeevergetstoacompiler.Asa
result,thenameASPECT_RATIOmaynotgetenteredintothesymboltable.This
canbeconfusingifyougetanerrorduringcompilationinvolvingtheuseofthe
constant,becausetheerrormessagemayreferto1.653,notASPECT_RATIO.If
ASPECT_RATIOwasdefinedinaheaderfileyoudidn'twrite,you'dthenhaveno
ideawherethat1.653camefrom,andyou'dprobablywastetimetrackingit
down.Thisproblemcanalsocropupinasymbolicdebugger,because,again,the
nameyou'reprogrammingwithmaynotbeinthesymboltable.
Thesolutiontothissorryscenarioissimpleandsuccinct.Insteadofusinga
preprocessormacro,defineaconstant:

constdoubleASPECT_RATIO=1.653;

Thisapproachworkslikeacharm.Therearetwospecialcasesworth
mentioning,however.
First,thingscangetabittrickywhendefiningconstantpointers.Because
constantdefinitionsaretypicallyputinheaderfiles(wheremanydifferent
sourcefileswillincludethem),it'simportantthatthepointerbedeclaredconst,
usuallyinadditiontowhatthepointerpointsto.Todefineaconstantchar*basedstringinaheaderfile,forexample,youhavetowriteconsttwice:
constchar*constauthorName="ScottMeyers";

Foradiscussionofthemeaningsandusesofconst,especiallyinconjunction
withpointers,seeItem21.
Second,it'softenconvenienttodefineclass-specificconstants,andthatcallsfor


aslightlydifferenttack.Tolimitthescopeofaconstanttoaclass,youmust
makeitamember,andtoensurethere'satmostonecopyoftheconstant,you
mustmakeitastaticmember:
classGamePlayer{
private:
staticconstintNUM_TURNS=5;//constantdeclaration
intscores[NUM_TURNS];//useofconstant
...
};

There'saminorwrinkle,however,whichisthatwhatyouseeaboveisa
declarationforNUM_TURNS,notadefinition.Youmuststilldefinestaticclass
membersinanimplementationfile:
constintGamePlayer::NUM_TURNS;//mandatorydefinition;
//goesinclassimpl.file


There'snoneedtolosesleepworryingaboutthisdetail.Ifyouforgetthe
definition,yourlinkershouldremindyou.
Oldercompilersmaynotacceptthissyntax,becauseitusedtobeillegalto
provideaninitialvalueforastaticclassmemberatitspointofdeclaration.
Furthermore,in-classinitializationisallowedonlyforintegraltypes(e.g.,ints,
bools,chars,etc.),andonlyforconstants.Incaseswheretheabovesyntaxcan't
beused,youputtheinitialvalueatthepointofdefinition:
classEngineeringConstants{//thisgoesintheclass
private://headerfile
staticconstdoubleFUDGE_FACTOR;
...
};
//thisgoesintheclassimplementationfile
constdoubleEngineeringConstants::FUDGE_FACTOR=1.35;

Thisisallyouneedalmostallthetime.Theonlyexceptioniswhenyouneedthe
valueofaclassconstantduringcompilationoftheclass,suchasinthe
declarationofthearrayGamePlayer::scoresabove(wherecompilersinsiston
knowingthesizeofthearrayduringcompilation).Thentheacceptedwayto


compensateforcompilersthat(incorrectly)forbidthein-classspecificationof
initialvaluesforintegralclassconstantsistousewhatisaffectionatelyknownas
"theenumhack."Thistechniquetakesadvantageofthefactthatthevaluesofan
enumeratedtypecanbeusedwhereintsareexpected,soGamePlayercouldjust
aswellhavebeendefinedlikethis:
classGamePlayer{
private:
enum{NUM_TURNS=5};//"theenumhack"—makes

//NUM_TURNSasymbolicname
//for5
intscores[NUM_TURNS];//fine
...
};

Unlessyou'redealingwithcompilersofprimarilyhistoricalinterest(i.e.,those
writtenbefore1995),youshouldn'thavetousetheenumhack.Still,it'sworth
knowingwhatitlookslike,becauseit'snotuncommontoencounteritincode
datingbacktothoseearly,simplertimes.
Gettingbacktothepreprocessor,anothercommon(mis)useofthe#define
directiveisusingittoimplementmacrosthatlooklikefunctionsbutthatdon't
incurtheoverheadofafunctioncall.Thecanonicalexampleiscomputingthe
maximumoftwovalues:
#definemax(a,b)((a)>(b)?(a):(b))

Thislittlenumberhassomanydrawbacks,justthinkingaboutthemispainful.
You'rebetteroffplayinginthefreewayduringrushhour.
Wheneveryouwriteamacrolikethis,youhavetoremembertoparenthesizeall
theargumentswhenyouwritethemacrobody;otherwiseyoucanruninto
troublewhensomebodycallsthemacrowithanexpression.Butevenifyouget
thatright,lookattheweirdthingsthatcanhappen:
inta=5,b=0;
max(++a,b);//aisincrementedtwice
max(++a,b+10);//aisincrementedonce


Here,whathappenstoainsidemaxdependsonwhatitisbeingcomparedwith!
Fortunately,youdon'tneedtoputupwiththisnonsense.Youcangetallthe
efficiencyofamacroplusallthepredictablebehaviorandtype-safetyofa

regularfunctionbyusinganinlinefunction(seeItem33):
inlineintmax(inta,intb){returna>b?a:b;}

Nowthisisn'tquitethesameasthemacroabove,becausethisversionofmaxcan
onlybecalledwithints,butatemplatefixesthatproblemquitenicely:
template<classT>
inlineconstT&max(constT&a,constT&b)
{returna>b?a:b;}

Thistemplategeneratesawholefamilyoffunctions,eachofwhichtakestwo
objectsconvertibletothesametypeandreturnsareferenceto(aconstant
versionof)thegreaterofthetwoobjects.Becauseyoudon'tknowwhatthetype
Twillbe,youpassandreturnbyreferenceforefficiency(seeItem22).
Bytheway,beforeyouconsiderwritingtemplatesforcommonlyuseful
functionslikemax,checkthestandardlibrary(seeItem49)toseeiftheyalready
exist.Inthecaseofmax,you'llbepleasantlysurprisedtofindthatyoucanrest
onothers'laurels:maxispartofthestandardC++library.
Giventheavailabilityofconstsandinlines,yourneedforthepreprocessoris
reduced,butit'snotcompletelyeliminated.Thedayisfarfromnearwhenyou
canabandon#include,and#ifdef/#ifndefcontinuetoplayimportantrolesin
controllingcompilation.It'snotyettimetoretirethepreprocessor,butyou
shoulddefinitelyplantostartgivingitlongerandmorefrequentvacations.
BacktoShiftingfromCtoC++
ContinuetoItem2:Prefer<iostream>to<stdio.h>.


BacktoItem1:Preferconstandinlineto#define.
ContinuetoItem3:Prefernewanddeletetomallocandfree.

Item2:Prefer<iostream>to<stdio.h>.

Yes,they'reportable.Yes,they'reefficient.Yes,youalreadyknowhowtouse
them.Yes,yes,yes.Butveneratedthoughtheyare,thefactofthematteristhat
scanfandprintfandalltheirilkcouldusesomeimprovement.Inparticular,
they'renottype-safeandthey'renotextensible.Becausetypesafetyand
extensibilityarecornerstonesoftheC++wayoflife,youmightjustaswell
resignyourselftothemrightnow.Besides,theprintf/scanffamilyoffunctions
separatethevariablestobereadorwrittenfromtheformattinginformationthat
controlsthereadsandwrites,justlikeFORTRANdoes.It'stimetobidthe1950s
afondfarewell.
Notsurprisingly,theseweaknessesofprintf/scanfarethestrengthsof
operator>>andoperator<<.
inti;
Rationalr;//risarationalnumber
...
cin>>i>>r;
cout<
Ifthiscodeistocompile,theremustbefunctionsoperator>>andoperator<<
thatcanworkwithanobjectoftypeRational(possiblyviaimplicittype
conversion—seeItemM5).Ifthesefunctionsaremissing,it'sanerror.(The
versionsforintsarestandard.)Furthermore,compilerstakecareoffiguringout
whichversionsoftheoperatorstocallfordifferentvariables,soyouneedn't
worryaboutspecifyingthatthefirstobjecttobereadorwrittenisanintandthe
secondisaRational.
Inaddition,objectstobereadarepassedusingthesamesyntacticformasare
thosetobewritten,soyoudon'thavetoremembersillyruleslikeyoudofor
scanf,whereifyoudon'talreadyhaveapointer,youhavetobesuretotakean
address,butifyou'vealreadygotapointer,youhavetobesurenottotakean
address.LetC++compilerstakecareofthosedetails.Theyhavenothingbetter
todo,andyoudohavebetterthingstodo.Finally,notethatbuilt-intypeslike

intarereadandwritteninthesamemannerasuser-definedtypeslikeRational.


Trythatusingscanfandprintf!
Here'showyoumightwriteanoutputroutineforaclassrepresentingrational
numbers:
classRational{
public:
Rational(intnumerator=0,intdenominator=1);
...
private:
intn,d;//numeratoranddenominator
friendostream&operator<<(ostream&s,constRational&r);
};
ostream&operator<<(ostream&s,constRational&r)
{
s<returns;
}

Thisversionofoperator<arediscussedelsewhereinthisbook.Forexample,operator<function(Item19explainswhy),andtheRationalobjecttobeoutputispassed
intooperator<Thecorrespondinginputfunction,operator>>,wouldbedeclaredand
implementedinasimilarmanner.
ReluctantthoughIamtoadmitit,therearesomesituationsinwhichitmay
makesensetofallbackonthetriedandtrue.First,someimplementationsof
iostreamoperationsarelessefficientthanthecorrespondingCstream
operations,soit'spossible(thoughunlikely—seeItemM16)thatyouhavean

applicationinwhichthismakesasignificantdifference.Bearinmind,though,
thatthissaysnothingaboutiostreamsingeneral,onlyaboutparticular
implementations;seeItemM23.Second,theiostreamlibrarywasmodifiedin
someratherfundamentalwaysduringthecourseofitsstandardization(seeItem
49),soapplicationsthatmustbemaximallyportablemaydiscoverthatdifferent
vendorssupportdifferentapproximationstothestandard.Finally,becausethe
classesoftheiostreamlibraryhaveconstructorsandthefunctionsin<stdio.h>
donot,therearerareoccasionsinvolvingtheinitializationorderofstaticobjects
(seeItem47)whenthestandardClibrarymaybemoreusefulsimplybecause


youknowthatyoucanalwayscallitwithimpunity.
Thetypesafetyandextensibilityofferedbytheclassesandfunctionsinthe
iostreamlibraryaremoreusefulthanyoumightinitiallyimagine,sodon'tthrow
themawayjustbecauseyou'reusedto<stdio.h>.Afterall,evenafterthe
transition,you'llstillhaveyourmemories.
Incidentally,that'snotypointheItemtitle;Ireallymean<iostream>andnot
<iostream.h>.Technicallyspeaking,thereisnosuchthingas<iostream.h>—
the standardizationcommitteeeliminateditinfavorof<iostream>whenthey
truncatedthenamesoftheothernon-Cstandardheadernames.Thereasonsfor
theirdoingthisareexplainedinItem49,butwhatyoureallyneedtounderstand
isthatif(asislikely)yourcompilerssupportboth<iostream>and
<iostream.h>,theheadersaresubtlydifferent.Inparticular,ifyou#include
<iostream>,yougettheelementsoftheiostreamlibraryensconcedwithinthe
namespacestd(seeItem28),butifyou#include<iostream.h>,yougetthose
sameelementsatglobalscope.Gettingthematglobalscopecanleadtoname
conflicts,preciselythekindsofnameconflictstheuseofnamespacesis
designedtoprevent.Besides,<iostream>islesstotypethan<iostream.h>.For
manypeople,that'sreasonenoughtopreferit.
°


BacktoItem1:Preferconstandinlineto#define.
ContinuetoItem3:Prefernewanddeletetomallocandfree.


BacktoItem2:Prefer<iostream>to<stdio.h>.
ContinuetoItem4:PreferC++-stylecomments.

Item3:Prefernewanddeletetomallocandfree.
Theproblemwithmallocandfree(andtheirvariants)issimple:theydon't
knowaboutconstructorsanddestructors.
Considerthefollowingtwowaystogetspaceforanarrayof10stringobjects,
oneusingmalloc,theotherusingnew:
string*stringArray1=
static_cast<string*>(malloc(10*sizeof(string)));
string*stringArray2=newstring[10];

HerestringArray1pointstoenoughmemoryfor10stringobjects,butno
objectshavebeenconstructedinthatmemory.Furthermore,withoutjumping
throughsomeratherobscurelinguistichoops(suchasthosedescribedinItems
M4andM8),youhavenowaytoinitializetheobjectsinthearray.Inother
words,stringArray1isprettyuseless.Incontrast,stringArray2pointstoan
arrayof10fullyconstructedstringobjects,eachofwhichcansafelybeusedin
anyoperationtakingastring.
Nonetheless,let'ssupposeyoumagicallymanagedtoinitializetheobjectsinthe
stringArray1array.Lateroninyourprogram,then,you'dexpecttodothis:
free(stringArray1);
delete[]stringArray2;//seeItem5forwhythe
//"[]"isnecessary


ThecalltofreewillreleasethememorypointedtobystringArray1,butno
destructorswillbecalledonthestringobjectsinthatmemory.Ifthestring
objectsthemselvesallocatedmemory,asstringobjectsarewonttodo,allthe
memorytheyallocatedwillbelost.Ontheotherhand,whendeleteiscalledon
stringArray2,adestructoriscalledforeachobjectinthearraybeforeany
memoryisreleased.
Becausenewanddeleteinteractproperlywithconstructorsanddestructors,they
areclearlythesuperiorchoice.


Mixingnewanddeletewithmallocandfreeisusuallyabadidea.Whenyou
trytocallfreeonapointeryougotfromneworcalldeleteonapointeryougot
frommalloc,theresultsareundefined,andweallknowwhat"undefined"
means:itmeansitworksduringdevelopment,itworksduringtesting,andit
blowsupinyourmostimportantcustomers'faces.
Theincompatibilityofnew/deleteandmalloc/freecanleadtosomeinteresting
complications.Forexample,thestrdupfunctioncommonlyfoundin
<string.h>takesachar*-basedstringandreturnsacopyofit:
char*strdup(constchar*ps);//returnacopyofwhat
//pspointsto

Atsomesites,bothCandC++usethesameversionofstrdup,sothememory
allocatedinsidethefunctioncomesfrommalloc.Asaresult,unwittingC++
programmerscallingstrdupmightoverlookthefactthattheymustusefreeon
thepointerreturnedfromstrdup.Butwait!Toforestallsuchcomplications,
somesitesmightdecidetorewritestrdupforC++andhavethisrewritten
versioncallnewinsidethefunction,therebymandatingthatcallerslateruse
delete.Asyoucanimagine,thiscanleadtosomeprettynightmarishportability
problemsascodeisshuttledbackandforthbetweensiteswithdifferentformsof
strdup.

Still,C++programmersareasinterestedincodereuseasCprogrammers,and
it'sasimplefactthattherearelotsofClibrariesbasedonmallocandfree
containingcodethatisverymuchworthreusing.Whentakingadvantageofsuch
alibrary,it'slikelyyou'llendupwiththeresponsibilityforfreeingmemory
mallocedbythelibraryand/ormallocingmemorythelibraryitselfwillfree.
That'sfine.There'snothingwrongwithcallingmallocandfreeinsideaC++
programaslongasyoumakesurethepointersyougetfrommallocalwaysmeet
theirmakerinfreeandthepointersyougetfromneweventuallyfindtheirway
todelete.Theproblemsstartwhenyougetsloppyandtrytomixnewwithfree
ormallocwithdelete.That'sjustaskingfortrouble.
Giventhatmallocandfreeareignorantofconstructorsanddestructorsandthat
mixingmalloc/freewithnew/deletecanbemorevolatilethanafraternityrush
party,you'rebestoffstickingtoanexclusivedietofnewsanddeleteswhenever
youcan.
BacktoItem2:Prefer<iostream>to<stdio.h>.


ContinuetoItem4:PreferC++-stylecomments.


BacktoItem3:Prefernewanddeletetomallocandfree.
ContinuetoMemoryManagement

Item4:PreferC++-stylecomments.
ThegoodoldCcommentsyntaxworksinC++too,butthenewfangledC++
comment-to-end-of-linesyntaxhassomedistinctadvantages.Forexample,
considerthissituation:
if(a>b){
//inttemp=a;//swapaandb
//a=b;

//b=temp;
}

Hereyouhaveacodeblockthathasbeencommentedoutforsomereasonor
other,butinastunningdisplayofsoftwareengineering,theprogrammerwho
originallywrotethecodeactuallyincludedacommenttoindicatewhatwas
goingon.WhentheC++commentformwasusedtocommentouttheblock,the
embeddedcommentwasofnoconcern,buttherecouldhavebeenaserious
problemhadeverybodychosentouseC-stylecomments:
if(a>b){
/*inttemp=a;/*swapaandb*/
a=b;
b=temp;
*/
}

Noticehowtheembeddedcommentinadvertentlyputsaprematureendtothe
commentthatissupposedtocommentoutthecodeblock.
C-stylecommentsstillhavetheirplace.Forexample,they'reinvaluablein
headerfilesthatareprocessedbybothCandC++compilers.Still,ifyoucanuse
C++-stylecomments,youareoftenbetteroffdoingso.
It'sworthpointingoutthatretrogradepreprocessorsthatwerewrittenonlyforC
don'tknowhowtocopewithC++-stylecomments,sothingslikethefollowing
sometimesdon'tworkasexpected:
#defineLIGHT_SPEED3e8//m/sec(inavacuum)

GivenapreprocessorunfamiliarwithC++,thecommentattheendoftheline


becomespartofthemacro!Ofcourse,asisdiscussedinItem1,youshouldn'tbe

usingthepreprocessortodefineconstantsanyway.
BacktoItem3:Prefernewanddeletetomallocandfree.
ContinuetoMemoryManagement


BacktoItem4:PreferC++-stylecomments.
ContinuetoItem5:Usethesameformincorrespondingusesofnewanddelete.

MemoryManagement
MemorymanagementconcernsinC++fallintotwogeneralcamps:gettingit
rightandmakingitperformefficiently.Goodprogrammersunderstandthatthese
concernsshouldbeaddressedinthatorder,becauseaprogramthatisdazzlingly
fastandastoundinglysmallisoflittleuseifitdoesn'tbehavethewayit's
supposedto.Formostprogrammers,gettingthingsrightmeanscallingmemory
allocationanddeallocationroutinescorrectly.Makingthingsperformefficiently,
ontheotherhand,oftenmeanswritingcustomversionsoftheallocationand
deallocationroutines.Gettingthingsrightthereisevenmoreimportant.
Onthecorrectnessfront,C++inheritsfromConeofitsbiggestheadaches,that
ofpotentialmemoryleaks.Evenvirtualmemory,wonderfulinventionthoughit
is,isfinite,andnoteverybodyhasvirtualmemoryinthefirstplace.
InC,amemoryleakariseswhenevermemoryallocatedthroughmallocisnever
returnedthroughfree.ThenamesoftheplayersinC++arenewanddelete,but
thestoryismuchthesame.However,thesituationisimprovedsomewhatbythe
presenceofdestructors,becausetheyprovideaconvenientrepositoryforcallsto
deletethatallobjectsmustmakewhentheyaredestroyed.Atthesametime,
thereismoretoworryabout,becausenewimplicitlycallsconstructorsand
deleteimplicitlycallsdestructors.Furthermore,thereisthecomplicationthat
youcandefineyourownversionsofoperatornewandoperatordelete,both
insideandoutsideofclasses.Thisgivesrisetoallkindsofopportunitiestomake
mistakes.ThefollowingItems(aswellasItemM8)shouldhelpyouavoidsome

ofthemostcommonones.
BacktoItem4:PreferC++-stylecomments.
ContinuetoItem5:Usethesameformincorrespondingusesofnewanddelete.


BacktoMemoryManagement
ContinuetoItem6:Usedeleteonpointermembersindestructors.

Item5:Usethesameformincorrespondingusesofnew
anddelete.
What'swrongwiththispicture?
string*stringArray=newstring[100];
...
deletestringArray;

Everythinghereappearstobeinorder—theuseofnewismatchedwithauseof
delete—butsomethingisstillquitewrong:yourprogram'sbehavioris
undefined.Attheveryleast,99ofthe100stringobjectspointedtoby
stringArrayareunlikelytobeproperlydestroyed,becausetheirdestructorswill
probablyneverbecalled.
Whenyouusenew,twothingshappen.First,memoryisallocated(viathe
functionoperatornew,aboutwhichI'llhavemoretosayinItems7-10aswell
asItemM8).Second,oneormoreconstructorsarecalledforthatmemory.When
youusedelete,twootherthingshappen:oneormoredestructorsarecalledfor
thememory,thenthememoryisdeallocated(viathefunctionoperatordelete
—seeItems8andM8).Thebigquestionfordeleteisthis:howmanyobjects
resideinthememorybeingdeleted?Theanswertothatdetermineshowmany
destructorsmustbecalled.
Actually,thequestionissimpler:doesthepointerbeingdeletedpointtoasingle
objectortoanarrayofobjects?Theonlywayfordeletetoknowisforyouto

tellit.Ifyoudon'tusebracketsinyouruseofdelete,deleteassumesasingle
objectispointedto.Otherwise,itassumesthatanarrayispointedto:
string*stringPtr1=newstring;
string*stringPtr2=newstring[100];
...
deletestringPtr1;//deleteanobject


delete[]stringPtr2;//deleteanarrayof
//objects

Whatwouldhappenifyouusedthe"[]"formonstringPtr1?Theresultis
undefined.Whatwouldhappenifyoudidn'tusethe"[]"formonstringPtr2?
Well,that'sundefinedtoo.Furthermore,it'sundefinedevenforbuilt-intypeslike
ints,eventhoughsuchtypeslackdestructors.Therule,then,issimple:ifyou
use[]whenyoucallnew,youmustuse[]whenyoucalldelete.Ifyoudon't
use[]whenyoucallnew,don'tuse[]whenyoucalldelete.
Thisisaparticularlyimportantruletobearinmindwhenyouarewritingaclass
containingapointerdatamemberandalsoofferingmultipleconstructors,
becausethenyou'vegottobecarefultousethesameformofnewinallthe
constructorstoinitializethepointermember.Ifyoudon't,howwillyouknow
whatformofdeletetouseinyourdestructor?Forafurtherexaminationofthis
issue,seeItem11.
Thisruleisalsoimportantforthetypedef-inclined,becauseitmeansthata
typedef'sauthormustdocumentwhichformofdeleteshouldbeemployed
whennewisusedtoconjureupobjectsofthetypedeftype.Forexample,
considerthistypedef:
typedefstringAddressLines[4];//aperson'saddress
//has4lines,eachof
//whichisastring


BecauseAddressLinesisanarray,thisuseofnew,
string*pal=newAddressLines;//notethat"new
//AddressLines"returns
//astring*,justlike
//"newstring[4]"would

mustbematchedwiththearrayformofdelete:
deletepal;//undefined!
delete[]pal;//fine

Toavoidsuchconfusion,you'reprobablybestoffabstainingfromtypedefsfor
arraytypes.Thatshouldbeeasy,however,becausethestandardC++library(see
Item49)includesstringandvectortemplatesthatreducetheneedforbuilt-in


arraystonearlyzero.Here,forexample,AddressLinescouldbedefinedtobea
vectorofstrings.Thatis,AddressLinescouldbeoftypevector<string>.
BacktoMemoryManagement
ContinuetoItem6:Usedeleteonpointermembersindestructors.


BacktoItem5:Usethesameformincorrespondingusesofnewanddelete.
ContinuetoItem7:Bepreparedforout-of-memoryconditions.

Item6:Usedeleteonpointermembersindestructors.
Mostofthetime,classesperformingdynamicmemoryallocationwillusenewin
theconstructor(s)toallocatethememoryandwilllaterusedeleteinthe
destructortofreeupthememory.Thisisn'ttoodifficulttogetrightwhenyou
firstwritetheclass,provided,ofcourse,thatyouremembertoemploydelete

onallthemembersthatcouldhavebeenassignedmemoryinanyconstructor.
However,thesituationbecomesmoredifficultasclassesaremaintainedand
enhanced,becausetheprogrammersmakingthemodificationstotheclassmay
notbetheoneswhowrotetheclassinthefirstplace.Underthoseconditions,it's
easytoforgetthataddingapointermemberalmostalwaysrequireseachofthe
following:
Initializationofthepointerineachoftheconstructors.Ifnomemoryisto
beallocatedtothepointerinaparticularconstructor,thepointershouldbe
initializedto0(i.e.,thenullpointer).
Deletionoftheexistingmemoryandassignmentofnewmemoryinthe
assignmentoperator.(SeealsoItem17.)
Deletionofthepointerinthedestructor.
Ifyouforgettoinitializeapointerinaconstructor,orifyouforgettohandleit
insidetheassignmentoperator,theproblemusuallybecomesapparentfairly
quickly,soinpracticethoseissuesdon'ttendtoplagueyou.Failingtodeletethe
pointerinthedestructor,however,oftenexhibitsnoobviousexternalsymptoms.
Instead,itmanifestsitselfasasubtlememoryleak,aslowlygrowingcancerthat
willeventuallydevouryouraddressspaceanddriveyourprogramtoanearly
demise.Becausethisparticularproblemdoesn'tusuallycallattentiontoitself,
it'simportantthatyoukeepitinmindwheneveryouaddapointermembertoa
class.
Note,bytheway,thatdeletinganullpointerisalwayssafe(itdoesnothing).
Thus,ifyouwriteyourconstructors,yourassignmentoperators,andyourother
memberfunctionssuchthateachpointermemberoftheclassisalwayseither
pointingtovalidmemoryorisnull,youcanmerrilydeleteawayinthe
destructorwithoutregardforwhetheryoueverusednewforthepointerin
question.


There'snoreasontogetfascistaboutthisItem.Forexample,youcertainlydon't

wanttousedeleteonapointerthatwasn'tinitializedvianew,and,exceptinthe
caseofsmartpointerobjects(seeItemM28),youalmostneverwanttodeletea
pointerthatwaspassedtoyouinthefirstplace.Inotherwords,yourclass
destructorusuallyshouldn'tbeusingdeleteunlessyourclassmemberswerethe
oneswhousednewinthefirstplace.
Speakingofsmartpointers,onewaytoavoidtheneedtodeletepointermembers
istoreplacethosememberswithsmartpointerobjectslikethestandardC++
Library'sauto_ptr.Toseehowthiscanwork,takealookatItemsM9andM10.
BacktoItem5:Usethesameformincorrespondingusesofnewanddelete.
ContinuetoItem7:Bepreparedforout-of-memoryconditions.


BacktoItem6:Usedeleteonpointermembersindestructors.
ContinuetoItem8:Adheretoconventionwhenwritingoperatornewandoperatordelete.

Item7:Bepreparedforout-of-memoryconditions.
Whenoperatornewcan'tallocatethememoryyourequest,itthrowsan
exception.(Itusedtoreturn0,andsomeoldercompilersstilldothat.Youcan
makeyourcompilersdoitagainifyouwantto,butI'lldeferthatdiscussionuntil
theendofthisItem.)Deepinyourheartofhearts,youknowthathandlingoutof-memoryexceptionsistheonlytrulymoralcourseofaction.Atthesametime,
youarekeenlyawareofthefactthatdoingsoisapainintheneck.Asaresult,
chancesarethatyouomitsuchhandlingfromtimetotime.Likealways,
perhaps.Still,youmustharboralurkingsenseofguilt.Imean,whatifnew
reallydoesyieldanexception?
Youmaythinkthatonereasonablewaytocopewiththismatteristofallbackon
yourdaysinthegutter,i.e.,tousethepreprocessor.Forexample,acommonC
idiomistodefineatype-independentmacrotoallocatememoryandthencheck
tomakesuretheallocationsucceeded.ForC++,suchamacromightlook
somethinglikethis:
#defineNEW(PTR,TYPE)\

try{(PTR)=newTYPE;}\
catch(std::bad_alloc&){assert(0);}

("Wait!What'sthisstd::bad_allocbusiness?",youask.bad_allocisthetype
ofexceptionoperatornewthrowswhenitcan'tsatisfyamemoryallocation
request,andstdisthenameofthenamespace(seeItem28)wherebad_allocis
defined."Okay,"youcontinue,"what'sthisassertbusiness?"Well,ifyoulook
inthestandardCincludefile<assert.h>(oritsnamespace-savvyC++
equivalent,<cassert>—seeItem49),you'llfindthatassertisamacro.The
macrocheckstoseeiftheexpressionit'spassedisnon-zero,and,ifit'snot,it
issuesanerrormessageandcallsabort.Okay,itdoesthatonlywhenthe
standardmacroNDEBUGisn'tdefined,i.e.,indebugmode.Inproductionmode,
i.e.,whenNDEBUGisdefined,assertexpandstonothing—toavoidstatement.
Youthuscheckassertionsonlywhendebugging.)
ThisNEWmacrosuffersfromthecommonerrorofusinganasserttotesta
conditionthatmightoccurinproductioncode(afterall,youcanrunoutof
memoryatanytime),butitalsohasadrawbackspecifictoC++:itfailstotake


Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×