配色: 字号:
forward declaration of class
2013-09-11 | 阅:  转:  |  分享 
  
UsingIncomplete(Forward)Declarations

DavidKieras,EECSDept.,Univ.ofMichigan

December19,2012

Anincompletedeclarationisthekeywordclassorstructfollowedbythenameofaclassorstructuretype.Ittellsthe

compilerthatthenamedclassorstructtypeexists,butdoesn''tsayanythingatallaboutthememberfunctionsorvariablesoftheclass

orstruct;thisomissionmeansthatitisa(seriously)incompletedeclarationofthetype.Usuallythecompilerwillbesuppliedwiththe

completedeclarationlaterinthecompilation,whichiswhyanincompletedeclarationisoftencalledaforwarddeclaration-itisan

advanceforwardannouncementoftheexistenceofthetype.Sinceanincompletedeclarationdoesn''ttellthecompilerwhatisinthe

classorstruct,untilthecompilergetsthecompletedeclaration,itwon''tbeabletocompilecodethatreferstothemembersoftheclass

orstruct,orrequiresknowingthesizeofaclassorstructobject(toknowthesizerequiresknowingthetypesofthemembervariables)

Useanincompletedeclarationinaheaderfilewheneverpossible.Byusinganincompletedeclarationinaheaderfile,wecan

eliminatetheneedto#includetheheaderfilefortheclassorstruct,whichreducesthecoupling,ordependencies,betweenmodules,

resultinginfastercompilationsandeasierdevelopment.Ifthe.cppfileneedstoaccessthemembersoftheclassorstruct,itwillthen

#includetheheadercontainingthecompletedeclaration.

Inthefollowingdiscussion,Xwillbetheclassorstructtypethatisbeingincompletelydeclared,andA.hwillbetheheaderfile

thatcontainstheincompletedeclarationofX.A.cppwillbetheimplementationfilewhichwilltypically#includethecomplete

declarationinX.hsothatitcanmakefulluseofX.Insteadof"classorstruct",we''lljustsay"class"inwhatfollows.

Whenwillanincompletedeclarationworkinaheaderfile?

1.IftheclasstypeXappearsonlyasthetypeofaparameterorareturntypeinafunctionprototype.Whenitcomestimeforthe

compilertoactuallycompileacalltothefunctioninA.cpp,itwillhavethecompletedeclarationXsothatitknowshowtogenerate

thecodeforthecall-forexample,itwillneedtoknowhowbiganobjecttopushontothefunctioncallstack.Buttheheaderfile

needsonlytheincompletedeclarationtosuccessfullydeclareafunctionthattakesanXparameterorreturnsanXvalue.Forexample,

wecoulddeclareafunctionprototypeinA.hlikethis:

!clasX;

!Xfo(Xx);

2.IftheclasstypeXisreferredtoonlybypointer(X)orreference(X&),evenasamembervariableofaclassdeclaredinA.h.

Thecompilerdoesnothavetoknowhowbiganobjectis,norwhatitscontentsare,tounderstandapointer(orreference)tothattype

ofobject(referencesareusuallyimplementedintermsofpointers).Thisisbecauseanaddressisanaddress,regardlessofwhatkind

ofthingisatthataddress,andaddressesarealwaysthesamesizeregardlessofwhattheypointto.Thecompilercanenforcetype-

safetyofpointersperfectlywellwithouthavingtoknowanythingmorethanthetypenameaboutthepointed-toobjects.SoA.hcould

containsomethinglikethefollowing:

!clasX;

!clasA{

!/othermembers/

!private:

!!Xx_ptr;

!!X&x_ref;

!};

Ofcourse,anycodethatactuallydereferencesthepointer,orusesmembervariablesorfunctionsofX,orrequiresknowinghow

bigXis,willrequirethecompletedeclaration;thustheA.cppfilewilltypically#include"X.h".

3.IfyouareusinganopaquetypeXasamembervariableofaclassdeclaredinA.h.Thisisatypereferredtoonlythrougha

