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