Dynamic Instantiation in C++ (The Prototype Pattern)

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 6

DynamicInstantiationinC++(ThePrototypePattern)

Unfortunately,C++doesn'thavebuiltinsupportfordynamicinstantiation,buttheprototypepatternprovidesa
standardwaytoaddthisfeaturetoC++programs:
Prototype[Go4]
Problem
A"factory"classcan'tanticipatethetypeof"product"objectsitmustcreate.
Solution
DeriveallproductclassesfromanabstractProductbaseclassthatdeclaresapurevirtualclone()method.TheProductbase
classalsofunctionsastheproductfactorybyprovidingastaticfactorymethodcalledmakeProduct().Thisfunctionusesa
typedescriptionparametertolocateaprototypeinastaticprototypetablemaintainedbytheProductbaseclass.The
prototypeclonesitself,andthecloneisreturnedtothecaller.

StaticStructure
WesawthatJavaandMFCbothrequiredsomecommonbaseclassforallclassesthatcanbedynamically
instantiated:ObjectinJavaandCObjectinMFC.ThePrototypePatternalsorequiresacommonbaseclass,
whichwecallProduct.Inadditiontoservingasthebaseclassforallproducts,theProductclassmaintainsa
statictablethatholdsassociationsbetweennamesofProductderivedclasses("Product1"forexample)anda
correspondingprototypicalinstanceoftheclass.Italsoprovidesastaticfunctionforaddingnewassociationsto
theprototypetable(addPrototype),andastaticfactorymethodforcreatingproducts(makeProduct).Finally,
Productisanabstractclassbecauseitcontainsapurevirtualclone()method.Thefollowingdiagramshowsthe
ProductclasswiththreeProductderivedclasses.

TestProgram

Beforedescribingtheimplementation,let'slookatatestprogram.Ourtestdriver,main(),beginsbydisplaying
theprototypetable.Itthenentersaperpetualloopthatpromptstheuserforthenameofaproducttype,usesa
staticfactorymethodtoinstantiatethetypeentered,thenusestheRTTIfeatureofC++todisplaythetypeof
productcreated:

intmain()
intmain()
{
stringdescription
Product*p=0
cout<<"PrototypeTable:\n"
cout<<Product::protoTable<<endl
while(true)
{
cout<<"Enterthetypeofproducttocreate:"
cin>>description
p=Product::makeProduct(description)
cout<<"Typeofproductcreated="
cout<<typeid(*p).name()<<endl
deletep
}
return0
}

ProgramOutput

Curiously,thetestprogramproducesoutputbeforemain()iscalled:
addingprototypeforProduct1
done
addingprototypeforProduct2
done
addingprototypeforProduct3
done

Apparently,prototypesofthreeproductclasseshavebeenaddedtotheprototypetable.Thisisconfirmedwhen
theprototypetableisdisplayedatthebeginningofmain().Itshowspairsconsistingoftheproductnameandthe
addressofthecorrespondingprototype:
PrototypeTable:
{
(Product1,0xba1cc)
(Product2,0xba1dc)
(Product3,0xba1ec)
}

Nowtheloopbegins.Theusercreatesinstancesofeachoftheproductclasses,asconfirmedbyruntimetype
identification.(WeareusingtheDJGPPcompilerinthisexample.)Whentheuserattemptstocreateaninstance
ofanunknownclass,anerrormessageisdisplayedandtheprogramterminates:
Enterthetypeofproducttocreate:Product1
Typeofproductcreated=8Product1
Enterthetypeofproducttocreate:Product2
Typeofproductcreated=8Product2
Enterthetypeofproducttocreate:Product3
Typeofproductcreated=8Product3
Enterthetypeofproducttocreate:Product4
Error,prototypenotfound!

TheProductBaseClass

ThemainjoboftheProductbaseclassistomaintaintheprototypetable.Sincetheprototypetablecontains
associationsbetweentypenames(string)andtheaddressesofcorrespondingprototypes(Product*),wedeclareit
asastaticmapoftype:
map<string,Product*>

TheProductbaseclassalsoprovidesastaticfactorymethodfordynamicallycreatingproducts(makeProduct)
TheProductbaseclassalsoprovidesastaticfactorymethodfordynamicallycreatingproducts(makeProduct)
andastaticmethodforaddingprototypestotheprototypetable(addPrototype).Lastly,thePrototypePattern
requiresthateachproductknowshowtocloneitself.Thiscanbeenforcedbyplacingapurevirtualclonemethod
intheProductbaseclass(thisideaisrelatedtotheVirtualBodyPatterninChapter4).Here'sthedeclaration:
classProduct
{
public:
virtual~Product(){}
virtualProduct*clone()const=0
staticProduct*makeProduct(stringtype)
staticProduct*addPrototype(stringtype,Product*p)
staticmap<string,Product*>protoTable
}