pointer,andwhosecompletedeclarationisnotsupposedtobeavailable,andisnotinanyheaderfile.Thusanincompletedeclaration

ofthetypeistheonlydeclarationyourcodewillevermakeorneedeitherinA.horA.cpp.

Whenwillanincompletedeclarationnotworkinaheaderfile?

1.IfyourA.hheaderfiledeclaresaclassAinwhichtheincompletelydeclaredtypeXappearsasthetypeofamembervariable.

TheclasstypeAitselfcannotbecompletelydeclaredunlessthecompileratleastknowshowbiganobjectofthattypeis,which

requiresthatallofthethemembervariabletypesbecompleteddeclared.Thefollowingwillproduceacompileerror:

!clasX;

!clasA{

!private:

!!Xx_member;/eror-can''tdeclareamembervariableofincompletetype!

!};

1

2.IfyourA.hheaderfiledeclaresaclassAinwhichtheincompletelydeclaredtypeXisabaseclass(AinheritsfromX).Theclass

typeAitselfcannotbecompletelydeclaredunlessthecompileratleastknowshowbiganobjectofthattypeis,whichrequiresthatit

knowthetypesofallofthethemembervariablesinthebaseclass;thecompletedeclarationisnecessaryforthis.Sothefollowing

alsofailstocompile:

classX;

classA:publicX{//error-baseclassisincompletetype!

3.Ifyoudon''tactuallyknowthenameofthetype.Youcan''tforwarddeclareatypeunlessyouknowitscorrectname.Thiscanbe

aproblemwithsomeofthetypesdefinedintheStandardLibrary,wherethenormalnameofthetypeisactuallyatypedeffora

particulartemplateinstantiatedwithsomeothertype,usuallywithmultipletemplateparameters.Forexample,thefollowingwillnot

worktoincompletelydeclarethestd::stringclass:

classstd::string;

Thiswon''tworkbecausestd::stringisactuallyatypedefforthestd::basic_string<>templateinstantiatedtowork

withchardata(asopposedtowidecharacterdata).Similarly,thestd::istream,std::ostream,andothermembersof

aretypedefsfortemplatedclassesinstantiatedforchardata.Comingupwiththecorrectincompletedeclarationof

thesetypesinvolvesknowingsomeprettycrypticstuffabouttheexacttemplateparametersinvolved.

Fortunately,theStandardLibraryprovidesaheaderfilethatcontainsacompletesetofforwarddeclarationsforthe

types,called.Theruleisto#includeinsteadofwheneverpossible.Unfortunately,thereisno

suchhandyforwarddeclarationfileforthestd::stringfamily,soplanon#includingtoaccessthecomplete

declaration–anincompletedeclarationisnotpracticalinthiscase.Sothefollowingisanexampleofwhatyoumustputinaheader

fileinbothofthesecases:

!#include!/forwarddeclarationsofiostreamclases

!#include!/completedeclarationofstringclas

!

!voidwrite_string_to_file(conststd:string&label_text,std:ofstream&outfile);

Declaringclasses(orstructs)thatrefertoeachother

Forwarddeclarationsareessentialforthefollowingproblem:Supposewehavetwoclasseswhosememberfunctionsmakeuseof

eitherparametersormemberfunctionsoftheotherclass.Hereisasimple,butnastyexample-onlyacoupleofmemberfunctionsare

shown,buthavingadditionalmembervariablesandfunctionsdoesnotchangetheproblem.

classA{

public:

!voidfo(Bb)!/Ex.1:aparameteroftheotherclastype

!!{

!!!b.zap();!/Ex.2:calamemberfunctionoftheotherclas

!!}

!voidgo()

!!{/whatever/}

};

classB{

public:

!voidzot(Aa)!/Ex.3:aparameteroftheotherclastype

!!{

!!!a.go();!/Ex.4:calamemberfunctionoftheotherclas

!!}

!voidzap()

!!{/whatever/}

};

Thecompilerwillbalkwhenittriestocompilethisbitofcode.TheproblemisthatwhenitcompilesthedeclarationofclassA,it

won''tbeabletounderstandthelinelabeledEx.1-ithasn''tseenadeclarationofByet.ObviouslyitwouldhaveaproblemwithEx.2,

becauseitdoesn''tknowaboutfunctionzapeither.ButifthecompilercouldsomehowunderstandtheclassAdeclaration,itwould

thenhavenoproblemwithclassB,becauseitwouldalreadyknowaboutclassAwhenitseesEx.3andEx.4.However,sincethe

compilercan''tcompiletheclassAdeclaration,itwillnotbeabletocompiletheclassBdeclarationeither.

Sometimesyoucanfixthisdeclaration-orderingproblembysimplyputtingthedeclarationsinreverseorder,sothatthecompiler

hasalreadyseeneverythingitneedstoknowwhenitseeseachdeclaration.Butinthisdiabolicalexample,reversingthedeclarations

won''tworkbecauseclassBusesthingsinclassA-wewouldjustgetthesamecompilererrormessagesontheotherclass.Sowe

2

mightaswellstickwiththedeclarationsinthisorder.Tosavespaceinwhatfollows,thepartsoftheexampledeclarationsthataren''t

directlyrelevantwillbeomitted.

WhatweneedissomewayoftellingthecompilerjustenoughaboutclassBtoallowittocompiletheclassAdeclaration,andput

offrequiringanymoreinformationaboutBuntilithasprocessedthecompleteBdeclaration.

Incompletedeclarationstotherescue!Oops,notyet

AddingaforwarddeclarationofBtotheaboveexamplewouldlooklikethis:

classB;!//forwardincompletedeclaration-classBwillbefullydeclaredlater

classA{

public:

!!voidfo(Bb)!/Ex.1:aparameteroftheotherclastype

!!{

!!!b.zap();!!/Ex.2:calamemberfunctionoftheotherclas

!!}

!!/restomitedtosavespace/

};

classB{

public:

/restomittedtosavespace/

};

Thishelps,butthereisstillaproblem.WhenthecompilerseeslineEx.1,itishappywiththeclassnameBbecauseithasbeen

toldthatBisthenameofaclass.ButEx.2isstillaproblembecausethecompilerhasn''tseenthewholeclassdeclarationofB,andso

doesn''tknowhowtogeneratemachinecodeforacalltothefunctionnamedzap.Thisisafatalcompileerror.Sojustprovidingthe

nameofto-be-declaredclassinaforwarddeclarationisnotenoughinthiscase.

Abitofre-arrangingdoesthetrick!

Here''showwesolvethisproblem.Wetakethefunctiondefinitionsoutofthefirstclassdeclarations,sothatthecompilerwillsee

allofthesecondclassbeforeithastocompilethecodeforthefirstclassfunctions.Hereishowtheexamplewouldlook:

classB;!//forwardincompletedeclaration-classBwillbefullydeclaredlater

classA{

public:

!voidfo(Bb);/Ex.1:

!/restomitedtosavespace/

};

classB{

public:

/restomittedtosavespace/

};

//thefunctiondefinitionlaterorinaseparate.cppfile

voidA::foo(Bb)!//Ex.1B:defineA''sfoofunctionaftertheBdeclaration!!

{

!b.zap();!!/Ex.2:calamemberfunctionoftheotherclas

}

Thisexamplenowcompilessuccessfully;hereisthestory:WhenthecompilerseeslineEx.1,whichisnowjustafunction

prototype,itknowsthatBisthenameofaclass(fromtheforwarddeclaration),andsincewehaveonlyafunctionprototypehere,we

arenolongertryingtocallthestill-unknownzapmemberfunctioninB.Thecompilersimplyrecordsthefooprototypeand

continues.ThecompilercanthenhandletheclassBdeclarationwithnoproblembecauseitgotallitneededfromtheclassA

declaration.

InlineEx.1B,classA''sfunctionfooisdefinedoutsideoftheclassAdeclaration,afterthecompilerhasseenthecompleteclassB

declaration.Noticethescopeoperator::thattellsthecompilerthatthisfooistheoneprototypedintheclassAdeclaration.The

compilercanhandlethepreviouslyproblematicLineEx.2becauseitisnowknowsaboutB''szapmemberfunction.

3

Membervariablesinrelatedclasses:Alotmessier

Theabovestoryisaboutfunctioncallsandparameters.Whataboutmembervariables?Let''susethesameclasses,andtryto

declaremembervariableswhosetypeistheotherclass:

classB;!//forwardincompletedeclaration-classBwillbefullydeclaredlater

classA{

public:

!!/restomitedtosavespace/

private:

!Bb_member;/Ex.5

};

classB{

public:

/restomittedtosavespace/

private:

!Aa_member;!/Ex.6

};

IfyouguessedthatthecompilerwillchokeinthedeclarationofAatEx.5,youareright!Inspiteoftheincompletedeclarationof

B,thecompilercan''tfullycomprehendthedeclarationofAbecauseitrequiresknowingthedefinition(atleastthesize)ofaBobject,

whichithasn''tseen.Incontrast,Ex.6willbefine,ifwecangetclassA''sdeclarationtowork.Wecangetcorrectlycompilingcodeby

usingapointertoaBobjectasamembervariableinsteadofaBobject,takingadvantageofthepropertiesofpointertypesmentioned

above:

classB;!//forwardincompletedeclaration-classBwillbefullydeclaredlater

classA{

public:

!!/restomitedtosavespace/

private:

!Bb_member;/Ex.5-noproblem

};

classB{

public:

/restomittedtosavespace/

private:

!Aa_member;!/Ex.6-noproblem

};

UsingapointertotheBobjectgivesuscorrectlycompilingcode,butwehaveanewproblem:WhenaBobjectcomesinto

existence,itwillautomagicallygetafullyinitializedAobjectasamembervariable.Incontrast,whenanAobjectcomesinto

existence,itwillnotautomaticallygetafullyfunctioningb_memberobjecttouse.Yourcode(e.g.intheAconstructor)willhaveto

knowoforcreate(e.g.withnew)asuitableBobjectandsettheb_membertopointtoit.Thisisugly,butinthissituation,it''sthebest

youcando.

WhataboutusingaB&referenceinsteadofaBpointerforb_member?Itwillalsogivecorrectlycompilingcode,andthesame

problemwillarisethatwhenanAobjectiscreated,aBobjectneedstobeinexistenceforthereferencetoreferto.Thecompilerinsists

thatyouinitializeareference-typevariablewiththereferred-toobjectatthepointofdefinition.Referencesmustalwaysrefertoa

singlesomething-theycan''tbechangedtorefertoadifferentobject.Theonlywaytoinitializeareference-typemembervariableisin

aconstructorinitializer,whichmeansthattheBobjectneedstoexistbeforetheAconstructoriscalled.Thissetofconstraintsmeans

reference-typemembervariablesareusuallyinconvenient,andsoyourarelyseethem,andgenerallyonlywhenthecleansyntaxofa

referencecomparedtoapointerisespeciallyuseful(suchasforostreamobjects).

Afinalnote:Oftentheexplicitincompletedeclarationofaclassorstructisredundant,becausethefirstdeclarationofa

pointerorreferenceusingastructorclassnameimplicitlymakesanincompletedeclaration.However,itisgoodprogramming

practicetowritetheexplicitincompletedeclarationtomakeitobvioustothereaderthattheclassorstructdeclarationisnotyet

knownatthispoint.Otherwise,theymightbethinking"DidImisssomething?Istheresomethinginoneofthe#included.hfilesI

didn''tknowabout?"

4

献花(0)
+1
(本文系windycitybo...首藏)