Recallthatthedeclarationofastaticclassvariable,likeprotoTable,isapuredeclarationthatsimplybindsa
name(protoTable)toatype(map<>).Novariableisactuallycreated.Instead,thismustbedonewithaseparate
variabledefinition.AssumingtheProductclassisdeclaredinafilecalledproduct.h,wemightwanttoplacethe
definitionoftheprototypevariableatthetopofthefileproduct.cpp:
//product.cpp
#include"product.h"
map<string,Product*>Product::protoTable

Ourproduct.cppimplementationfilealsocontainsthedefinitionsofthemakeProduct()factorymethodandthe
addPrototype()function.

ThemakeProduct()factorymethodusestheglobalfind()functiondefinedinutil.h(whichislistedinAppendix3
andshouldbeincludedatthetopofproduct.h)tosearchtheprototypetable.Theerror()functiondefinedinutil.h
isusedtohandletheerrorifthesearchfails.Otherwise,theprototypelocatedbythesearchiscloned,andthe
cloneisreturnedtothecaller:
Product*Product::makeProduct(stringtype)
{
Product*proto
if(!find(type,proto,protoTable))
error("prototypenotfound")
returnproto>clone()
}

TheaddPrototype()functionhastwoparametersrepresentingthenameofaProductderivedclass("Product1"
forexample)andapointertoaprototypicalinstanceofthatclass.Thefunctionsimplyaddsanewassociationto
theprototypetable.Fordebuggingpurposes,thestatementissandwichedbetweendiagnosticmessages.Iffor
somereasonwefailtoaddaparticularprototypetotheprototypetable,wewillknowexactlywhichonecaused
problems.(Moreonthislater.)Finally,noticethattheprototypepointerisreturned.Thepurposeofthisreturn
statementwillalsobeexplainedlater.
Product*Product::addPrototype(stringtype,Product*p)
{
cout<<"addingprototypefor"<<type<<endl
protoTable[type]=p
cout<<"done\n"
returnp//handy
}

CreatingProductDerivedClasses
CreatingProductDerivedClasses

Onemeasureofqualityforaframeworkishoweasyitistocustomize.Frameworkswithheavyoverhead(i.e.,
thatrequirecustomizerstowritehundredsoflinesofcodebeyondwhattheyalreadyhavetowrite)areoften
veryunpopular.HowmuchextraworkisittoderiveaclassfromourProductbaseclass?Onlyfourextralinesof
codearerequired.

AssumewewanttodeclareaclassnamedProduct1inafilenamedproduct1.h.Wewanttobeableto
dynamicallyinstantiateProduct1,sowemustderiveitfromtheProductbaseclass.Theboldfacelinesshowthe
overheadimposedbytheProductbaseclass:
//product1.h
#include"product.h"

classProduct1:publicProduct
{
public:
IMPLEMENT_CLONE(Product1)
//etc.
}

AssumetheProduct1classisimplementedinafilenamedproduct1.cpp.Wemustaddasinglelinetothatfile,
too:
//product1.cpp
#include"product1.h"
MAKE_PROTOTYPE(Product1)
//etc.

Macros

IMPLEMENT_CLONE()andMAKE_PROTOTYPE()aremacrosdefinedinproduct.h.Recallthatmacrocalls
areexpandedbytheCpreprocessorbeforecompilationbegins.Forexample,ifaprogrammerdefinesthe
followingmacro:
#definePI3.1416

AllcallsoroccurrencesofPIinaprogramarereplacedbythevalue3.1416.

Macroscanalsohaveparameters.Inthiscaseanargumentisspecifiedwhenthemacroiscalled,andthe
expansionprocessautomaticallysubstitutestheargumentforalloccurrencesofthecorrespondingparameterin
themacro'sbody.

Forexample,eachProductderivedclassmustimplementthepurevirtualclone()functionspecifiedinthe
Productbaseclass.Infact,theimplementationsaresimpleandwon'tvarymuchfromoneclasstothenext.
Thereisarisk,however,thatprogrammersmightgettoocreativeandcomeupwithanimplementationthat'stoo
complexorjustplainwrong.

Toreducethisrisk,weprovidetheIMPLEMENT_CLONE()macro,whichisparameterizedbythetypeof
producttoclone.Themacrobodyistheinlineimplementationoftherequiredclonefunction:
#defineIMPLEMENT_CLONE(TYPE)\
Product*clone()const{returnnewTYPE(*this)}

(Noticethatmacrodefinitionsthespanmultiplelinesuseabackslashcharacterasalineterminator.)

WeplacedacalltothismacrointhedeclarationoftheProduct1class:
classProduct1:publicProduct
{
public:
IMPLEMENT_CLONE(Product1)
//etc.
}

AfterpreprocessorexpandsthiscallthedeclarationofProduct1willlooklikethis:
classProduct1:publicProduct
{
public:
Product*clone()const{returnnewProduct1(*this)}
//etc.
}

NoticethattheTYPEparameterinthemacrobodyhasbeenreplacedbytheProduct1argument,formingacall
totheProduct1copyconstructor.Readersshouldverifythattheimplementationcorrectlyreturnsacloneofthe
implicitparameter.

CreatingPrototypes

Theoutputproducedbyourtestprogramshowedthreeprototypeswerecreatedandaddedtotheprototypetable
beforemain()wascalled.Howwasthatdone?Ingeneral,howcanprogrammersarrangetohavecodeexecuted
beforemain()?Isn'tmain()thefirstfunctioncalledwhenaC++programstarts?

Actually,wecanarrangetohaveanyfunctioncalledbeforemain(),providedthatfunctionhasareturnvalue.For
example,thefunction:
inthello()
{
cout<<"Hello,World\n"
return0//abogusreturnvalue
}

willbecalledbeforemain()ifweuseitsreturnvaluetoinitializeaglobalvariable:
intx=hello()//x=abogusglobal

Thiscanbeverifiedbyplacingadiagnosticmessageatthebeginningofmain()andobservingthatthe"Hello,
World"messageappearsfirst.

RecallthattheaddPrototype()functionreturnedapointertotheprototype.Ifweusethisreturnvaluetoinitialize
abogusglobalProductpointervariable:
Product*Product1_myPrototype=
Product::addPrototype("Product1",newProduct1())

thenthecalltoaddPrototype()willprecedethecalltomain().Inprinciple,wecanbuildtheentireprototype
tablebeforemain()iscalled.

OurMAKE_PROTOTYPE()macroexpandsintodefinitionsliketheoneabove:
#defineMAKE_PROTOTYPE(TYPE)\
Product*TYPE##_myProtoype=\
Product::addPrototype(#TYPE,newTYPE())

Duringexpansion,themacroparameter,TYPE,willbereplacedbythemacroargument,Product1forexample,
inthreeplaces.First,the##operatorisusedtoconcatenatethetypenamewith_myPrototype.Inourexample
inthreeplaces.First,the##operatorisusedtoconcatenatethetypenamewith_myPrototype.Inourexample
thisproducesProduct1_myPrototype,a(hopefully)uniquenameforaglobalvariable.

Second,the#operatorisusedtostringifytheargument.IftheargumentisProduct1,#TYPEwillbereplacedby
"Product1",thestringnameofthetype.

Finally,thelastoccurrenceoftheTYPEparameterwillbereplacedbyacalltothedefaultconstructorspecified
bytheargument.

LinkerIssues

Addingentriestotheprototypetablebeforemain()iscalledisrisky.Recallthatthedefinitionoftheprototype
tableoccursinproduct.cpp,whilethecallstotheMAKE_PROTOTYPE()macrooccurinthefilesproduct1.cpp,
product2.cpp,andproduct3.cpp.Thesefileswillcompileintotheobjectfilesproduct.o,product1.o,product2.o,
andproduct3.o,respectively,whichwillbelinkedwithmain.obythelinker(ld).Theactuallinkcommandmight
looklikethis:
lddemomain.oproduct.oproduct1.oproduct2.oproduct3.o

Supposetheorderofobjectfileargumentspassedtothelinkerismodified:
lddemomain.oproduct1.oproduct.oproduct2.oproduct3.o

Intheexecutableimageproducedbythelinker,thedeclaration:
Product*Product1_myPrototype=
Product::addPrototype("Product1",newProduct1())

containedinproduct1.omayprecedethecreationoftheprototypetablecontainedinproduct.o:
map<string,Product*>Product::protoTable

SincethecalltoaddPrototype()attemptstoinstallapairintotheprototypetable,thiswillresultinamysterious
programcrashthatdefinesmostdebuggers,becausetheproblemoccursbeforemain()iscalled.(Inourcasethis
bugwillbeeasytocatchthankstothediagnosticmessagesprintedbyaddPrototype.)
Theproblemiseasilyrectifiedifweabandontheideaofaddingentriestotheprototypetablebeforemain()is
called.Inthiscasethemacrocallswouldbemadeatthetopofmain():
intmain()
{
MAKE_PROTOTYPE(Product1)
MAKE_PROTOTYPE(Product2)
MAKE_PROTOTYPE(Product3)
//etc.
}

You might also like