CS106L From Stanford
CS106L From Stanford
*/ virtual ~FunctionBase() {}
})
/* *emplate derived class that e#ecutes a speci!ic type o! !unction. */ template +typename ,naryFunction- class Function.mpl: public FunctionBase { public: e#plicit Function.mpl(,naryFunction !n) : !n(!n) {} virtual "et e#ecute(const $r%& val) { return !n(val)) } virtual Function.mpl* clone() const { return ne/ Function.mpl(*this)) } ,naryFunction !n) })
CS106L
/* alls the stored !unction. */ virtual "et e#ecute(const $r%& val) ' () virtual FunctionBase* clone() const ' ()
Acknowledgements
_________________________________________________________________________________________________________
This course reader represents the culmination of two years' work on CS106 and its course handouts! "either the class nor this reader would have #een possi#le without $ulie %elenski's support and &enerosity durin& CS106 's infancy! ' stron&ly encoura&e you to take one of $ulie's classes ( you will not #e disappointed! ''d also like to e)tend thanks to all of the CS106 students ''ve had the pleasure of teachin& over the years! 't is truly a *oy to watch students li&ht up when they see e)actly what C++ can do! The lon& hours that went into this course reader would not have #een possi#le without the knowled&e that students are &enuinely interested in the material! ,dditionally- ' would like to thank the #rave souls who were &enerous enou&h to proofread draft versions of this course reader! .in /uan& and Steven 0u offered particularly apt advice on style and &rammar! 'lya Sherman &ave wonderful su&&estions on typesettin& and layout and cau&ht many errors that slipped under my radar! Kyle Knutson helped dou#le1check the correctness of the code in the e)tended e)amples! 2avid 3old#latt helped me stay on top of recent developments in C++0) and pointed out how to make many of the ST practice pro#lems truer to the spirit of the li#rary! Sam Schrei#er provided e)cellent advice a#out the overall structure of the reader and was the inspiration for the 4Criti5uin& Class 2esi&n6 chapter! eonid Shamis astutely su&&ested that ' e)1 pand the section on development environments! 7rittney 8raser's amazin& feed#ack made many of the e)amples easier to understand and prevented several ma*or errors from makin& it into this reader! This is the first edition of this course reader! 0hile much of the content is taken directly from previous 5uarters' course handouts- over half of this course reader is entirely new! There are certainly layout pro#lems- typoz- &rammatic1 ally errors- and spelin& misstakes that have made it into this version! 'f you have any comments- corrections- or su&&estions- please send me an email at htiek9cs!stanford!edu! This course reader and its contents- e)cept for 5uotations from other sources- are all : ;00< Keith Schwarz! 'f you would like to copy this course reader or its contents- send me an email and ''d #e &lad to see how ' can help out!
Table of Contents
Introduction..........................................................................................................................................................1 Chapter 0= 0hat is C++>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!? Chapter 1= 3ettin& Started!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!< Chapter ;= C++ without genlib.h!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1@ Introduction to the C++ Standard Library...................................................................................................... 1 Chapter A= Streams!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;? Chapter B= ST Containers- Cart '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!B1 Chapter ?= D)tended D)ample= Snake!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!?A Chapter 6= ST 'terators!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!E1 Chapter @= ST Containers- Cart ''!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<1 Chapter E= D)tended D)ample= 8inite ,utomata!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<< Chapter <= ST ,l&orithms!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!11A Chapter 10= D)tended D)ample= Calindromes!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1;@ C++ Core Language !eatures..........................................................................................................................1"" Chapter 11= Cointers and Feferences!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1A? Chapter 1;= C Strin&s!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1B@ Chapter 1A= The Creprocessor!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1?@ Chapter 1B= 'ntroduction to Templates!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1EA Chapter 1?= const!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;0? Chapter 16= D)tended D)ample= UnionFind!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;;? Chapter 1@= Gem#er 'nitializer ists!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;A@ Chapter 1E= static!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;B? Chapter 1<= Conversion Constructors!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;?? Chapter ;0= Copy Constructors and ,ssi&nment Hperators!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;61 Chapter ;1= D)tended D)ample= Criti5uin& Class 2esi&n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;@< Chapter ;;= Hperator Hverloadin&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!;<1 Chapter ;A= D)tended D)ample= SmartPointer!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!A1? Chapter ;B= D)tended D)ample= DimensionType!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!AA1 Chapter ;?= D)tended D)ample= grid!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!AB? Chapter ;6= 8unctors!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!A6? Chapter ;@= 'ntroduction to D)ception /andlin&!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!A<1 Chapter ;E= D)tended D)ample= 3auss1$ordan Dlimination!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!B0? Chapter ;<= 'ntroduction to 'nheritance!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!B;< Chapter A0= D)tended D)ample= Function!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!B6A #ore to $%&lore................................................................................................................................................'() Chapter A1= C++0)!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!BE1 Chapter A;= 0here to 3o 8rom /ere!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!B<@ A&&endices........................................................................................................................................................*+1 ,ppendi) 0= Govin& from C to C++!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!?0A ,ppendi) 1= Solutions to Cractice Cro#lems!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!?1? ,ibliogra&hy.....................................................................................................................................................*"( Inde%..................................................................................................................................................................*")
-art .ero
Introduction
Suppose we want to write a function that computes the avera&e of a list of num#ers! Hne implementation is &iv1 en here=
double GetAverage(double arr[], int numElems) { double total = 0.0; for(int h = 0; h numElems; !!h) total != arr[h] " numElems; # return total;
2on't panic if you don't understand any of this code ( you're not e)pected to at this point ( #ut even without an understandin& of how either of these functions work it's clear that they are implemented differently! ,lthou&h #oth of these functions are valid C++ and accurately compute the avera&e- e)perienced C++ pro&rammers will likely prefer the second version to the first #ecause it is safer- more concise- and more versatile! To understand why you would prefer the second version of this function re5uires a solid understandin& of the C++ pro&ram1 min& lan&ua&e! "ot only must you have a firm &rasp of how all the lan&ua&e features involved in each solution work- #ut you must also understand the #enefits and weaknesses of each of the approaches and ultimately which is a more versatile solution! The purpose of this course is to &et you up to speed on C++'s lan&ua&e features and li#raries to the point where you are capa#le of not only writin& C++ code- #ut also criti5uin& your desi&n decisions and ar&uin& why the cocktail of lan&ua&e features you chose is appropriate for your specific application! This is an am#itious &oal#ut if you take the time to read throu&h this reader and work out some of the practice pro#lems you should #e in e)cellent C++ shape! /ho this Course is !or This course is desi&ned to au&ment CS1067IJ #y providin& a workin& knowled&e of C++ and its applications! C++ is an industrial1stren&th tool that can #e harnessed to solve a wide array of pro#lems- and #y the time you've completed CS1067IJ and CS106 you should #e e5uipped with the skill set necessary to identify solutions to comple) pro#lems- then to precisely and efficiently implement those solutions in C++! This course reader assumes a knowled&e of C++ at the level at which it would #e covered in the first two weeks of CS1067IJ! 'n particular- ' assume that you are familiar with the followin&=
1;1 0! 1! ;! A! B! ?! 6! @! /ow to print to the console Ki!e! *out and endlL Crimitive varia#le types Kint- double- etc!L The string type! enums and stru*ts. 8unctions and function prototypes! Cass1#y1value and pass1#y1reference! Control structures Kif- for- 'hile- do- s'it*hL! CS1067IJ1specific li#raries Kgenlib.h- sim$io.h- the ,2Ts- etc!L
Introduction
'f you are unfamiliar with any of these terms- ' recommend readin& the first chapter of Programming Abstractions in C++ #y Dric Fo#erts and $ulie %elenski- which has an e)cellent treatment of the material! These con1 cepts are fundamental to C++ #ut aren't that particular to the lan&ua&e ( you'll find similar constructs in C- $avaCython- and other lan&ua&es ( and so ' won't discuss them at &reat len&th! 'n addition to the lan&ua&e prere5uis1 ites- you should have at least one 5uarter of pro&rammin& e)perience under your #elt KCS106, should #e more than enou&hL! 0e'll #e writin& a lot of code- and the more pro&rammin& savvy you #rin& to this course- the more you'll take out of it! 0ow this 1eader is 2rgani3ed The course reader is lo&ically divided into four sections= 0! Introduction= This section motivates and introduces the material and covers information necessary to #e a workin& C++ pro&rammer! 'n particular- it focuses on the history of C++- how to set up a C++ pro*ect for compilation- and how to move away from the genlib.h trainin& wheels we've provided you in CS1067IJ! 1! The C++ Standard Library= C++ has a standard li#rary chock1full of pro&rammin& &oodies! 7efore movin& on to more advanced lan&ua&e features- we'll e)plore what the streams li#rary and ST have to offer! ;! C++ Core Language= C++ is an enormous lan&ua&e that affords &reat fle)i#ility and control over e)1 actly how your pro&rams e)ecute! This section discusses what tools are at your disposal and provides &uidelines for their proper usa&e! A! #ore to $%&lore= Unfortunately- this course reader cannot cover the entire C++ pro&rammin& lan&ua&e! This section helps set up your *ourney into C++ with a discussion of the future of C++ and relevant C++ resources! "otice that this course reader focuses on C++'s standard li#raries #efore em#arkin& on a detailed tour of its lan1 &ua&e features! This may seem #ackwards ( after all- how can you understand li#raries written in a lan&ua&e you have not yet studied> ( #ut from e)perience ' #elieve this is the #est way to learn C++! , comprehensive understandin& of the streams li#rary and ST re5uires a rich understandin& of templates- inheritance- functorsand operator overloadin&- #ut even without knowled&e of these techni5ues it's still possi#le to write nontrivial C++ pro&rams that use these li#raries! 8or e)ample- after a 5uick tour of the streams li#rary and #asic ST con1 tainers- we'll see how to write an implementation of the &ame Snake with an ,'1controlled player! ater- once we've e)plored the proper lan&ua&e features- we'll revisit the standard li#raries and see how they're put toðer! To &ive you a feel for how C++ looks in practice- this course reader contains ten e)tended e)amples that demon1 strate how to harness the concepts of the previous chapters to solve a particular pro#lem! ' strongly su&&est that you take the time to read over these e)amples and play around with the code! The e)tended e)amples showcase how to use the techni5ues developed in previous chapters- and #y seein& how the different pieces of C++ work toðer you will #e a much more capa#le coder! 'n addition- ''ve tried to conclude each chapter with a few prac1 tice pro#lems! Take a sta# at them ( you'll &et a much more nuanced view of the lan&ua&e if you do! Solutions to some of my favorite pro#lems are &iven in ,ppendi) Hne! D)ercises with solutions are marked with a dia1 mond KML!
Introduction
1A1
C++ is a lar&e lan&ua&e and it is impossi#le to cover all of its features in a sin&le course! To help &uide further e)ploration into C++ techni5ues- most chapters contain a 4Gore to D)plore6 section listin& important topics and techni5ues that may prove useful in your future C++ career! Su&&lemental 1eading This course reader is #y no means a complete C++ reference and there are many li#raries and lan&ua&e features that we simply do not have time to cover! /owever- the portions of C++ we do cover are amon& the most1com1 monly used and you should #e a#le to pick up the remainin& pieces on a need1to1know #asis! 'f you are inter1 ested in a more complete reference te)t- 7*arne Stroustrup's The C++ Programming Language, Third Edition is an e)cellent choice! 7e aware that TC++PL is not a tutorial ( it's a reference ( and so you will pro#a#ly want to read the relevant sections from this course reader #efore divin& into it! 'f you're interested in a hy#rid referenceItutorial- ' would recommend C++ Primer, Fourth Edition #y ippman- a*oie- and Goo! ,s for on1 line resources- the C++ 8,N ite at www!parashift!comIc++1fa51liteI has a &reat discussion of C++'s core lan1 &ua&e features! cplusplus!com has perhaps the #est covera&e of the C++ standard li#rary on the 'nternet- thou&h its discussion of the lan&ua&e as a whole is fairly limited! 2nward and !orward4
Dvery pro&rammin& lan&ua&e has its own distinct flavor influenced #y its history and desi&n! 7efore seriously studyin& a pro&rammin& lan&ua&e- it's important to learn why the lan&ua&e e)ists and what its o#*ectives are! This chapter covers a 5uick history of C++- alon& with some of its desi&n principles! An Abbre7iated 0istory of C++8 The story of C++ #e&ins with 7*arne Stroustrup- a 2anish computer scientist workin& toward his Ch2 at Cam1 #rid&e University! 8or his research- Stroustrup developed a simulator which modeled computers communicatin& over a network! Stroustrup chose to work in a lan&ua&e called Simula- at the time one of the foremost o#*ect1 oriented pro&rammin& lan&ua&es! ,s Stroustrup recalled- at first Simula seemed like the perfect tool for the *o#= 't was a pleasure to write that simulator! The features of Simula were almost ideal for the purposeand ' was particularly impressed #y the way the concepts of the lan&ua&e helped me think a#out the pro#lems in my application! The class concept allowed me to map my application concepts into the lan&ua&e constructs in a direct way that made my code more reada#le than ' had seen in any other lan&ua&e!!! ' had used Simula #efore!!! #ut was very pleasantly surprised #y the way the mechanisms of the Sim1 ula lan&ua&e #ecame increasin&ly helpful as the size of the pro&ram increased! OStr<BP 'n Simula- it was possi#le to model a computer usin& a computer object and a network usin& a network objectand the way that physical computers sent packets over physical networks corresponded to the way computer o#1 *ects sent and received messa&es from network o#*ects! 7ut while Simula made it easier for Stroustrup to devel1 op the simulator- the resultin& pro&ram was so slow that it failed to produce any meanin&ful results! This was not the fault of Stroustrup's implementation- #ut of Simula itself! Simula was #loated and lan&ua&e features Stroustrup didn't use in his pro&ram were cripplin& the simulator's efficiency! 8or e)ample- Stroustrup found that ei&hty percent of his pro&ram time was #ein& spent on &ar#a&e collection despite the fact that the simulation didn't create any &ar#a&e! OStr<BP 'n other words- while Simula had decreased the time re5uired to #uild the simulator- it dramatically increased the time re5uired for the simulator to e)ecute! Stroustrup realized that his Simula1#ased simulator was &oin& nowhere! To continue his research- Stroustrup scrapped his Simula implementation and rewrote the pro&ram in a lan&ua&e he knew ran 5uickly and efficiently= 7CC ! 7CC has since &one the way of the dodo- #ut at the time was a widely used- low1level systems pro1 &rammin& lan&ua&e! Stroustrup later recalled that writin& the simulator in 7CC was 4horri#le!6 OStr<BP ,s a low1level lan&ua&e- 7CC lacked o#*ects and to represent computers and networks Stroustrup had to manually lay out and manipulate the proper #its and #ytes! /owever- 7CC pro&rams were far more efficient than their Simula counterparts- and Stroustrup's updated simulator worked marvelously! Stroustrup's e)periences with the distri#uted systems simulator impressed upon him the need for a more suita#le tool for constructin& lar&e software systems! Stroustrup sou&ht a hy#ridization of the #est features of Simula and 7CC ( a lan&ua&e with #oth hi&h1level constructs and low1level runtime efficiency! ,fter receivin& his Ch2- Stroustrup accepted a position at 7ell a#oratories and #e&an to create such a lan&ua&e! Settlin& on C as a #ase lan&ua&e- Stroustrup incorporated hi&h1level constructs in the style of Simula while still maintainin& C's underlyin& efficiency!
Q This section is #ased on information from The esign and E!olution o" C++ #y 7*arne Stroustrup!
161
,fter several revisions- C with Classes- as the lan&ua&e was ori&inally du##ed- accumulated other hi&h1level fea1 tures and was officially renamed C++! C++ was an overni&ht success and spread rapidly into the pro&rammin& communityR for many years the num#er of C++ pro&rammers was dou#lin& every seven months! 'n ;00@- C++ achieved three million users worldwide! OStr0<P 0hat #e&an as Stroustrup's pro*ect at 7ell a#oratories #ecame an 'SH1standardized pro&rammin& lan&ua&e found in a variety of applications! C++ Today C++ #e&an as a hy#rid of hi&h1 and low1level lan&ua&es #ut has since evolved into a distinctive lan&ua&e with its own idioms and constructs! Gany pro&rammers treat C++ as little more than an o#*ect1oriented C- #ut this view o#scures much of the ma&ic of C++! C++ is a multi#aradigm pro&rammin& lan&ua&e- meanin& that it supports several different pro&rammin& styles! C++ supports im#erati!e pro&rammin& in the style of C- meanin& that you can treat C++ as an up&raded C! C++ supports object-oriented pro&rammin&- so you can construct ela#orate class hierarchies that hide comple)ity #ehind simple interfaces! C++ supports generic pro&rammin&- allowin& you to write code reusa#le in a lar&e num#er of conte)ts! 8inally- C++ supports a limited form of higher-order pro&rammin&- allowin& you to write functions that construct and manipulate other functions at runtime! 9esign -hiloso&hy C++ is a comparatively old lan&ua&eR its first release was in 1<E?! Since then numerous other pro&rammin& lan1 &ua&es have sprun& up ( $ava- Cython- CS- and $avascript- to name a few! /ow e)actly has C++ survived so lon& when others have failed> C++ may #e useful and versatile- #ut so were 7CC and Simula- neither of which are in widespread use today! Hne of the main reasons that C++ is still in use Kand evolvin&L today has #een its core &uidin& principles! Stroustrup has maintained an active interest in C++ since its inception and has steadfastly adhered to a particular desi&n philosophy! /ere is a samplin& of the desi&n points- as articulated in Stroustrup's The esign and E!olution o" C++!
C++(s e!olution must be dri!en by real #roblems) 0hen e)istin& pro&rammin& styles prove insufficient for modern challen&es- C++ adapts! 8or e)ample- the introduction of e)ception handlin& provided a much1needed system for error recovery- and a#stract classes allowed pro&rammers to define interfaces more naturally! on(t try to "orce #eo#le! C++ supports multiple pro&rammin& styles! .ou can write code similar to that found in pure C- desi&n class hierarchies as you would in $ava- or develop software somewhere in #etween the two! C++ respects and trusts you as a pro&rammer- allowin& you to write the style of code you find most suita#le to the task at hand rather than ri&idly lockin& you into a sin&le pattern! Always #ro!ide a transition #ath) C++ is desi&ned such that the pro&rammin& principles and techni5ues developed at any point in its history are still applica#le! 0ith few e)ceptions- C++ code written ten or twenty years a&o should still compile and run on modern C++ compilers! Goreover- C++ is desi&ned to #e mostly #ackwards1compati#le with C- meanin& that veteran C coders can 5uickly &et up to speed with C++!
The :oal of C++ There is one 5uote from Stroustrup KOStr<BPL ' #elieve #est sums up C++= C++ ma*es #rogramming more enjoyable "or serious programmers) 0hat e)actly does this mean> et's #e&in with what constitutes a serious #rogrammer! Fi&idly definin& 4seri1 ous pro&rammer6 is difficult- so instead ''ll list some of the pro&rams and pro*ects written in C++ and leave it as an e)ercise to the reader to infer a proper definition! 8or e)ample- you'll find C++ in=
1@1 #o3illa !irefo%! The core infrastructure underlyin& all Gozilla pro1 *ects is written predominantly in C++! 0hile much of the code for 8irefo) is written in $avascript and JU - these lan&ua&es are e)1 ecuted #y interpreters written in C++! The 0e#Kit layout en&ine used #y Safari and 3oo&le Chrome is also written in C++! ,lthou&h it's closed1source- ' suspect that 'nternet D)plorer is also written in C++! 'f you're #rowsin& the we#- you're seein& C++ in action!
;a7a 0otS&ot! The widespread success of $ava is in part due to +ot,#ot- Sun's implementation of the $ava Tirtual Gachine! /otSpot supports *ust1in1time compilation and optimization and is a #eauti1 fully en&ineered piece of software! 't's also written in C++! The ne)t time that someone en&a&es you in a de#ate a#out the relative merits of C++ and $ava- you can mention that if not for a well1architected C++ pro&ram $ava would not #e a competitive lan&ua&e!
<ASA = ;-L. The rovers currently e)plorin& the surface of Gars have their autonomous drivin& systems written in C++! C++ is on -ars.
C++ ma*es #rogramming more enjoyable "or serious #rogrammers! "ot only does C++ power all of the a#ove applications- it powers them in style! .ou can pro&ram with hi&h1level constructs yet en*oy the runtime effi1 ciency of a low1level lan&ua&e like C! .ou can choose the pro&rammin& style that's ri&ht for you and work in a lan&ua&e that trusts and respects your e)pertise! .ou can write code once that you will reuse time and time a&ain! This is what C++ is all a#out- and the purpose of this #ook is to &et you up to speed on the mechanicsstyle- and *ust plain e)citement of C++! 0ith that said- let's dive into C++! Hur *ourney #e&insU
Dvery *ourney #e&ins with a sin&le step- and in ours it's &ettin& to the point where you can compile- link- runand de#u& C++ pro&rams! This depends on what operatin& system you have- so in this section we'll see how to &et a C++ pro*ect up and runnin& under 0indows- Gac HS J- and inu)! Com&iling C++ -rograms under /indows This section assumes that you are usin& Gicrosoft Tisual Studio ;00? KTS;00?L! 'f you are a current CS1067IJ student- you can follow the directions on the course we#site to o#tain a copy! Htherwise- #e prepared to shell out some cash to &et your own copy- thou&h it is definitely a worthwhile investment!Q ,lternatively- you can download Tisual C++ ;00E D)press Ddition- a free version of Gicrosoft's development environment sportin& a fully1functional C++ compiler! The e)press edition of Tisual C++ lacks support for advanced 0indows devel1 opment- #ut is otherwise a perfectly fine C++ compiler! .ou can &et Tisual C++ ;00E D)press Ddition from http=IIwww!microsoft!comIe)pressIvcI! 0ith only a few minor chan&es- the directions for usin& TS;00? should also apply to Tisual C++ ;00E D)press Ddition- so this section will only cover TS;00?! TS;00? or&anizes C++ code into 4pro*ects-6 collections of source and header files that will #e #uilt into a pro1 &ram! The first step in creatin& a C++ pro&ram is to &et an empty C++ pro*ect up and runnin&- then to populate it with the necessary files! To #e&in- open TS;00? and from the !ile menu choose <ew > -ro?ect...! .ou should see a window that looks like this=
Q ' first #e&an pro&rammin& in C++ in ;001 usin& Gicrosoft Tisual C++ 6!0- which cost rou&hly ei&hty dollars! ' recently K;00EL switched to Tisual Studio ;00?! This means that the compiler cost *ust over ten dollars a year! Considerin& the sheer num#er of hours ' have spent pro&rammin&- this was pro#a#ly the #est investment ' have made!
1 10 1
,s you can see- TS;00? has template support for all sorts of different pro*ects- most of which are for Gicrosoft1 specific applications such as dynamic1link li#raries K2 sL or ,ctiveJ controls! 0e're not particularly inter1 ested in most of these choices ( we *ust want a simple C++ pro&ramU To create one- find and choose /in" Console A&&lication! 3ive your pro*ect an appropriate name- then click 2@! .ou should now see a window that looks like this- which will ask you to confi&ure pro*ect settin&s=
"ote that the window title will have the name of the pro*ect you entered in the previous step in its titleR 4.et ,n1 other C++ Cro&ram6 is a placeholder! ,t this point- you do not want to click !inish! 'nstead- hit <e%t > and you'll #e presented with the followin& screen=
1 11 1
Keep all of the default settin&s listed here- #ut make sure that you check the #o) marked $m&ty -ro?ect! Hther1 wise TS;00? will &ive you a pro*ect with all sorts of Gicrosoft1specific features #uilt into it! Hnce you've checked that #o)- click !inish and you'll have a fully functional Kal#eit emptyL C++ pro*ect! "ow- it's time to create and add some source files to this pro*ect so that you can enter C++ code! To do this- &o to -ro?ect > Add <ew Item... Kor press CTF +S/'8T+,L! .ou'll #e presented with the followin& dialo& #o)=
1 1; 1
Choose C++ !ile A.c&&B and enter a name for it inside the "ame field! TS;00? automatically appends !cpp to the end of the filename- so don't worry a#out manually enterin& the e)tension! Hnce you're ready- click Add and you should have your source file ready to &o! ,ny C++ code you enter in here will #e considered #y the com1 piler and #uilt into your final application! Hnce you've written the source code- you can compile and run your pro&rams #y pressin& !*- choosin& 9ebug> Start 9ebugging- or clickin& the &reen 4play6 icon! 7y default TS;00? will close the console window after your pro&ram finishes runnin&- and if you want the window to persist after the pro&ram finishes e)ecutin& you can run the pro&ram without de#u&&in& #y pressin& CT1L+!* or choosin& 9ebug > Start /ithout 9eC bugging! .ou should #e all set to &oU Com&iling C++ -rograms in #ac 2S D 'f you're developin& C++ pro&rams on Gac HS J- your #est option is to use ,pple's Jcode development envir1 onment! .ou can download Jcode free of char&e from the ,pple 2eveloper Connection we#site at http=IIdeveloper!apple!comI! Hnce you've downloaded and installed Jcode- it's reasona#ly strai&htforward to create a new C++ pro*ect! Hpen Jcode! The first time that you run the pro&ram you'll &et a nice welcome screen- which you're free to peruse #ut which you can safely dismiss! To create a C++ pro*ect- choose !ile > <ew -ro?ect...! .ou'll #e presented with a screen that looks like this=
There are a lot of options here- most of which are ,pple1specific or use lan&ua&es other than C++ Ksuch as $ava or H#*ective1CL! 'n the panel on the left side of the screen- choose Command Line Etility and you will see the followin& options=
1 1A 1
Select C++ Tool and click the Choose... #utton! .ou'll #e prompted for a pro*ect name and directoryR feel free to choose whatever name and location you'd like! 'n this e)ample ''ve used the name 4.et ,nother C++ Cro*ect-6 thou&h ' su&&est you pick a more descriptive name! Hnce you've made your selection- you'll see the pro*ect win1 dow- which looks like this=
1 1B 1
"otice that your pro*ect comes prepacka&ed with a file called main.*$$! This is a C++ source file that will #e compiled and linked into the final pro&ram! 7y default- it contains a skeleton implementation of the /ello0orldU pro&ram- as shown here=
8eel free to delete any of the code you see here and rewrite it as you see fit! 7ecause the pro&ram we've *ust created is a command1line utility- you will need to pull up the console window to see the output from your pro&ram! .ou can do this #y choosin& 1un > Console or #y pressin& F! 'ni1 tially the console will #e empty- as shown here=
1 1? 1
Hnce you've run your pro&ram- the output will #e displayed here in the console! .ou can run the pro&ram #y clickin& the ,uild and :o #utton Kthe hammer ne)t to a &reen circle containin& an arrowL! That's itU .ou now have a workin& C++ pro*ect! 'f you're interested in compilin& pro&rams from the Gac HS J terminal- you mi&ht find the followin& section on inu) development useful! Com&iling C++ -rograms under Linu% 8or those of you usin& a inu)1#ased operatin& system- you're in luck ( inu) is e)tremely developer1friendly and all of the tools you'll need are at your disposal from the command1line! Unlike the 0indows or Gac environments- when compilin& code in inu) you won't need to set up a develop1 ment environment usin& Tisual Studio or Jcode! 'nstead- you'll *ust set up a directory where you'll put and edit your C++ files- then will directly invoke the 3"U C++ Compiler Kg!!L from the command1line! 'f you're usin& inu) ''ll assume that you're already familiar with simple commands like m+dir and *hdir and that you know how to edit and save a te)t document! 0hen writin& C++ source code- you'll pro#a#ly want to save header files with the !h e)tension and C++ files with the !cc- !cpp- !C- or !c++ e)tension! The !cc e)tension seems to #e in vo&ue these days- thou&h !cpp is also 5uite popular! To compile your source code- you can e)ecute g!! from the command line #y typin& g!! and then a list of the files you want to compile! 8or e)ample- to compile m%file.** and m%otherfile.**- you'd type
g!! m%file.** m%otherfile.**
7y default- this produces a file named a.out- which you can e)ecute #y enterin& ."a.out! 'f you want to chan&e the name of the pro&ram to somethin& else- you can use g!!'s ,o switch- which produces an output file of a different name! 8or e)ample- to create an e)ecuta#le called m%$rogram from the file m%file.**- you could write
g!! m%file.** -o myprogram g!! has a whole host of other switches Ksuch as ,* to compile #ut not link a fileL- so #e sure to consult the man
pa&es for more info! 't can &et tedious writin& out the commands to compile every sin&le file in a pro*ect to form a finished e)ecut1 a#le- so most inu) developers use ma*e"iles- scripts which allow you to compile an entire pro*ect #y typin& the ma+e command! , full tour of makefiles is far #eyond the scope of an introductory C++ te)t- #ut fortunately there are many &ood online tutorials on how to construct a makefile! The full manual for ma+e is availa#le on1 line at http=IIwww!&nu!or&IsoftwareImakeImanualImake!html! 2ther 9e7elo&ment Tools 'f you are interested in usin& other development environments than the ones listed a#ove- you're in luck! There are dozens of '2Ds availa#le that work on a wide ran&e of platforms! /ere's a small samplin&=
<et,eans= The "et7eans '2D supports C++ pro&rammin& and is hi&hly customiza#le! 't also is com1 pletely cross1platform compati#le- so you can use it on 0indows- Gac HS J- and inu)! #in:/= Gin30 is a port of common 3"U tools to Gicrosoft 0indows- so you can use tools like &++ without runnin& inu)! Gany lar&e software pro*ects use Gin30 as part of their #uild environment- so you mi&ht want to e)plore what it offers you!
1 16 1
Cha#ter /% 0etting ,tarted $cli&se= This popular $ava '2D can #e confi&ured to run as a C++ compiler with a #it of additional ef1 fort! 'f you're usin& 0indows you mi&ht need to install some additional software to &et this '2D work1 in&- #ut otherwise it should #e reasona#ly strai&htforward to confi&ure! Sun Studio= 'f you're a inu) user and command1line hackin& isn't your cup of tea- you mi&ht want to consider installin& Sun Studio- Sun Gicrosystem's C++ development environment- which has a wonder1 ful 3U' and solid de#u&&in& support!
0hen you arrived at your first CS1067IJ lecture- you pro#a#ly learned to write a simple 4/ello- 0orld6 pro1 &ram like the one shown #elow=
-in*lude .genlib.h. -in*lude iostream) int main() { *out ./ello, 'orld0. return 0; #
endl;
0hether or not you have previous e)perience with C++- you pro#a#ly realized that the first line means that the source code references an e)ternal file called genlib.h! 8or the purposes of CS1067IJ- this is entirely accept1 a#le Kin fact- it's re5uiredUL- #ut once you mi&rate from the educational settin& to professional code you will run into trou#le #ecause genlib.h is not a standard header fileR it's included in the CS1067IJ li#raries to simplify certain lan&ua&e features so you can focus on writin& code- rather than appeasin& the compiler! 'n CS106 - none of our pro&rams will use genlib.h- sim$io.h- or any of the other CS1067IJ li#rary files! 2on't worry- thou&h- #ecause none of the functions e)ported #y these files are 4ma&ical!6 'n fact- in the ne)t few chapters you will learn how to rewrite or supersede the functions and classes e)ported #y the CS1067IJ li#rar1 ies!Q 'f you have the time- ' encoura&e you to actually open up the genlib.h file and peek around at its con1 tents! To write 4/ello- 0orld6 without genlib.h- you'll need to add another line to your pro&ram! The 4pure6 C++ version of 4/ello- 0orld6 thus looks somethin& like this=
-in*lude iostream) using namespace std; int main() { *out ./ello, 1orld0. return 0; #
endl;
0e've replaced the header file genlib.h with the cryptic statement 4using names$a*e std;6 7efore e)1 plainin& e)actly what this statement does- we need to take a 5uick diversion to lessons learned from develop1 ment history! Suppose you're workin& at a company that produces two types of software= &raphics desi&n pro1 &rams and online &unfi&hter duels Kadmittedly- this com#ination is pretty unlikely- #ut humor me for a whileL! Dach pro*ect has its own source code files complete with a set of helper functions and classes! /ere are some sample header files from each pro*ect- with most of the commentin& removed=
Q The e)ceptions are the &raphics and sound li#raries! C++ does not have natural lan&ua&e support for multimedia- and althou&h many such li#raries e)ist- we won't cover them in this te)t!
1 1E 1 0ra#hics2tility)h%
"2 &ile3 gra$hi*sutilit%.h 2 Gra$hi*s utilit% fun*tions. 2" "2 4lear5*ene3 4lears the *urrent s*ene. 2" void 4lear5*ene(); "2 Add6ine3 Adds a line to the *urrent s*ene. 2" void Add6ine(int 70, int %0, int 78, int %8); "2 9ra'3 9ra's the *urrent s*ene. 2" void 9ra'();
0un"ighter2tility)h%
"2 &ile3 gunfighterutilit%.h 2 Gunfighter utilit% fun*tions. 2" "2 :ar*h;en<a*es3 :ar*hes ten $a*es, animating ea*h ste$. 2" void :ar*h;en<a*es(<la%er=b>e*t ?to:ove); "2 &a*e&oe3 ;urns to fa*e the o$$onent. 2" void &a*e&oe(); "2 9ra'3 @nholsters and aims the $istol. 2" void 9ra'();
Suppose the &unfi&hter team is implementin& :ar*h;en<a*es and needs to animate the &unfi&hters walkin& away from one another! Fealizin& that the &raphics team has already implemented an entire li#rary &eared to1 ward this- the &unfi&hter pro&rammers import gra$hi*sutilit%.h into their pro*ect- write code usin& the &raphics functions- and try to compile! /owever- when they try to test their code- the linker reports errors to the effect of 4error3 fun*tion Avoid 9ra'()A alread% defined.6 The pro#lem is that the &raphics and &unfi&hter modules each contain functions named 9ra'() with the same si&nature and the compiler can't distin&uish #etween them! 't's impractical for either team to rename their 9ra' function- #oth #ecause the other pro&rammin& teams e)pect them to provide functions named 9ra' and #ecause their code is already filled with calls to 9ra'! 8ortunately- there's an ele&ant resolution to this pro#lem! Dnter the C++ names$a*e keyword! , names#ace adds another layer of namin& onto your functions and varia#les! 8or e)ample- if all of the &unfi&hter code was in the namespace 4 Gunfighter-6 the function 9ra' would have the full name Gunfighter339ra'! Similarly- if the &raphics pro&rammers put their code inside namespace 4Gra$hi*s-6 they would reference the function 9ra' as Gra$hi*s339ra'! 'f this is the case- there is no lon&er any am#i&uity #etween the two functions- and the &unfi&hter development team can compile their code! 7ut there's still one pro#lem ( other pro&rammin& teams e)pect to find functions named 4lear5*ene and &a*e&oe- not Gra$hi*s334lear5*ene and Gunfighter33&a*e&oe! 8ortunately- C++ allows what's known as a using declaration that lets you i&nore fully 5ualified names from a namespace and instead use the shorter names! 7ack to the /ello- 0orld e)ample- reprinted here=
1 1< 1
endl;
The statement 4using names$a*e std;6 followin& the -in*lude directive tells the compiler that all of the functions and classes in the namespace std can #e used without their fully15ualified names! This 4std6 namespace is the C++ standard names#ace that includes all the li#rary functions and classes of the standard li#1 rary! 8or e)ample- *out is truly named std33*out- and without the usin& declaration importin& the std namespace- /ello- 0orld would look somethin& like this=
-in*lude iostream)
./ello, 1orld0.
std::endl;
0hile some pro&rammers prefer to use the fully15ualified names when usin& standard li#rary components- re1 peatedly writin& std33 can #e a hassle! To eliminate this pro#lem- in genlib.h- we included the using de1 claration for you! 7ut now that we've taken the trainin& wheels off and genlib.h is no more- you'll have to re1 mem#er to include it yourselfU There's one more important part of genlib.h- the string type! Unlike other pro&rammin& lan&ua&es- C++ lacks a primitive strin& type!Q Sure- there's the class string- #ut unlike int or double it's not a #uilt1in type and must #e included with a -in*lude directive! Specifically- you'll need to write -in*lude string) at the top of any pro&ram that wants to use C++1style strin&s! ,nd don't for&et the using declaration- or you'll need to write std33string every time you want to use C++ strin&sU
Q Technically speakin& there are primitive strin&s in C++- #ut they aren't o#*ects! See the chapter on C strin&s for more in1 formation!
-art 2ne
Introduction to the C++ Standard Library
C++ has an enormous host of li#rary functions- constants- and classes that simplify or o#viate comple) pro&ram1 min& tasks! 0hile much of the C++ standard li#rary is #eyond the scope of this class- a su#stantial portion of the li#rary is accessi#le to #e&innin& or intermediate1level C++ pro&rammers! This chapter summarizes the li#1 rary's contents and serves as a launchin& point for further e)ploration! The C 1untime Library C++ was ori&inally desi&ned as a superset of the C pro&rammin& lan&ua&e ( that is- with few e)ceptions- code that you write in pure C should compile and run in C++! Conse5uently- for #ackwards compati#ility- C++ a#1 sor#ed C's runtime li#rary! .ou can identify li#raries a#sor#ed from C #y the letter 'c' in the header file names! 8or e)ample- to access the C li#rary functions that let you access and manipulate the date and time- use the head1 er file *time)- and for the core C standard li#rary use *stdlib)! Guch K#ut #y no means allL of the C runtime li#rary has #een superseded #y other portions of the C++ li#rary! 8or e)ample- C's inputIoutput routines like $rintf and s*anf can #e replaced with C++'s safer and more ro1 #ust *out and *in streams! /owever- there are many useful C runtime li#rary functions Kmy personal favorite is tm$namL and ' recommend that you take some time to look over what's there! 7ecause some of the C runtime li#rary is unsafe when mi)ed with standard C++ or re5uires an understandin& of the lan&ua&e #eyond the scope of this class- we will not cover the C runtime li#rary in &reat depth! /owever- if you're interested in learnin& to use the C runtime li#rary- there are some &reat resources online- such as www!cppreference!com= , we#site coverin& #oth the C and C++ li#raries! .ou mi&ht want to consider #ookmarkin& this pa&e as a 5uick reference #ecause it's 5uite useful! www!cplusplus!comIreferenceIcli#raryI= , cate&orized overview of the C runtime li#rary that includes code e)amples for most of the functions and macros! ,s with the a#ove link- this is an e)tremely useful we#1 site and you mi&ht want to consider #ookmarkin& it! 8or those of you with hi&h1end cell phones- con1 sider addin& it to speed1dial! The Streams Library The streams li#rary is C++'s way of readin& and writin& formatted input and output! The streams li#rary in1 cludes functionality to read from and write to the console- files on disk- and even strin&s! 'n addition- it specifies a set of o#*ects called stream mani#ulators that allows you to control the formattin& and e)pression of data in a stream! 0hile our discussion of streams will cover a &ood deal of the li#rary- we will not cover some topics such as #in1 ary file readin& and random access- nor will we address some of the lower1level stream o#*ects! 8or more in1 formation on these topics- you mi&ht want to refer to= www!cplusplus!comIreferenceIiostreamI= cplusplus!com has a very &ood overview of the streams li#rary that includes a handy class li#rary and even offers a peek into the inner workin&s of the classes!
8or those of you with a #ack&round in pure C- the C++ strin& li#rary mi&ht seem like nothin& short of a miracle! The C++ string is li&htwei&ht- fast- fle)i#le- and powerful! 'n fact- it's so powerful and easy to use that it's one of the few standard C++ classes used in CS1067IJ! 8or more information a#out string- refer to Programming Abstractions in C++ and the handouts from CS1067IJ! The Standard Tem&late Library ASTLB The ST is a collection of classes that store data KcontainersL- o#*ects to access data KiteratorsL- functions that op1 erate on data Kal&orithmsL- and o#*ects that manipulate functions KfunctorsL! ,n understandin& of the ST is crit1 ical to fully appreciate how powerful and versatile C++ is! /owever- as is #ound to happen with any powerful pro&rammin& li#rary- the ST is comple) and re5uires a stron& understandin& of the C++ lan&ua&e to fully use! ,lthou&h we will dedicate several chapters to the ST - there simply isn't enou&h time to e)plore all of its facets and functionality! 'f you're interested in e)plorin& more topics in the ST - consider referrin& to these sources= Scott Geyers! E""ecti!e ,TL% 3$ ,#eci"ic &ays to Im#ro!e 4our 2se o" the ,tandard Tem#late Library! ,d1 dison10esley! 'S7" 01;011@B<6;1<! This is widely reco&nized as one of the most useful #ooks concern1 in& the ST ! Father than servin& as an introductory te)t on the su#*ect- Geyers' #ook descri#es how to ma)imize efficiency and &et the most for your money out of the ST ! www!cplusplus!comIreferenceI= , &reat Kand freeUL online reference coverin& much of the ST - includin& code samples! <umeric Libraries The numeric li#raries- mostly defined in the numeri*) and valarra%) headers- are classes and functions desi&ned for computational or mathematical pro&rammin&! 8or those of you with a #ack&round in Cython- this header includes classes that let you access arrays via slices! 0e will not cover the numeric classes in this te)t#ut those of you who are interested may want to consider lookin& into= msdn;!microsoft!comIen1usIli#raryIfzkkAcyEKTS!E0L!asp)= Gicrosoft's valarra% reference- which is one of the #etter covera&es ''ve found! .an&- 2ao5i! C++ and 5bject-oriented 6umeric Com#uting "or ,cientists and Engineers) Sprin&er! 'S7" 01AE@1<E<<010! 'f you're interested in learnin& C++ in order to do computational pro&rammin&- this is the #ook for you! 't's a &ood introduction to C++ and includes many mathematical e)amples- such as solvin& H2Ds! This #ook re5uires a mathematical #ack&round in linear al&e#ra and calculus! #emory #anagement Libraries The C++ li#rary also contains various o#*ects and functions desi&ned to help with memory allocation and deal1 location! 8or e)ample- the autoB$tr template class acts a 4smart pointer6 that automatically deallocates its memory- while the setBne'Bhandler function can #e used to set an emer&ency handler in case o$erator ne' can't find enou&h memory to satisfy a re5uest! 0hile we will cover a su#set of the memory mana&ement li#raries in the second half of this course reader- a complete treatment of memory mana&ement is an advanced topic far #eyond the scope of an introductory te)t! 'f you're interested in some practical applications of the memory li#raries- consider readin&= www!&otw!caIpu#licationsIusin&VautoVptrVeffectively!htm= The autoB$tr class can simplify your code and make it safer to use! /owever- it is not completely intuitive! This article is a &reat introduction to autoB$tr and offers several pointers and caveats!
1 ;A 1
C++ supports e)ception1handlin& with tr%Ithro'I*at*h #locks- as those of you familiar with $ava mi&ht re1 co&nize! 0hile we will cover e)ception handlin& in the second half of this course- you may still want to e)plore the li#raries in more depth than what is covered here! 'f you're interested in e)ception1handlin& in &eneral- these resources mi&ht #e useful= Sutter- /er#! E7ce#tional C++% 89 Engineering Pu::les, Programming Problems, and ,olutions) ,ddison1 0esley! 'S7" 01;01161?6;1;! This #ook is an e)cellent resource on writin& e)ception1safe code and is hi&hly re&arded in the C++ community! 'f you're interested in learnin& a#out writin& code compati#le with C++ e)ceptions- this is the #ook for you! www!#oost!or&ImoreI&enericVe)ceptionVsafety!html= 0hile this site is primarily aimed at writin& e)ception1 safe container classes- it nonetheless provides an e)cellent introduction to C++ e)ception safety! Locale Libraries Gany pro&rams are desi&ned for an international audience where notational conventions vary Kfor e)ampleW1-;AB!?6 in the United States mi&ht #e written as 1!;AB-?6 US elsewhereL! The locale li#raries offer a method for writin& code that can easily #e localized into other re&ions! ocale functions are far #eyond the scope of an introductory te)t- #ut those of you who are interested may consider the followin& source useful= www!cantrip!or&Ilocale!html= This is one of the #est introductions to locales that ''ve come across on the 'n1 ternet! 't uses advanced C++ synta) that mi&ht #e confusin&- so you mi&ht want to have a reference handy #efore proceedin&! Language Su&&ort Library The C++ standard specifies the synta) and semantics of the C++ lan&ua&e- #ut it leaves many important de1 cisions to individual implementers! 8or e)ample- the size of an int or the #ehavior of a double holdin& too lar&e a value vary on a system1#y1system #asis! To ena#le C++ pro&rams to determine the confi&uration of their systems- C++ provides the lan&ua&e support li#rary- a set of classes and constants that contain information a#out the particulars of the current C++ implementation! 'n CS1067IJ and CS106 - you will not need to worry a#out the lan&ua&e support li#rary! /owever- if you plan on workin& on a cross1platform C++ pro*ect where these details matter- you may want to consider lookin& into= http=IIwww!unc!eduIdeptsIcaseIp&iIp&C++Vli#Istdli#crInumV?6@<!htm= , complete reference for the numeri*Blimits class- which e)ports most of the information specific to a particular C++ implement1 ation! The ,oost C++ Libraries ,lthou&h this course will only cover standard C++- if you plan to pursue C++ #eyond this class you should stron&ly consider lookin& into the 7oost C++ i#raries! 7oost is the most prominent third1party C++ li#raryand several parts of 7oost are #ein& considered #y the C++ Standards Committee for inclusion in the ne)t re1 lease of C++! Hnce you've &otten the han& of the C++ standard li#rary- you should stron&ly consider e)plorin& 7oost- especially since many parts of 7oost seamlessly inte&rate with the e)istin& li#raries! .ou can find the 7oost li#raries at www!#oost!or&!
1 ;B 1 ThirdC-arty Libraries
8or those of you with e)perience in lan&ua&es like $ava or Cython- the C++ standard li#rary mi&ht seem limited! C++ lacks a &raphics and sound packa&e- has no support for networkin&- and does not natively support window1 in&! To use features like these- you'll need to rely on third1party li#raries- like Gicrosoft's 0inA; ,C' or the J 0indow System! There are several reasons C++ opted out of a 4monolithic li#rary6 strate&y! 8irst- since the C++ li#raries focus more on data manipulation than presentation- C++ works on more platforms than other lan&ua&esR you can use C++ to pro&ram #oth we# #rowsers and microcontrollers! Second- some features like multimedia and window1 in& vary &reatly from system to system! Standardizin& these features would result in a standard li#rary caterin& to the lowest common denominator- somethin& likely to please no one! 'nstead- C++ leaves li#raries like these to third parties- so the resultin& li#raries are less porta#le #ut more powerful! 8inally- li#raries sometimes provide fundamentally different ways to approach pro&rammin& in C++! Standardizin& li#raries like these would force C++ pro&rammers comforta#le with one pro&rammin& style to uncomforta#ly ad*ust to another- and conse5uently li#raries of this sort are left to third1parties! 7ased on the sort of functionality you're lookin& for- the followin& third1party li#raries mi&ht #e worth e)plor1 in&= <etworking= The 'nternet Sockets ,C' is a widely1used set of types and functions used to write networkin& code in C and C++! The li#rary is written in C- so you may want to #uild C++ wrappers around some of the core functionality! Socket pro&rammin& works on most ma*or operatin& systems- thou&h the 0indows version K0in1 sockL has a few syntactic differences from the rest of the sockets ,C'! ,lternatively- you can use the 7oost li#1 rary's asio packa&e- which supports a wide array of networkin& protocols! :ra&hics= There are literally hundreds of &raphics packa&es written for CIC++- of which Hpen3 Kcross1plat1 formL and 2irectJ KGicrosoft1specificL are amon& the most well1known! These systems have a #it of a learnin& curve- #ut if you're interested you may want to take CS1BE K'ntroduction to Computer 3raphicsL! #ultithreading= There are numerous multithreadin& li#raries availa#le for CIC++! Gicrosoft's 0inA; ,C' has a rich set of threadin& capa#ilities- #ut is 0indows1specific! 7oost's threadin& classes are widely1used in industry and are cross1platform- #ut have a steep learnin& curve! 8or the truly #rave amon& you- the pthreads li#rary is a low1level C li#rary that supports multithreadin&! /indowing= ,ll ma*or operatin& systems provide some platform1specific interface that you can use to develop windowed software! 'f you're interested in a cross1platform windowin& system- you mi&ht want to look into the 3TK+ ,C'! Gicrosoft's 0inA; ,C' is e)cellent if you want to learn to pro&ram 0indows1specific applications!
The streams li#rary is C++'s way of formattin& input and output to and from a variety of sources- includin& the console- files- and strin& #uffers! /owever- like most parts of the standard li#rary- the streams li#rary has many features and idiosyncrasies that can take some time to ad*ust to! This chapter introduces the streams li#rary and includes useful tips and tricks for practical pro&rammin&!
cout and cin
,s you've seen in CS1067IJ- C++ provides a stream o#*ect called *out Kcharacter outputL you can use to write formatted data to the console! 8or e)ample- to print out a messa&e to the user- you can write code that looks like this=
*out .(Am sorr% 9ave. (Am afraid ( *anAt do that.. endl;
/ere- the
operator is called the stream insertion o#erator and instructs C++ to push data into a stream!
To #uild a truly interactive pro&ram- however- we'll need to &et input from the user! 'n CS1067IJ- we provide the sim$io.h header file- which e)ports the input functions Get6ine- Get(nteger- GetCeal- and Get6ong! Thou&h useful- these functions are not part of the C++ standard li#rary and will not #e availa#le outside of CS1067IJ! 2on't worry- thou&h- #ecause #y the end of this chapter we'll see how to implement them usin& only standard C++! The streams li#rary e)ports another stream o#*ect called *in Kcharacter inputL which lets you read values dir1 ectly from the user! To read a value from *in- you use the stream e7traction o#erator ))! Syntactically- the stream e)traction operator mirrors the stream insertion operator! 8or e)ample- here's a code snippet to prompt the user for an inte&er!
*out .<lease enter an integer3 .; int m%(nteger; cin >> myInteger; "" Dalue stored in m%(nteger
.ou can also read multiple values from *in #y chainin& toðer the stream e)traction operator in the same way that you can write multiple values to *out #y chainin& the stream insertion operator=
int m%(nteger; string m%5tring; cin >> myInteger >> myString; "" Cead an integer and string from *in
"ote that when usin& *in- you should not read into endl the way that you write endl when usin& *out! /ence the followin& code is ille&al=
int m%(nteger; *in )) m%(nteger )) endl; "" Error3 4annot read into endl.
'n practice- it is not a &ood idea to read values directly from *in! Unlike Get(nteger and the like- *in does not perform any safety checkin& of user input and if the user does not enter valid data *in will malfunction! 0e will cover how to fi) these pro#lems later in this chapter!
Cha#ter ;% ,treams
C++ provides a header file called fstream) Kfile streamL that e)ports the ifstream and ofstream typesstreams that perform file 'IH! The namin& convention is unfortunate ( ifstream stands for input file stream Knot 4somethin& that mi&ht #e a stream6L and ofstream for output file stream! There is also a &eneric fstream class which can do #oth input and output- #ut we will not cover it in this chapter! To create an ifstream that reads from a file- you can use this synta)=
i stream myStream!"myFile.t#t"$;
This creates a new stream o#*ect named m%5tream which reads from the file m%&ile.t7t- provided of course that the file e)ists! 0e can then read data from m%5tream *ust as we would from *in- as shown here=
ifstream m%5tream(.m%&ile.t7t.); int m%(nteger; myStream >> myInteger; "" Cead an integer from m%&ile.t7t
"otice that the final line looks almost identical to code that reads an inte&er from the console! .ou can also open a file #y usin& the ifstream's o$en mem#er function- as shown here=
ifstream m%5tream; "" Eote3 did not s$e*if% the file myStream.open!"myFile.t#t"$; "" Eo' reading from m%&ile.t7t
0hen openin& a file usin& an ifstream- there is a chance that the specified file can't #e opened! The filename mi&ht not specify an actual file- you mi&ht not have permission to read the file- or perhaps the file is locked! 'f you try readin& data from an ifstream that is not associated with an open file- the read will fail and you will not &et #ack meanin&ful data! ,fter tryin& to open a file- you can check if the operation succeeded #y usin& the .isBo$en() mem#er function! 8or e)ample- here's code to open a file and report an error to the user if a pro#1 lem occurred=
ifstream in$ut(.m%file.t7t.); if(%input.is&open!$) *err .4ouldnAt o$en the file m%file.t7t.
endl;
"otice that we report the error to the *err stream! *err- like *out- is an output stream- #ut unlike *out- *err is desi&ned for error reportin& and is sometimes handled differently #y the operatin& system! The output counterpart to ifstream is ofstream! ,s with ifstream- you specify which file to write to either #y usin& the .o$en() mem#er function or #y specifyin& the file when you create the ofstream- as shown #e1 low=
o stream myStream!"myFile.t#t"$; "" 1rite to m%&ile.t7t
, word of warnin&= if you try writin& to a none)istent file with an ofstream- the ofstream will create the file for you! /owever- if you open a file that already e)ists- the ofstream will overwrite all of the contents of the file! 7e careful not to write to important files without first #ackin& them upU The streams li#rary is one of the older li#raries in C++ and the o$en functions on the ifstream and ofstream classes predate the string type! 'f you have the name of a file stored in a C++ string- you will need to con1 vert the string into a C1style strin& Kcovered in the second half of this #ookL #efore passin& it as a parameter to o$en! This can #e done usin& the .*Bstr() mem#er function of the string class- as shown here=
Cha#ter ;% ,treams
ifstream in$ut(myString.c&str!$); "" =$en the filename stored in m%5tring
1 ;@ 1
0hen a file stream o#*ect &oes out of scope- C++ will automatically close the file for you so that other processes can read and write the file! 'f you want to close the file prematurely- you can use the .*lose() mem#er func1 tion! ,fter callin& *lose- readin& or writin& to or from the file stream will fail! ,s mentioned a#ove in the section on *in- when readin& from or writin& to files you will need to do e)tensive error checkin& to ensure that the operations succeed! ,&ain- we'll see how to do this later! Stream #ani&ulators Consider the followin& code that prints data to *out=
*out .;his is a string0. endl;
0hat e)actly is endl> 't's an e)ample of a stream mani#ulator- an o#*ect that can #e inserted into a stream to chan&e some sort of stream property! endl is one of the most common stream manipulators- thou&h others e)ist as well! To motivate some of the more comple) manipulators- let's suppose that we have a file called table,data.t7t containin& four lines of te)t- where each line consists of an inte&er value and a real num#er! 8or e)ample= table-data)t7t
8FG JH GILGLIG 8FFG H.G8IHI F.8J8KL 8.M0I .08808080008
0e want to write a pro&ram which reads in this data and prints it out in a ta#le- as shown here=
,,,,,,,,,,,,,,,,,,,,,!,,,,,,,,,,,,,,,,,,,,,,!,,,,,,,,,,,,,,,,,,,,, 8 N 8FG N H.G8IHI H N JH N F.8J8KL F N GILGLIG N 8.M0I J N 8FFG N 0.08808
/ere- the first column is the one1inde)ed line num#er- the second the inte&er values from the file- and the third the real1num#ered values from the file! et's #e&in #y definin& a few constants to control what the output should look like! Since there are four lines in the file- we can write
*onst int E@:B6(EE5 = J;
"e)t- we'll pick an ar#itrary width for each column! 0e'll choose twenty characters- thou&h in principle we could pick any value as lon& as the data fit=
*onst int 4=6@:EB1(9;/ = H0;
"ow- we need to read in the ta#le data and print out the formatted ta#le! 0e'll decompose this pro#lem into two smaller steps- resultin& in the followin& source code=
1 ;E 1
-in*lude iostream) -in*lude fstream) using names$a*e std; *onst int E@:B6(EE5 = J; *onst int E@:B4=6@:E5 = F; *onst int 4=6@:EB1(9;/ = H0; int main() { PrintTable'eader!$; PrintTable(ody!$; return 0; #
Cha#ter ;% ,treams
<rint;able/eader is responsi#le for printin& out the top part of the ta#le Kthe row of dashes and plusesL and <rint;ableOod% will load the contents of the file and print them to the console!
2espite the fact that <rint;able/eader precedes <rint;ableOod% in this pro&ram- we'll #e&in #y imple1 mentin& <rint;ableOod% as it illustrates e)actly how much firepower we can &et from the stream manipulat1 ors! 0e know that we need to open the file table,data.t7t and that we'll need to read four lines of data from it- so we can #e&in writin& this function as follows=
)oid PrintTable(ody!$ * i stream input!"table-data.t#t"$; "2 Eo error,*he*+ing here, but %ou should be sure to do this in an% real 2 $rogram. 2" "2 6oo$ over the lines in the file reading data. 2" or!int + , -; + . /U0&1I/2S; 33+$ * "2 ... $ro*ess data ... 2" 4
.ou may have noticed that at the end of this for loop ''ve written !!+ instead of +!!! There's a sli&ht difference #etween the two synta)es- #ut in this conte)t they are interchan&ea#le! 0hen we talk a#out operator overload1 in& in a later chapter we'll talk a#out why it's &enerally considered #etter practice to use the prefi) increment op1 erator instead of the postfi)! "ow- we need to read data from the file and print it as a ta#le! 0e can start #y actually readin& the values from the file- as shown here=
Cha#ter ;% ,treams
void <rint;ableOod%() { ifstream in$ut(.table,data.t7t.); "2 Eo error,*he*+ing here, but %ou should be sure to do this in an% real 2 $rogram. 2" "2 6oo$ over the lines in the file reading data. 2" for(int + = 0; + E@:B6(EE5; !!+) { int int5alue; double double5alue; input >> int5alue >> double5alue; # #
1 ;< 1
"e)t- we need to print out the ta#le row! This is where thin&s &et tricky! 'f you'll recall- the ta#le is supposed to #e printed as three columns- each a fi)ed width- that contain the relevant data! /ow can we ensure that when we print the values to *out that we put in the appropriate amount of whitespace> Ganually writin& space charac1 ters would #e difficult- so instead we'll use a stream manipulator called set' Kset widthL to force *out to pad its output with the ri&ht num#er of spaces! set' is defined in the iomani$) header file and can #e used as fol1 lows=
*out set6!7-$ 8FG endl;
This tells *out that the ne)t item it prints out should #e padded with spaces so that it takes up at least ten charac1 ters! Similarly*out set6!8-$ ./ello there0. endl;
0ould print out /ello there0 with sufficient leadin& whitespace! 7y default set' pads the ne)t operation with spaces on the left side! .ou can customize this #ehavior with the left and right stream manipulators- as shown here=
*out *out A[A A[A le t right set6!7-$ set6!7-$ ./ello0. ./ello0. A]A A]A endl; endl; "" 9 'ello%: "" 9'ello% :
7ack to our e)ample! 0e want to ensure that every ta#le column is e)actly 4=6@:EB1(9;/ spaces across! Us1 in& set'- this is relatively strai&htforward and can #e done as follows=
1 A0 1
Cha#ter ;% ,treams
void <rint;ableOod%() { ifstream in$ut(.table,data.t7t.); "2 Eo error,*he*+ing here, but %ou should be sure to do this in an% real 2 $rogram. 2" "2 6oo$ over the lines in the file reading data. 2" for(int + = 0; + E@:B6(EE5; !!+) { int intDalue; double doubleDalue; in$ut )) intDalue )) doubleDalue; cout .. set6!;<1U0/&=IDT'$ .. !+ 3 7$ .. " > "; cout .. set6!;<1U0/&=IDT'$ .. int5alue .. " > "; cout .. set6!;<1U0/&=IDT'$ .. double5alue .. endl;
# #
This produces the followin& output when run on the input file descri#ed a#ove=
8 H F J N N N N 8FG JH GILGLIG 8FFG N N N N H.G8IHI F.8J8KL 8.M0I 0.08808
The #ody of the ta#le looks &reat- and now we *ust need to print the ta#le header- which looks like this=
,,,,,,,,,,,,,,,,,,,,,!,,,,,,,,,,,,,,,,,,,,,,!,,,,,,,,,,,,,,,,,,,,,
'f you'll notice- this is formed #y printin& twenty dashes- then the pattern ,!,- another twenty dashes- the pattern ,!,- and finally another twenty dashes! 0e could thus implement <rint;able/eader like this=
)oid PrintTable'eader!$ * "2 <rint the ,,,...,,,!, $attern for all but the last *olumn. 2" or!int column , -; column . /U0&;<1U0/S ? 7; 33column$ * or!int + , -; + . ;<1U0/&=IDT'; 33+$ cout .. @-@; cout .. "-3-"; 4 "2 Eo' $rint the ,,,...,,, $attern for the last *olumn. 2" or!int + , -; + . ;<1U0/&=IDT'; 33+$ cout .. @-@; "2 <rint a ne'line... thereAs nothing else on this line. 2" cout .. endl; 4
,s written there's nothin& wron& with this code and the pro&ram will work *ust fine- #ut we can simplify the im1 plementation #y harnessin& stream manipulators! "otice that at two points we need to print out 4=6@:EB1(9;/ copies of the dash character! 0hen printin& out the ta#le #ody- we were a#le to use the set' stream manipulator to print multiple copies of the space characterR is there some way that we can use it here to print out multiple dashes> The answer is yes- thanks to setfill! The setfill manipulator accepts a parameter indicatin& what
Cha#ter ;% ,treams
1 A1 1
character to use as a fill character for set'- then chan&es the stream such that all future calls to set' pad the stream with the specified character! 8or e)ample=
*out *out set ill!@-@$ set6!A$ 8000 endl; "" <rints ----7--set6!A$ 8000 endl; "" <rints ----7--- be*ause of last setfill
"ote that setfill does not replace all space characters with instances of some other character! 't is only mean1 in&ful in con*unction with set'! 8or e)ample=
*out setfill(APA) .5ome 5$a*es. endl; "" <rints Some Spaces
Usin& setfill and set'- we can print out 4=6@:EB1(9;/ copies of the dash character as follows=
cout .. set ill!@-@$ .. set6!;<1U0/&=IDT'$ .. "" .. set ill!@ @$;
This code is dense- so let's walk throu&h it one step at a time! The first part- setfill(A,A)- tells *out to pad all output with dashes instead of spaces! "e)t- we use set' to tell *out that the ne)t operation should take up at least 4=6@:EB1(9;/ characters! The trick is the ne)t step- printin& the empty strin&! Since the empty strin& has len&th zero and the ne)t operation will always print out at least 4=6@:EB1(9;/ characters padded with dashes- this code prints out 4=6@:EB1(9;/ dashes in a row! 8inally- since setfill permanently sets the fill character- we use setfill(A A) to undo the chan&es we made to *out! Usin& this code- we can rewrite <rint;able/eader as follows=
void <rint;able/eader() { "2 <rint the ,,,...,,,!, $attern for all but the last *olumn. 2" for(int *olumn = 0; *olumn E@:B4=6@:E5 Q 8; !!*olumn) cout .. set ill!@-@$ .. set6!;<1U0/&=IDT'$ .. "" .. "-3-"; "2 Eo' $rint the ,,,...,,, $attern for the last *olumn and a ne'line. 2" cout .. set6!;<1U0/&=IDT'$ .. "" .. set ill!@ @$ .. endl; #
"otice that we only call setfill(A A) once- at the end of this function- since there's no reason to clear it at each step! ,lso notice that we've reduced the len&th of this function dramatically #y havin& the li#rary take care of the heavy liftin& for us! The code to print out a ta#le header is now three lines lon&U There are many stream manipulators availa#le in C++! The followin& ta#le lists some of the more commonly1 used ones=
boolal$ha *out *out true endl; boolal$ha true endl; "" =ut$ut3 8 "" =ut$ut3 true
2etermines whether or not the stream should output #oolean values as 1 and 0 or as 4true6 and 4false!6 The opposite manipulator is noboolal$ha- which reverses this #e1 havior!
set'(n) *out *out 80 endl; set'(K) 80 "" =ut$ut3 80 endl; "" =ut$ut3 80
Sets the minimum width of the output for the ne)t stream operation! 'f the data doesn't meet the minimum field re5uirement- it is padded with the default fill character until it is the proper size!
Cha#ter ;% ,treams
"" =ut$ut3 80 endl; "" =ut$ut3 80 endl; "" =ut$ut3 8H endl; "" =ut$ut3 a "" Ceads a he7ade*imal value.
Sets the radi) on the stream to either octal K#ase EL- decimal K#ase 10L- or he)adecimal K#ase 16L! This can #e used either to format output or chan&e the #ase for input!
's m%5tream )) 's )) value;
Skips any whitespace stored in the stream! 7y default the stream e)traction operator skips over whitespace- #ut other functions like getline do not! 's can sometimes #e useful in con*unction with these other functions! /hen Streams :o ,ad 7ecause stream operations often involve transformin& data from one form into another- stream operations are not always &uaranteed to succeed! 8or e)ample- consider the followin& code snippet- which reads inte&er values from a file=
ifstream in(.in$ut.t7t.); "" Cead from in$ut.t7t for(int i = 0; i E@:B(E;5; !!i) { int value; in )) value; "2 ... $ro*ess value here ... 2" #
'f the file in$ut.t7t contains E@:B(E;5 consecutive inte&er values- then this code will work correctly! /owever- what happens if the file contains some other type of data- such as a strin& or a real num#er> 'f you try to read stream data of one type into a varia#le of another type- rather than crashin& the pro&ram or fillin& the varia#le with &ar#a&e data- the stream fails #y enterin& an error state and the value of the varia#le will not chan&e! Hnce the stream is in this error state- any su#se5uent read or write operations will automatically and silently fail- which can #e a serious pro#lem! .ou can check if a stream is in an error state with the .fail() mem#er function! 2on't let the name mislead you ( fail checks if a stream is in an error state- rather than puttin& the stream into that state! 8or e)amplehere's code to read input from *in and check if an error occurred=
int m%(nteger; *in )) m%(nteger; if(cin. ail!$) { "2 ... error ... 2" #
'f a stream is in a fail state- you'll pro#a#ly want to perform some special handlin&- possi#ly #y reportin& the er1 ror! Hnce you've fi)ed any pro#lems- you need to tell the stream that everythin& is okay #y usin& the .*lear() mem#er function to #rin& the stream out of its error state! "ote that *lear won't skip over the input that put the stream into an error stateR you will need to e)tract this input manually! Streams can also &o into error states if a read operation fails #ecause no data is availa#le! This occurs most com1 monly when readin& data from a file! et's return to the ta#le1printin& e)ample! 'n the <rint;able9ata func1 tion- we hardcoded the assumption that the file contains e)actly four lines of data! 7ut what if we want to print
Cha#ter ;% ,treams
1 AA 1
out ta#les of ar#itrary len&th> 'n that case- we'd need to continuously read throu&h the file e)tractin& and print1 in& num#ers until we e)haust its contents! 0e can tell when we've run out of data #y checkin& the .fail() mem#er function after performin& a read! 'f .fail() returns true- somethin& prevented us from e)tractin& data Keither #ecause the file was malformed or #ecause there was no more dataL and we can stop loopin&! Fecall that the ori&inal code for readin& data looks like this=
void <rint;ableOod%() { ifstream in$ut(.table,data.t7t.); "2 6oo$ over the lines in the file reading data. 2" for(int + = 0; + E@:B6(EE5; !!+) { int intDalue; double doubleDalue; in$ut )) intDalue )) doubleDalue; *out *out *out set'(4=6@:EB1(9;/) set'(4=6@:EB1(9;/) set'(4=6@:EB1(9;/) (+ ! 8) . N .; intDalue . N .; doubleDalue endl;
# #
The updated version of this code- which reads all of the contents of the file- is shown here=
void <rint;ableOod%() { ifstream in$ut(.table,data.t7t.); "2 6oo$ over the lines in the file reading data. 2" int ro6/umber , -; 6hile!true$ { int intDalue; double doubleDalue; in$ut )) intDalue )) doubleDalue; i !input. ail!$$ brea+; *out *out *out # # set'(4=6@:EB1(9;/) set'(4=6@:EB1(9;/) set'(4=6@:EB1(9;/) (ro6/umber ! 8) . N .; intDalue . N .; doubleDalue endl;
ro6/umber33;
"otice that we put the main lo&ic into a 'hile(true) loop that brea+s when in$ut.fail() returns true in1 stead of a 'hile(0in$ut.fail()) loop! These two structures may at first appear similar- #ut are 5uite differ1 ent from one another! 'n a 'hile(0in$ut.fail()) loop- we only check to see if the stream encountered an error after readin& and processin& the data in the #ody of the loop! This means that the loop will e)ecute once more than it should- #ecause we don't notice that the stream malfunctioned until the top of the loop! Hn the other hand- in the a#ove loop structure K'hile(true) plus brea+L- we stop loopin& as soon as the stream realizes that somethin& has &one awry! Confusin& these two loop structures is a common error- so #e sure that you un1 derstand why to use the 4loop1and1a1half6 idiom rather than a simple 'hile loop!
1 AB 1 /hen Streams 9o Too #uch Consider the followin& code snippet- which prompts a user for an a&e and hourly salary=
int age; double hourl%1age; *out .<lease enter %our age3 .; *in )) age; *out .<lease enter %our hourl% 'age3 .; *in )) hourl%1age;
Cha#ter ;% ,treams
,s mentioned a#ove- if the user enters a strin& or otherwise non1inte&er value when prompted for their a&e- the stream will enter an error state! There is another ed&e case to consider! Suppose the input is H.G8IHI! .ou would e)pect that- since this isn't an inte&er Kit's a real num#erL- the stream would &o into an error state! /owever- this isn't what happens! The first call- *in )) age- will set age to H! The ne)t call*in )) hourl%1age- rather than promptin& the user for a value- will find the .G8IHI from the earlier input and fill in hourl%1age with that information! 2espite the fact that the input was malformed for the first prompt- the stream was a#le to partially interpret it and no error was si&naled! ,s if this wasn't #ad enou&h- suppose we have this pro&ram instead- which prompts a user for an administrator password and then asks whether the user wants to format her hard drive=
string $ass'ord; *out .Enter administrator $ass'ord3 .; cin >> pass6ord; if($ass'ord == .$ass'ord.) "" @se a better $ass'ord, b% the 'a%0 { *out .9o %ou 'ant to erase %our hard drive (R or E)S .; *har %es=rEo; cin >> yes<r/o; if(%es=rEo == A%A) Erase/ard9rive(); #
0hat happens if someone enters $ass'ord %> The first call- *in )) $ass'ord- will read only $ass'ord! Hnce we reach the second *in read- it automatically fills in %es=rEo with the leftover %- and there &oes our hard driveU Clearly this is not what we intended! ,s you can see- readin& directly from *in is unsafe and poses more pro#lems than it solves! 'n CS1067IJ we provide you with the sim$io.h li#rary primarily so you don't have to deal with these sorts of errors! 'n the ne)t section- we'll e)plore an entirely different way of readin& input that avoids the a#ove pro#lems! An Alternati7e5 getline Up to this point- we have #een readin& data usin& the stream e)traction operator- which- as you've seen- can #e dan&erous! /owever- there are other functions that read data from a stream! Hne of these functions is getlinewhich reads characters from a stream until a newline character is encountered- then stores the read characters Kminus the newlineL in a string! getline accepts two parameters- a stream to read from and a string to write to! 8or e)ample- to read a line of te)t from the console- you could use this code=
string m%5tr; getline!cinB myStr$;
"o matter how many words or tokens the user types on this line- #ecause getline reads until it encounters a newline- all of the data will #e a#sor#ed and stored in m%5tr! Goreover- #ecause any data the user types in can
Cha#ter ;% ,treams
1 A? 1
#e e)pressed as a strin&- unless your input stream encounters a read error- getline will not put the stream into a fail state! "o lon&er do you need to worry a#out stran&e 'IH ed&e casesU .ou may have noticed that the getline function acts similarly to the CS1067IJ Get6ine function! This is no coincidence- and in fact the Get6ine function from sim$io.h is implemented as follows=Q
string Get6ine() { string result; getline!cinB result$; return result; #
,t this point- getline may seem like a silver1#ullet solution to our input pro#lems! /owever- getline has a small pro#lem when mi)ed with the stream e)traction operator! 0hen the user presses return after enterin& te)t in response to a *in prompt- the newline character is stored in the *in internal #uffer! "ormally- whenever you try to e)tract data from a stream usin& the )) operator- the stream skips over newline and whitespace characters #efore readin& meanin&ful data! This means that if you write code like this=
int first, se*ond; *in )) first; *in )) se*ond;
The newline stored in *in after the user enters a value for first is eaten #y *in #efore se*ond is read! /owever- if we replace the second call to *in with a call to getline- as shown here=
int dumm%(nt; string dumm%5tring; *in )) dumm%(nt; getline!cinB dummyString$; getline will return an empty strin&! 0hy> Unlike the stream e)traction operator- getline does not skip over the whitespace still remainin& in the *in stream! Conse5uently- as soon as getline is called- it will find the newline remainin& from the previous *in statement- assume the user has pressed return- and return the empty
strin&! To fi) this pro#lem- your #est option is to replace all normal stream e)traction operations with calls to li#rary functions like Get(nteger and Get6ine that accomplish the same thin&! 8ortunately- with the information in the ne)t section- you'll #e a#le to write Get(nteger and almost any GetBBBB function you'd ever need to use! 0hen we cover templates and operator overloadin& in later chapters- you'll see how to #uild a &eneric read func1 tion that can parse any sort of data from the user! A String ,uffer5 stringstream 7efore we discuss writin& Get(nteger- we'll need to take a diversion to another type of C++ stream! Hften you will need to construct a strin& composed #oth of plain te)t and numeric or other data! 8or e)amplesuppose you wanted to call this hypothetical function=
void :essageOo7Alert(string message);
Q Technically- the implementation of Get6ine from sim$io.h is sli&htly different- as it checks to make sure that *in is not in an error state #efore readin&!
1 A6 1
Cha#ter ;% ,treams
and have it display a messa&e #o) to the user informin& her that the level num#er she wanted to warp to is out of #ounds! ,t first thou&ht- you mi&ht try somethin& like
int levelEum = "2 ... 2"; :essageOo7Alert(.6evel . ! levelEum ! . is out of bounds..); "" 2CC<C
8or those of you with $ava e)perience this mi&ht seem natural- #ut in C++ this isn't le&al #ecause you can't add num#ers to strin&s Kand when you can- it's almost certainly won't do what you e)pectedR see the chapter on C strin&sL! Hne solution to this pro#lem is to use another kind of stream o#*ect known as a stringstream- e)ported #y the sstream) header! ike console streams and file streams- stringstreams are stream o#*ects and con1 se5uently all of the stream operations we've covered a#ove work on stringstreams! /owever- instead of readin& or writin& data to an e)ternal source- stringstreams store data in temporary strin& #uffers! 'n other words- you can view a stringstream as a way to create and read strin& data usin& stream operations! 8or e)ample- here is a code snippet to create a stringstream and put te)t data into it=
stringstream m%5tream; myStream .. "'ello%" .. 7DE;
Hnce you've put data into a stringstream- you can retrieve the strin& you've created usin& the .str() mem1 #er function! Continuin& the a#ove e)ample- we can print out an error messa&e as follows=
int levelEum = "2 ... 2"; stringstream messageTe#t; messageTe#t .. "1e)el " .. le)el/um .. " is out o :essageOo7Alert(messageTe#t.str!$);
bounds.";
stringstreams are an e)ample of an iostream- a stream that can perform #oth input and output! .ou can #oth insert data into a stringstream to convert the data to a strin& and e)tract data from a stringstream to
The standard rules &overnin& stream e)traction operators still apply to stringstreams- so if you try to read data from a stringstream in one format that doesn't match the character data- the stream will fail! 0e'll e)1 ploit this functionality in the ne)t section! -utting it all together5 /riting FetInteger Usin& the techni5ues we covered in the previous sections- we can implement a set of ro#ust user input functions alon& the lines of those provided #y sim$io.h! 'n this section we'll e)plore how to write Get(nteger- which prompts the user to enter an inte&er and returns only after the user enters valid input! Fecall from the a#ove sections that readin& an inte&er from *in can result in two types of pro#lems! 8irst- the user could enter somethin& that is not an inte&er- causin& *in to fail! Second- the user could enter too much in1 put- such as 8FG HJM or /ello FG- in which case the operation succeeds #ut leaves e)tra data in *in that can &ar#le future reads! 0e can immediately eliminate these sorts of pro#lems #y usin& the getline function to
Cha#ter ;% ,treams
1 A@ 1
read input- since getline cannot put *in into a fail state and &ra#s all of the user's data- rather than *ust the first token! The main pro#lem with getline is that the input is returned as a string- rather than as formatted data! 8ortu1 nately- usin& a stringstream- we can convert this te)t data into another format of our choice! This su&&ests an implementation of Get(nteger! 0e read data from the console usin& getline and funnel it into a string, stream! 0e then use standard stream manipulations to e)tract the inte&er from the stringstream- reportin& an error and repromptin& if una#le to do so! 0e can start writin& Get(nteger as follows=
int FetInteger!$ { 6hile!true$ "" Cead in$ut until user enters valid data { stringstream con)erter; con)erter .. Fet1ine!$; "2 <ro*ess data here. cout .. "Cetry: " # # =n error3 2"
,t this point- we've read in all of the data we need- and simply need to check that the data is in the proper format! ,s mentioned a#ove- there are two sorts of pro#lems we mi&ht run into ( either the data isn't an inte&er- or the data contains leftover information that isn't part of the inte&er! 0e need to check for #oth cases! Checkin& for the first turns out to #e pretty simple ( #ecause stringstreams are stream o#*ects- we can see if the data isn't an inte&er #y e)tractin& an inte&er from our stringstream and checkin& if this puts the stream into a fail state! 'f so- we know the data is invalid and can alert the user to this effect! The updated code for Get(nteger is as follows=
int Get(nteger() { 'hile(true) "" Cead in$ut until user enters valid data { stringstream *onverter; *onverter Get6ine(); "2 ;r% reading an int, *ontinue if 'e su**eeded. 2" int result; con)erter >> result; i !%con)erter. ail!$$ * "2 ... *he*+ that there isnAt an% leftover data ... 2" 4 else cout .. "Please enter an integer." .. endl; *out .Cetr%3 . # #
8inally- we need to check if there's any e)tra data left over! 'f so- we need to report to the user that somethin& is wron& with the input- and can otherwise return the value we read! 0hile there are several ways to check thisone simple method is to read in a sin&le *har from the stringstream! 'f it is possi#le to do so- then we know that there must have #een somethin& in the input stream that wasn't picked up when we e)tracted an inte&er and conse5uently that the input is #ad! Htherwise- the stream must #e out of data and will enter a fail state- si&nalin& that the user's input was valid! The final code for Get(nteger- which uses this trick- is shown here=
1 AE 1
int Get(nteger() { 'hile(true) "" Cead in$ut until user enters valid data { stringstream *onverter; *onverter Get6ine();
Cha#ter ;% ,treams
"2 ;r% reading an int, *ontinue if 'e su**eeded. 2" int result; *onverter )) result; if(0*onverter.fail()) { char remaining; con)erter >> remaining; "" 4he*+ for stra% in$ut i !con)erter. ail!$$ "" 4ouldnAt read an% more, so in$ut is valid return result; else cout .. "Une#pected character: " .. remaining .. endl; # else *out .<lease enter an integer.. endl; *out .Cetr%3 . # #
#ore To $%&lore C++ streams are e)tremely powerful and encompass a hu&e amount of functionality! 0hile there are many more facets to e)plore- ' hi&hly recommend e)plorin& some of these topics= 1andom Access5 Gost of the time- when performin& 'IH- you will access the data se5uentiallyR that isyou will read in one piece of data- then the ne)t- etc! /owever- in some cases you mi&ht know in ad1 vance that you want to look up only a certain piece of data in a file without considerin& all of the data #efore it! 8or e)ample- a %'C archive containin& a directory structure most likely stores each com1 pressed file at a different offset from the start of the file! Thus- if you wanted to write a pro&ram capa#le of e)tractin& a sin&le file from the archive- you'd almost certainly need the a#ility to *ump to ar#itrary locations in a file! C++ streams support this functionality with the see+g- tellg- see+$- and tell$ functions Kthe first two for istreams- the latter for ostreamsL! Fandom access lets you 5uickly *ump to sin&le records in lar&e data #locks and can #e useful in data file desi&n! read and 6rite5 0hen you write numeric data to a stream- you're actually convertin& them into se1 5uences of characters that represent those num#ers! 8or e)ample- when you print out the four1#yte value GILGLIG8- you're usin& ei&ht #ytes to represent the data on screen or in a file ( one for each character! These e)tra #ytes can 5uickly add up- and it's actually possi#le to have on1disk representations of data that are more than twice as lar&e as the data stored in memory! To &et around this- C++ streams let you directly write data from memory onto disk without any formattin&! ,ll ostreams support a 'rite function that writes unformatted data to a stream- and istreams support read to read unformatted data from a stream into memory! 0hen used well- these functions can cut file loadin& times and reduce disk space usa&e! 8or e)ample- The CS1067IJ 6e7i*on class uses read to 5uickly load its data file into memory!
1 A< 1
/ere are some 5uestions to help you play around with the material from this chapter! D)ercises with a diamond character have answers in ,ppendi) Hne! 1! 0rite a function E7tra*t&irst;o+en that accepts a strin& and returns the first token from that strin&! 8or e)ample- if you passed in the strin& 4Dleanor Foosevelt-6 the function should return 4Dleanor!6 8or our purposes- define a token as a sin&le continuous #lock of characters with no intervenin& whitespace! 0hile it's possi#le to write this usin& the C li#rary function iss$a*e and a for loop- there's a much shorter solution levera&in& off of a stringstream! ;! 'n common usa&e- num#ers are written in decimal or base /$! This means that a strin& of di&its is inter1 preted as a sum of multiples of powers of ten! 8or e)ample- the num#er 1A@ is 1X100 + AX10 + @X1which is the same as 1X10; + AX101 + @X100! /owever- it is possi#le to write num#ers in other #ases as well! 8or e)ample- octal- or #ase E- encodes num#ers as sums of multiples of powers of ei&ht! 8or e)1 ample- 1A@ in octal would #e 1XE; + AXE1 + @XE0 Y 6B + ;B + @ Y <? in decimal!Q Similarly- binary- or #ase ;- uses powers of two! 0hen workin& in a particular #ase- we only use di&its from 0 up to that #ase! Thus in #ase 10 we use the di&its zero throu&h nine- while in #ase five the only di&its would #e 0- 1- ;- A- and B! This means that ?@ is not a valid #ase1five num#er and <A is not a valid octal num#er! 0hen workin& in #ases num#ered hi&her than ten- it is customary to use letters from the #e&innin& of the alpha#et as di&its! 8or e)amplein he7adecimal- or #ase 16- one counts 0- 1- ;- !!!- <- ,- 7- C- 2- D- 8- 10! This means that A2B?D is a valid he)adecimal num#er- as is 2D,27DD8 or 2D8,CD2! 0rite a function /as/e76etters that accepts an int and returns whether or not that inte&er's he)a1 decimal representation contains letters! <+int% you(ll need to use the hex and dec stream mani#ulators in conjunction with a stringstream) Try to sol!e this #roblem without brute-"orcing it% le!erage o"" the streams library instead o" using loo#s)= M A! Godify the code for Get(nteger to create a function GetCeal that reads a real num#er from the user! /ow much did you need to modify to make this code work> B! Usin& the code for Get(nteger and the boolal$ha stream manipulator- write a function GetOoolean that waits for the user to enter 4true6 or 4false6 and returns the correspondin& #oolean value! ?! ,lthou&h the console does not naturally lend itself to &raphics pro&rammin&- it is possi#le to draw rudi1 mentary appro)imations of poly&ons #y printin& out multiple copies of a character at the proper location! 8or e)ample- we can draw a trian&le #y drawin& a sin&le character on one line- then three on the ne)tfive on the line after that- etc! 8or e)ample=
---------------------
Usin& the set' and setfill stream manipulators- write a function 9ra';riangle that takes in an int correspondin& to the hei&ht of the trian&le and a *har representin& a character to print- then draws a trian&le of the specified hei&ht usin& that character! The trian&le should #e ali&ned so that the #ottom row starts at the #e&innin& of its line! 6! 0rite a function =$en&ile that accepts as input an ifstream #y reference and prompts the user for the name of a file! 'f the file can #e found- =$en&ile should return with the ifstream opened to read that file! Htherwise- =$en&ile should print an error messa&e and reprompt the user! <+int% I" you try to o#en a none7istent "ile with an ifstream, the stream goes into a "ail state and you will need to use !clearKL to restore it be"ore trying again=!
Q 0hy do pro&rammers always confuse /alloween and Christmas> 7ecause A1 Hct Y ;? 2ec! Z
In 5ctober o" />9? I obser!ed that a certain algorithm @ #arallel reduction @ was associated with monoids% collections o" elements with an associati!e o#eration) That obser!ation led me to belie!e that it is #ossible to associate e!ery use"ul algorithm with a mathematical theory and that such association allows "or both widest #ossible use and meaning"ul ta7onomy) As mathematicians learned to li"t theorems into their most general settings, so I wanted to li"t algorithms and data structures) ( ,le) Stepanov- inventor of the ST ! OSte0@P The Standard Template i#rary KST L is a pro&rammer's dream! 't offers efficient ways to store- access- manipu1 late- and view data and is desi&ned for ma)imum e)tensi#ility! Hnce you've &otten over the initial synta) hurdles- you will 5uickly learn to appreciate the ST 's sheer power and fle)i#ility! To &ive a sense of e)actly where we're &oin&- here are a few 5uick e)amples of code usin& the ST = 0e can create a list of random num#ers- sort it- and print it to the console in "our lines o" code.
ve*tor int) m%De*tor(E@:B(E;5); generate(m%De*tor.begin(), m%De*tor.end(), rand); sort(m%De*tor.begin(), m%De*tor.end()); *o$%(m%De*tor.begin(), m%De*tor.end(), ostreamBiterator int)(*out, .Tn.));
0e can open a file and print its contents in two lines o" code.
ifstream in$ut(.m%,file.t7t.); *o$%(istreambufBiterator *har)(in$ut), istreambufBiterator *har)(), ostreambufBiterator *har)(*out));
'f you aren't already impressed #y the possi#ilities this li#rary entails- keep readin&! .ou will not #e disappoin1 ted! 27er7iew of the STL The ST is lo&ically divided into si) pieces- each consistin& of &eneric components that interoperate with the rest of the li#rary=
Containers! ,t the heart of the ST are a collection of container classes- standard C++'s analo& to the CS1067IJ ,2Ts! 8or e)ample- you can store an associative collection of keyIvalue pairs in an ST ma$- or a &rowin& list of elements in an ST ve*tor! Iterators! Dach ST container e)ports iterators- o#*ects that view and modify ran&es of stored data! 'terators have a common interface- allowin& you to write code that operates on data stored in ar#itrary containers! Algorithms! ST al&orithms are functions that operate over ran&es of data specified #y iterators! The scope of the ST al&orithms is sta&&erin& ( there are al&orithms for searchin&- sortin&- reorderin&- per1 mutin&- creatin&- and destroyin& sets of data!
1 B; 1
Cha#ter 8% ,TL Containers, Part I Ada&ters! ST ada#ters are o#*ects which transform an o#*ect from one form into another! 8or e)1 ample- the sta*+ adapter transforms a re&ular ve*tor or list into a '8H container- while the istreamBiterator transforms a standard C++ stream into an ST iterator! !unctors! 7ecause so much of the ST relies on user1defined call#ack functions- the ST provides fa1 cilities for creatin& and modifyin& functions at runtime! 0e will defer our discussion of functors to the second half of this te)t- as they re5uire operator overloadin&! Allocators! The ST allows clients of the container classes to customize how memory is allocated and deallocated- either for dia&nostic or performance reasons! 0hile allocators are an interestin& topic worthy of discussion- they are #eyond the scope of this course and we will not cover them!
ALGORITHMS
FUNCTORS
ITERATORS
ADAPTERS
ALLOCATORS
CONTAINERS
/ere- the containers rely on the allocators for memory and produce iterators! 'terators can then #e used in con1 *unction with the al&orithms! 8unctors provide special functions for the al&orithms- and adapters can produce functors- iterators- and containers! 'f this seems a #it confusin& now- don't worry- you'll understand this relation1 ship well #y the time you've finished the ne)t few chapters! A /ord on Safety 'n a sense- the ST is like the CS1067IJ ,2T li#rary ( it provides a way for you to store and manipulate data! There are- of course- many differences #etween the li#raries- of which the lar&est is safety! 0ith the CS1067IJ li#raries- if you try to access the tenth element of a nine1element De*tor- you'll &et an error messa&e alertin& you to the fact and your pro&ram will terminate! 0ith the ST - doin& this results in unde"ined beha!ior! This means that anythin& could happen ( your pro&ram mi&ht continue with &ar#a&e data- it mi&ht crash- or it mi&ht even cause yellow ducks to show up on the screen and start dancin&! 0hat's important to note- thou&h- is that this means that the ST can easily introduce nasty #u&s into your code! Gost of the time- if you read one spot over the end in an ST ve*tor- nothin& serious will happen- #ut when somethin& #ad does come up you'll &et crashes that can #e tricky to track down! Hn the plus side- however- this lack of error checkin& means the ST container classes are much- much faster than anythin& you'll &et from the CS1067IJ li#raries! 'n fact- the ST is so fast that it's reasona#le to assume that if there's an ST and a non1ST way to accomplish a task- the ST way is &oin& to #e faster! 0hat does this all mean> 7asically- you'll have to stop relyin& on user1friendly error messa&es to help de#u& your code! 'f you have #ounds1checkin& issues with your ST containers- or try to pop an element off an empty sta*+- you'll pro#a#ly #e directed to a cryptic ST implementation file where the error occurred! 't's the price you pay for efficiency!
1 BA 1
The compiler errors you will encounter when makin& errors with the ST are nothin& short of terrifyin&! "or1 mally- if you leave a semicolon off a line- or try to assi&n a CS1067IJ De*tor string) to a :a$ string)you'll &et error messa&es that are somewhat comprehensi#le! "ot so with the ST ! 'n fact- ST errors are so difficult to read that it's &oin& to take you a while #efore you'll have any idea what you're lookin& at! /ere's a sample ST error messa&e caused #y passin& a strin& into a function e)pectin& an int=
*3T...Talgorithm(ILG) 3 error 4HJJM3 A==A 3 no *onversion from A*onst *har 2A to AintA ;here is no *onte7t in 'hi*h this *onversion is $ossible *3T...Talgorithm(L0M) 3 see referen*e to fun*tion tem$late instantiation Avoid std33BCe$la*e std33BDe*torBiterator B;%,BAllo*),*onst *har[M])(B&'d(t,B&'d(t,*onst *har (?),*onst *har (?))A being *om$iled 'ith [ B;%=int, BAllo*=std33allo*ator int), B&'d(t=std33BDe*torBiterator int,std33allo*ator int)) ] *3T...Tmain.*$$(88) 3 see referen*e to fun*tion tem$late instantiation Avoid std33re$la*e std33BDe*torBiterator B;%,BAllo*),*onst *har[M])(B&'d(t,B&'d(t,*onst *har (?),*onst *har (?))A being *om$iled 'ith [ B;%=int, BAllo*=std33allo*ator int), B&'d(t=std33BDe*torBiterator int,std33allo*ator int)) ] *3T...Talgorithm(ILG) 3 error 4H0J03 A==A 3 AintA differs in levels of indire*tion from A*onst *har [M]A *3T...Talgorithm(ILI) 3 error 4HJJ03 A=A 3 *annot *onvert from A*onst *har [M]A to AintA ;here is no *onte7t in 'hi*h this *onversion is $ossible
0hen you &et ST compiler errors- make sure to take the time to slowly read them and to compare the different types that they mention! 'f you have a type mismatch- it won't #e immediately o#vious- #ut it will #e there! Similarly- if you try to call a mem#er function that doesn't e)ist in a container class- #e prepared to spend a few seconds readin& what type the compiler reports #efore you even &et to the mem#er function that caused the error! 0ith that said- let's #e&in our trek into the heart of the ST U The remainder of this chapter focuses on container classes- o#*ects like the CS1067IJ ,2Ts that store data in a specialized format! 0e'll e)plore a samplin& of the ST containers- how to harness their firepower- and what issues to #e aware of when usin& them! STL Containers 7s CS1+G,=D A9Ts .ou're pro#a#ly *ust &ettin& used to the CS1067IJ ,2Ts- so switchin& over to the ST container classes mi&ht #e a #it *arrin&! 8ortunately- for almost all of the CS1067IJ ,2Ts there is a correspondin& ST container class Kthe nota#le e)ception is GridL! The ST versions of the CS1067IJ ,2Ts have the same name- e)cept in lowercase= for e)ample- the CS106 De*tor is analo&ous to the ST ve*tor! 8urthermore- anythin& you can do with the CS1067IJ ,2Ts you can also do with the ST container classes- althou&h the synta) mi&ht #e more cryptic! The main advanta&es of the ST mi&ht not #e immediately apparent- #ut once you've finished the chapter on ST al&orithms you'll see e)actly how much the ST 's capa#ility dwarfs that of the CS1067IJ ,2T li#rary!
1 BB 1
To use the ST containers- you'll need to use a different set of headers than those you'll use for the CS1067IJ ,2Ts! The header files for the ST use an&le #rackets K and )L and don't end in !h! 8or e)ample- the header for the ST ve*tor is [ve*tor)- instead of the CS1067IJ .ve*tor.h.! stac+ Cerhaps the simplest ST container is the sta*+! The ST sta*+ is similar to the CS1067IJ 5ta*+ in most ways= it's a last1in- first1out K '8HL container that supports $ush and $o$R you can test to see if it's empty and- if not- how tall it isR and you can peek at the top element! /owever- there are a few su#tle differences #etween the CS1067IJ 5ta*+ and the ST sta*+- as you'll see in a minute! The followin& ta#le summarizes the mem#er functions you can perform on a sta*+! 0e have not covered the *onst keyword yet- so for now feel free to i&nore it! CS1+G,=D Stac+
int siUe()
STL stac+
siUeBt%$e siUe() *onst
9ifferences
,lthou&h the ST stack's return type is int- these functions #ehave identically!
siUeBt%$e
instead of
bool isEm$t%()
2on't &et confused #y the name= em$t% does not empty out the sta*+! The ST uses the function name em$t% instead of the CS1067IJ isEm$t%- #ut they do e)actly the same thin&! These functions do e)actly the same thin&- e)cept that the ST to$ function does not perform any #ounds1checkin&! These functions are essentially identical! See the ne)t section! There is no ST
sta*+
;? $ee+()
e5uivalent of *lear!
,s you can see- most of the functions from the CS1067IJ 5ta*+ work the same way in the ST sta*+- al#eit with a few syntactic differences! The #i& difference- however- is the $o$ function! 0ith the CS1067IJ 5ta*+you can write code like this=
int to$Elem = m%4580M5ta*+.$o$();
This is not the case with the ST sta*+! The ST function $o$ removes the top element from the stack #ut does not return a value! Thus- an ST version of the a#ove code mi&ht look somethin& like this=
int to$Elem = myST1Stac+.top!$; myST1Stac+.pop!$;
0hile this may make your code sli&htly less reada#le- it will improve your pro&rams' runtime efficiency #ecause the to$ function doesn't create a copy of the o#*ect on top of the sta*+! 't's much faster to return a reference to an o#*ect than a copy of it- and if your sta*+ stores non1primitive types the performance #oost over the CS1067IJ 5ta*+ is appecia#le! Gueue ,nother simple ST container class is the Vueue- which #ehaves in much the same way as the CS1067IJ Wueue! /ere's another summary ta#le=
1 B? 1 9ifferences
,lthou&h the ST Vueue's return type is int- these functions #ehave identically!
em$t% siUeBt%$e
STL Gueue
siUeBt%$e siUe() *onst
instead of
bool isEm$t%()
does not empty out the Vueue! The ST uses the func1 tion name em$t% instead of the CS1067IJ isEm$t%- #ut they do e)actly the same thin&! These functions do e)actly the same thin&- e)cept that the ST front function does not perform any #ounds1checkin&! ,lthou&h it's irksome that the Vueue uses 4push6 and 4pop6 in1 stead of 4en5ueue6 and 4de5ueue-6 these functions are the same! ,s with the sta*+- $o$ does not return the value it removes! There is no Vueue 4clear6 function! Feturns a reference to the #ack of the element that was $ushed on!
Vueue-
;? $ee+()
"otice that the enVueueIdeVueue functions you know from CS1067IJ are now called $ush and $o$! ,l1 thou&h they are called $ush and $o$ instead of enVueue and deVueue- they #ehave identically to their CS1067IJ Wueue counterparts! This unfortunate synta) is one of the reasons that CS1067 and CS106J don't use the ST ! )ector 8ortunately Kor unfortunately- dependin& on how you look at itL if you thou&ht that the ST was e)actly the same as the CS1067IJ li#raries with different function names- the only two 4simple6 ST containers are sta*+ and Vueue!Q 't's now time to deal with the ST ve*tor- one of the ST 's most u#i5uitous containers! 'n this ne)t section we'll talk a#out some #asic ve*tor functionality- concludin& with a summary of important ve*tor mem#er functions! !i%edCsi3ed )ectors The simplest way to use a ve*tor is to treat it as thou&h it's a mana&ed- fi)ed1size array! 8or now- we'll i&nore the ve*tor's a#ility to dynamically resize itself and instead focus on the functions you can use to access the ve*tor! 0hen you create a ve*tor- you can set its default size as a parameter to the constructor like this=
ve*tor ve*tor ve*tor ve*tor int) m%(ntDe*tor; int) m%(ntDe*tor!7-$; int) m%(ntDe*tor!I-B 7DE$; string) m%5trings!JB "blan+"$; "" "" "" "" ve*tor of siUe 0 ve*tor of siUe 80, elements set to Uero. siUe K0, all elements are 8FG. siUe J, elements set to .blan+.
you are not creatin& a De*tor with 100 elements! 'nstead you're providin& a size hint a#out the e)pected size of the De*tor! Thus- if you're portin& code from CS1067IJ to the ST e5uivalents- make sure to watch out for this discrepancy ( it won't work the way you want it toU
Q There is a third 4simple6 container- the $riorit%BVueue- which we won't cover here!
1 B6 1
'nterestin&ly- if you store primitive types like int or double in an ST ve*tor and specify an initial size for the ve*tor- the ve*tor contents will default to zero! This is not the #ehavior you see in the CS1067IJ ,2Tsso #e aware of the distinction! "ow- of course- a ve*tor can't do much anythin& if you can't access any of its data! 8ortunately- the ST ve*, tor is much like the CS1067IJ De*tor in that you can access its data in two ways- as shown #elow=
ve*tor int) m%(nts(80); m%(nts9-: = 80; "" 5et the first element to 80. m%(nts.at!-$ = 80; "" 5et the first element to 80. int ninthElement = m%(nts9K:; int ninthElement = m%(nts.at!K$;
There is a su#tle difference #etween the two synta)es- #ut for now it's safe to i&nore it! $ust remem#er that in #oth cases- if you &o off the end of the ve*tor- you're likely to &et your pro&ram to crash- so make sure to #ounds1check your accessesU ,s with the sta*+ and Vueue- the ve*tor uses siUe and em$t% to return the size and determine if the ve*, tor is empty- respectively! 8or e)ample=
ve*tor int) m%De*tor(80); for()ector.int>::siLe&type h = 0; h m%De*tor[h] = h; my5ector.siLe!$; !!h)
'n this a#ove code- note that we used the type ve*tor int)33siUeBt%$e for our iteration varia#le instead of the more traditional int! ,ll ST container classes define a special siUeBt%$e type that holds only positive in1 te&ers #ecause containers cannot hold ne&ative num#ers of elements! 'n nearly every case it's completely safe to i&nore the distinction and use ints for trackin& these varia#les- #ut it's &ood to know the distinction since your compiler mi&ht complain a#out 4implicit type conversions with possi#le losses of data!6 'f you want to clear out all of the entries of a ve*tor- use the *lear mem#er function- which #ehaves the same way as the CS1067IJ ,2T *lear mem#er functions=
ve*tor int) m%De*tor(8000); *out m%De*tor.siUe() endl; my5ector.clear!$; *out m%De*tor.siUe() endl; "" <rints 8000 "" <rints 0
HariableCsi3ed )ectors These a#ove functions are indeed useful- #ut they treat the ve*tor as thou&h it were simply a fi)ed1sized array! /owever- the real power of the ve*tor shows up when you resize it! The simplest way to resize a ve*tor is with the resiUe mem#er function! resiUe has the same synta) as the ve*tor constructorR that is- you can specify only a tar&et size- or #oth a tar&et size and a default value for any inserted elements! resiUe can #e used #oth to e)pand and shrink a ve*tor! 'f the ve*tor e)pands- any new elements are appended to the end! 'f it shrinks- they're deleted off the end! K8or the e)amples #elow- assume we have a <rintDe*tor function that takes in a ve*tor int) and prints it to the screen!QL
Q 8or now you can *ust use a simple for loop to do this! &orithms!
ater- we'll see a much cooler way that involves iterators and al1
1 B@ 1
Hf course- sometimes you only want to add one element to the ve*tor! 'f that new element is at the end of the ve*tor- you can use the $ushBba*+ method to 4push6 an element onto the #ack of the ve*tor! This is e5ui1 valent to the CS1067IJ add function! 8or e)ample=
ve*tor int) m%De*tor; for(int i = 0; i 80; !!i) my5ector.push&bac+!i$; <rintDe*tor(m%De*tor); "" =ut$ut3 0 8 H F J K M G I L
Similarly- to take an element off the #ack of a ve*tor- you can use $o$Bba*+- with #ehavior almost identical to that of the sta*+'s $o$ mem#er function=
ve*tor int) m%De*tor(80, 0); 'hile(0m%De*tor.em$t%()) my5ector.pop&bac+!$;
The ve*tor also has a mem#er function ba*+ that returns a reference to the last element in the ve*tor!
$ushBba*+ and $o$Bba*+ are nice- #ut what if you want to insert an element into an ar#itrary point in the ve*tor> This is possi#le- #ut the synta) is a #it 5uirky! Hnce you've finished the chapter on ST iteratorshowever- this code will seem fine! To insert an element into a ve*tor at an ar#itrary point- use this synta)= my5ector.insert!my5ector.begin!$ 3 nB element$;
/ere- n represents the inde) at which you want to insert the element! So- for e)ample- to insert the num#er 10 at the very #e&innin& of an inte&er ve*tor- you'd write
m%De*tor.insert(m%De*tor.begin(), 80);
$ust as you can use resiUe to &row a ve*tor and fill in the newly added elements- you can use insert to in1 sert multiple copies of a sin&le element #y usin& this synta)=
m%De*tor.insert(m%De*tor.begin() ! n, num4o$ies, element);
8or e)ample- to insert five copies of the num#er 1A@ at the start of a ve*tor- you could use the synta)
m%De*tor.insert(m%De*tor.begin(), K, 8FG)!
To remove a sin&le element from a random point in a ve*tor- use the erase method as follows=
my5ector.erase!my5ector.begin!$ 3 n$;
1 BE 1
where n represents the inde) of the element to erase! 'f you want to remove elements in the ran&e Ostart- stopLyou can alternatively use this synta)=Q
m%De*tor.erase(my5ector.begin!$ 3 startB my5ector.begin!$ 3 stop);
The followin& ta#le summarizes most of the important mem#er functions of the ve*tor= Again, we ha!en(t co!ered const yet, so it(s sa"e to ignore it "or now) &e also ha!en(t co!ered iterators yet, so don(t "eel scared when you see iterator "unctions)
Constructor= ve*tor
;) () ve*tor int) m%De*tor;
Constructs a vector of the specified size where all elements use their default values Kfor inte&ral types- this is zeroL! Constructor= ve*tor
;) (siUeBt%$e siUe, *onst ;? default) ve*tor string) m%De*tor(K, .blan+.);
Constructs a vector of the specified size where each element is e5ual to the specified default value!
siUeBt%$e siUe() *onst; for(int i = 0; i m%De*tor.siUe(); !!i) { ... #
Drases all the elements in the vector and sets the size to zero!
;? o$erator [] (siUeBt%$e $osition); *onst ;? o$erator [] (siUeBt%$e $osition) *onst; ;? at(siUeBt%$e $osition); *onst ;? at(siUeBt%$e $osition) *onst; m%De*tor[0] = 800; int 7 = m%De*tor[0]; m%De*tor.at(0) = 800; int 7 = m%De*tor.at(0);
Feturns a reference to the element at the specified position! The #racket notation [] does not do any #ounds checkin& and has undefined #ehavior past the end of the data! The at mem1 #er function will throw an e)ception if you try to access data #eyond the end! 0e will cover e)ception handlin& in a later chapter!
m%De*tor.resiUe(80); m%De*tor.resiUe(80, .default.);
Fesizes the vector so that it's &uaranteed to #e the specified size! 'n the second version- the ve*tor elements are initial1 ized to the value specified #y the second parameter! Dlements are added to and removed from the end of the ve*tor- so you can't use resiUe to add elements to or remove elements from the start of the ve*tor!
Q The notation O,- 7L denotes the interval #etween , and 7 includin& , #ut e)cludin& 7! Since the domains considered in this te)t are usually discrete- the ran&e O,- 7L corresponds to all elements #etween , and 7- includin& , #ut e)cludin& 7! Thus O0- AL contains 0- 1- and ; #ut not A!
1 B< 1
Feturns an iterator that points to the first element in the ve*, tor!
iterator end(); *onstBiterator end() *onst; 'hile(itr 0= m%De*tor.end());
Feturns an iterator to the element a"ter the last! The iterator returned #y end does not point to an element in the ve*tor!
iterator insert(iterator $osition, *onst ;? value); void insert(iterator start, siUeBt%$e num4o$ies, *onst ;? value); m%De*tor.insert(m%De*tor.begin() ! J, ./ello.); m%De*tor.insert(m%De*tor.begin(), H, .Ro0.);
The first version inserts the specified value into the ve*torand the second inserts num4o$ies copies of the value into the ve*tor! 7oth calls invalidate all outstandin& iterators for the ve*tor!
m%De*tor.erase(m%De*tor.begin()); m%De*tor.erase(start(tr, end(tr);
The first version erases the element at the position pointed to #y $osition! The second version erases all elements in the ran&e Ostart(tr- end(trL! "ote that this does not erase the element pointed to #y end(tr! ,ll iterators after the remove point are invalidated! 'f usin& this mem#er function on a deVue Ksee #elowL- all iterators are invalidated!
,s you can see- the ve*tor is very powerful and has a whole #unch of useful functions! Hthers e)ist as well- so #e sure to consult a reference! deGue5 Another seIuence container The ST ve*tor is a linear se5uence that &rows and shrinks dynamically! 0hile it's possi#le to add and remove elements anywhere in a ve*tor- insertions and deletions are fastest at the end of the ve*tor and are costly at the #e&innin&! 'f we want to work with a se5uence of elements that &rows and shrinks at #oth ends- we could do so usin& a ve*tor- #ut would suffer a severe performance hit! 's there some way that we can model a linear se1 5uence with efficient insertion and deletion at #oth ends> ,s mentioned at the start of this chapter- each of the CS1067IJ ,2Ts Ke)cludin& GridL has a correspondin& ST container! /owever- the ST has several containers which have no analo& in the CS1067IJ ,2Ts! Hne such container is the deque! deVue Kpronounced 4deck-6 as in a deck of cards- and not 4de5ueue-6 the 5ueue's
1 ?0 1
remove element operationL stands for 4dou#le1ended Iueue6 and is a se5uence container like ve*tor that sup1 ports efficient insertion and removal of elements at #oth the #e&innin& and the end of its se5uence! 0hat's inter1 estin& a#out the deVue is that all operations supported #y ve*tor are also provided #y deVue! Thus we can resiUe a deVue- use the #racket synta) to access individual elements- and erase elements at ar#itrary positions! /owever- deVues also support two more functions- $ushBfront and $o$Bfront- which work like the ve*, tor's $ushBba*+ and $o$Bba*+ e)cept that they insert and remove elements at the front of the deVue! These four functions K$ushBba*+- $o$Bba*+- $ushBfront- $o$BfrontL allow the deVue to mimic other data struc1 tures we've discussed this chapter! 8or e)ample- a deVue can act like a sta*+ if we use $ushBfront and $o$Bfront to insert and remove elements at the #e&innin& of its se5uence- and similarly could model a Vueue #y callin& $o$Bfront to remove elements and $ushBba*+ to add them! This is not a coincidence ( the sta*+ is actually implemented as a wrapper class that manipulates a deVue-Q as is the Vueue! 7ecause the sta*+ and Vueue 4adapt6 the deVue from one data type to another- they are not technically containers- #ut 4container ad1 apters!6 'f deVue has more functionality than ve*tor- why use ve*tor> The main reason is speed! deVues and ve*, tors are implemented in two different ways! Typically- a ve*tor stores its elements in conti&uous memory ad1 dresses! deVues- on the other hand- maintain a list of different 4pa&es6 that store information! This is shown here=
)ector
deGue
These different implementations impact the efficiency of the ve*tor and deVue operations! 'n a ve*tor- #e1 cause all elements are stored in consecutive locations- it is possi#le to locate elements throu&h simple arithmetic= to look up the nth element of a ve*tor- find the address of the first element in the ve*tor- then *ump forward n positions! 'n a deVue this lookup is more comple)= the deVue has to fi&ure out which pa&e the element will #e stored in- then has to search that pa&e for the proper item! /owever- insertin& elements at the front of a ve*tor re5uires the ve*tor to shuffle all e)istin& elements down to make room for the new element KslowL- while doin& the same in the deVue only re5uires the deVue to rearran&e elements in a sin&le pa&e KfastL! 'f you're de#atin& a#out whether to use a ve*tor or a deVue in a particular application- you mi&ht appreciate this advice from the C++ 'SH Standard Ksection ;A!1!1!;L=
vector is the ty#e o" seAuence that should be used by de"ault))) deque is the data structure o"
choice when most insertions and deletions ta*e #lace at the beginning or at the end o" the seAuence)
Q Hne mi&ht even say that it's stackin& the de5ue! Z
1 ?1 1
'f you ever find yourself a#out to use a ve*tor- check to see what you're doin& with it! 'f you need to optimize for fast access- keep usin& a ve*tor! 'f you're &oin& to #e insertin& or deletin& elements at the #e&innin& or end of the container fre5uently- consider usin& a deVue instead! #ore To $%&lore There's so much to e)plore with the ST that we could easily fill the rest of the course reader with ST content! 'f you're interested in some more advanced topics relatin& to this material and the ST in &eneral- consider read1 in& on these topics= 1! )alarray= The valarra% class is similar to a ve*tor in that it's a mana&ed array that can hold ele1 ments of any type! /owever- unlike ve*tor- valarra% is desi&ned for numerical computations! valarra%s are fi)ed1size and have intrinsic support for mathematical operators! 8or e)ample- you can use the synta) m%DalArra% 2= H to multiply all of the entries in a valarra% #y two! 'f you're inter1 ested in numeric or computational pro&rammin&- consider lookin& into the valarra%! ;! There's an e)cellent article online comparin& the performances of the ve*tor and deVue containers! 'f you're interested- you can see it at http=IIwww!codepro*ect!comIvcppIstlIvectorVvsVde5ue!asp! -ractice -roblems Dverythin& we've covered this chapter primarily concerns the #asic synta) of ST containers and thus doesn't lend itself to 4practice pro#lems6 in the traditional sense! /owever- ' hi&hly recommend that you try some of the followin& e)ercises= 1! Take some of the code you've seen from CS1067IJ that uses the ,2T li#rary and rewrite it usin& the ST ! 0hat are the ma*or differences #etween the two li#raries> 's one easier to work with than anoth1 er> ;! 2eli#erately write code with the ST that contains synta) errors and look at the error messa&es you &et! See if you have any idea what they say- and- if not- practice until you can &et a feel for what's &oin& on! 'f you want a real mess- try makin& mistakes with ve*tor string)! A! ,s mentioned a#ove- the deVue outperforms the ve*tor when insertin& and removin& elements at the end of the container! /owever- the ve*tor has a useful mem#er function called reserve that can #e used to increase its performance a&ainst the deVue in certain circumstances! The reserve function ac1 cepts an inte&er as a parameter and acts as a sort of 4size hint6 to the ve*tor! 7ehind the scenes- re, serve works #y allocatin& additional stora&e space for the ve*tor elements- reducin& the num#er of times that the ve*tor has to ask for more stora&e space! Hnce you have called reserve- as lon& as the size of the ve*tor is less than the num#er of elements you have reserved- calls to $ushBba*+ and in, sert on the ve*tor will e)ecute more 5uickly than normal! Hnce the ve*tor hits the size you re1 served- these operations revert to their ori&inal speed!Q 0rite a pro&ram that uses $ushBba*+ to insert a lar&e num#er of strings into two different ve*, tors ( one which has had reserve called on it and one which hasn't ( as well as a deVue! The e)act num#er and content of strin&s is up to you- #ut lar&e num#ers of lon& strin&s will &ive the most impress1 ive results! Use the *lo*+() function e)ported #y *time) to compute how lon& it takes to finish in1 sertin& the stringsR consult a reference for more information! "ow repeat this trial- #ut insert the ele1 ments at the #e&innin& of the container rather than the end! 2id callin& reserve help to make the ve*, tor more competitive a&ainst the deVue> B! Compare the performances of the ve*tor's #racket operator synta) Ke!&! m%De*tor[0]L to the ve*, tor's at function Ke!&! m%De*tor.at(0)L! 3iven that the at function is #ounds1checked- how serious are the runtime penalties of #ounds1checkin&>
Q Callin& $ushBba*+ n times always takes HKnL time- whether or not you call reserve! /owever- callin& reserve reduces the constant term in the #i&1H to a smaller value- meanin& that the overall e)ecution time is lower!
1 ?; 1
?! 'n this ne)t pro#lem we'll e)plore a simple encryption al&orithm called the BigenCre ci#her and how to implement it usin& the ST containers! Hne of the oldest known ciphers is the Caesar ci#her- named for $ulius Caesar- who alle&edly employed it! The idea is simple! 0e pick a secret num#er #etween 1 and ;6- inclusive- then encrypt the input strin& #y replacin& each letter with the letter that comes that many spaces after it! 'f this pushes us off the end of the alpha#et- we wrap around to the start of the alpha#et! 8or e)ample- if we were &iven the strin& 4The cookies are in the frid&e6 and picked the num#er 1- we would end up with the resultin& strin& 4Uif dppl*ft #sf *o uif &s*ehf!6 To decrypt the strin&- we simply need to push each letter #ack1 wards #y one spot! The Caesar cipher is an e)tremely weak form of encryptionR it was #roken in the ninth century #y the ,ra# polymath al1Kindi! The pro#lem is that the cipher preserves the relative fre5uencies of each of the letters in the source te)t! "ot all letters appear in Dn&lish with e5ual fre5uency ( e and t are far more common than 5 or w- for e)ample ( and #y lookin& at the relative letter fre5uencies in the encrypted te)t it is possi#le to determine which letter in the encrypted te)t corresponds to a letter in the source te)t and to recover the key! The pro#lem with the Caesar cipher is that it preserves letter fre5uencies #ecause each letter is trans1 formed usin& the same key! 7ut what if we were to use multi#le keys while encryptin& the messa&e> That is- we mi&ht encrypt the first letter with one key- the second with another- the third with yet anotheretc! Hne way of doin& this is to pick a se5uence of num#ers- then cycle throu&h them while encryptin& the te)t! 8or e)ample- let's suppose that we want to encrypt the a#ove messa&e usin& the key strin& 1- A@! Then we would do the followin&=
; 8 U / F M E G 1 4 8 D = F C = G 5 X 8 1 ( F 1 E G 1 5 8 T A F D C G N E 8 F ( F 1 E G U ; 8 U / F M E G 1 & 8 F C F U ( G P 9 8 2 G F O E G 1
"otice that the letters K'D from CHHK'DS are all mapped to the letter - makin& cryptanalysis much more difficult! This particular encryption system is the Ti&en\re cipher! "ow- let's consider what would happen if we wanted to implement this al&orithm in C++ to work on ar1 #itrary strin&s! Strin&s in C++ are composed of individual *hars- which can take on KtypicallyL one of ;?6 different values! 'f we had a list of inte&er keys- we could encrypt a strin& usin& the Ti&en\re cipher #y simply cyclin& throu&h those keys and incrementin& the appropriate letters of the strin&! 'n fact- the al&orithm is 5uite simple! 0e iterate over the characters of the strin&- at each point incrementin& the character #y the current key and then rotatin& the keys one cycle! a! Suppose that we want to represent a list of inte&er keys that can easily #e cycledR that is- we want to efficiently support movin& the first element of the list to the #ack! Hf the containers and container adapters covered in the a#ove section Ksta*+- Vueue- ve*tor- deVueL- which have the #est sup1 port for this operation> M #! 7ased on your decision- implement a function DigenereEn*r%$t that accepts a string and a list of int keys stored in the container Kor container adapterL of your choice- then encrypts the string usin& the Ti&en\re cipher! M
8ew computer &ames can #oast the lon&evity or addictive power of ,na*e! Fe&ardless of your #ack&roundchances are that you have played Snake or one of its many variants! The rules are simple ( you control a snake on a two1dimensional &rid and try to eat food pellets scattered around the &rid! .ou lose if you crash into the walls! True to "ewton's laws- the snake continues movin& in a sin&le direction until you e)plicitly chan&e its #earin& #y ninety de&rees! Dvery time the snake eats food- a new piece of food is randomly placed on the &rid and the snake's len&th increases! Hver time- the snake's #ody &rows so lon& that it #ecomes an o#stacle- and if the snake collides with itself the player loses! /ere's a screenshot from N7asic "i##les- a Gicrosoft implementation of Snake released with GS12HS version ?!0! The snake is the lon& #lack strin& at the #ottom of the map- and the num#er E is the food=
7ecause the rules of Snake are so simple- it's possi#le to implement the entire &ame in only a few hundred lines of C++ code! 'n this e)tended e)ample- we'll write a Snake pro&ram in which the com#uter controls the snake accordin& to a simple ,'! 'n the process- you'll &ain e)perience with the ST ve*tor and deVue- the streams li#rary- and a sprinklin& of C li#rary functions! Hnce we've finished- you'll have a rather snazzy pro&ram that can serve as a launchin& point for further C++ e)ploration! 2ur Hersion of Snake There are many variants of Snake- so to avoid confusion we'll e)plicitly spell out the rules of the &ame we're im1 plementin&=
1 ?B 1 1! ;! A! B! ?!
Cha#ter 3% E7tended E7am#le% ,na*e The snake moves #y e)tendin& its head in the direction it's movin& and pullin& its tail in one space! The snake wins if it eats twenty pieces of food! The snake loses if it crashes into itself or into a wall! 'f the snake eats a piece of food- its len&th &rows #y one and a new piece of food is randomly placed! There is only one level- the startin& level!
0hile traditionally Snake is played #y a human- our Snake will #e computer1controlled so that we can e)plore some important pieces of the C runtime li#rary! 0e'll discuss the ,' we'll use when we #e&in implementin& it! 1e&resenting the /orld 'n order to represent the Snake world- we need to keep track of the followin& information= 1! The size and layout of the world! ;! The location of the snake! A! /ow many pieces of food we've consumed! et's consider this information one piece at a time! 8irst- how should we represent the world> Since the world is two1dimensional- we will need to store this information in somethin& akin to the CS1067IJ Grid! Unfortu1 nately- the ST doesn't have a container class that encapsulates a multidimensional array- #ut we can emulate this functionality with an ST ve*tor of ve*tors! 8or e)ample- if we represent each s5uare with an o#*ect of type 1orld;ile- we could use a ve*tor ve*tor 1orld;ile) )! "ote that there is a space #etween the two closin& an&le #rackets ( this is deli#erate and is an unfortunate #u& in the C++ specification! 'f we omit the space- C++ would interpret the closin& #races on ve*tor ve*tor 1orld;ile)) as the stream e)traction op1 erator ))- as in *in )) m%Dalue! ,lthou&h most compilers will accept code that uses two ad*acent closin& #races- it's #ad practice to write it this way! 0hile we could use a ve*tor ve*tor 1orld;ile) )- there's actually a simpler option! Since we need to #e a#le to display the world to the user- we can instead store the world as a ve*tor string) where each string encodes one row of the #oard! This also simplifies displayin& the worldR &iven a ve*tor string) represent1 in& all the world information- we can draw the #oard #y outputtin& each strin& on its own line! Goreover- since we can use the #racket operator [] on #oth ve*tor and string- we can use the familiar synta) 'orld[ro'][*ol] to select individual locations! The first #rackets select the string out of the ve*tor and the second the character out of the string! 0e'll use the followin& characters to encode &ame information=
, space character KA AL represents an empty tile! , pound si&n KA-AL represents a wall! , dollar si&n KAYAL represents food! ,n asterisk KA2AL represents a tile occupied #y a snake!
8or simplicity- we'll #undle all the &ame data into a sin&le stru*t called game;! This will allow us to pass all the &ame information to functions as a sin&le parameter! 7ased on the a#ove information- we can #e&in writin& this stru*t as follows=
stru*t game; { )ector.string> 6orld; "2 ... 2" #;
1 ?? 1
0e also will need 5uick access to the dimensions of the playin& field- since we will need to #e a#le to check whether the snake is out of #ounds! 0hile we could access this information #y checkin& the dimensions of the ve*tor and the strin&s stored in it- for simplicity we'll store this information e)plicitly in the game; struct- as shown here=
stru*t game; { ve*tor string) 'orld; int numCo6sB num;ols; "2 ... 2" #;
8or consistency- we'll access elements in the ve*tor string) treatin& the first inde) as the row and the second as the column! Thus 'orld[F][K] is row three- column five Kwhere indices are zero1inde)edL! "ow- we need to settle on a representation for the snake! The snake lives on a two1dimensional &rid and moves at a certain velocity! 7ecause the &rid is discrete- we can represent the snake as a collection of its points alon& with its velocity vector! 8or e)ample- we can represent the followin& snake=
0 0
"
! "
,s the points K;- 0L- K;- 1L- K;- ;L- KA- ;L- KB- ;L- KB- AL and the velocity vector K11- 0L! The points comprisin& the snake #ody are ordered to determine how the snake moves! 0hen the snake movesthe first point Kthe headL moves one step in the direction of the velocity vector! The second piece then moves into the &ap left #y the first- the third moves into the &ap left #y the second piece- etc! This leaves a &ap where the tail used to #e! 8or e)ample- after movin& one step- the a#ove snake looks like this=
0 0
"
! "
1 ?6 1
To represent the snake in memory- we thus need to keep track of its velocity and an ordered list of the points comprisin& it! The former can #e represented usin& two ints- one for the ]) component and one for the ]y component! 7ut how should we represent the latter> 'n the previous chapter we learned a#out the sta*+Vueue- ve*tor- and deVue- each of which could represent the snake! To see what the #est option is- let's think a#out how we mi&ht implement snake motion! 0e can think of snake motion in one of two ways ( first- as the head movin& forward a step and the rest of the points shiftin& down one spot- and second as the snake &ettin& a new point in front of its current head and losin& its tail! The first approach re5uires us to update every element in the #ody and is not particularly efficient! The second approach can easily #e implemented with a deVue throu&h an appropriate com#ination of $ushBfront and $o$Bba*+! 0e will thus use a deVue to encode the snake #ody! 'f we want to have a deVue of points- we'll first need some way of encodin& a point! This can #e done with this struct=
stru*t $oint; { int ro', *ol; #;
Takin& these new considerations into account- our new game; struct looks like this=
stru*t game; { ve*tor string) 'orld; int numCo's, num4ols; deGue.pointT> sna+e; int d#B dy; "2 ... 2" #;
8inally- we need to keep track of how many pieces of food we've munched so far! That can easily #e stored in an int- yieldin& this final version of game;=
stru*t game; { ve*tor string) 'orld; int numCo's, num4ols; deVue $oint;) sna+e; int d7, d%; int num2aten; #;
The Skeleton Im&lementation "ow that we've settled on a representation for our &ame- we can start thinkin& a#out how to or&anize the pro1 &ram! There are two lo&ical steps ( setup and &ameplay ( leadin& to the followin& skeleton implementation=
1 ?@ 1
"2 ;he main $rogram. (nitialiUes the 'orld, then runs the simulation. 2" int main!$ * gameT game; InitialiLeFame!game$; CunSimulation!game$; return -; 4
,top this pro&ram are the necessary -in*ludes for the functions and o#*ects we're usin&- followed #y a list of constants for the &ame! The $oint; and game; structs are identical to those descri#ed a#ove! main creates a game; o#*ect- passes it into (nitialiUeGame for initialization- and finally hands it to Cun5imulation to play the &ame! 0e'll #e&in #y writin& (nitialiUeGame so that we can &et a valid game; for Cun5imulation! 7ut how should we initialize the &ame #oard> Should we use the same #oard every time- or let the user specify a level of their choosin&> 7oth of these are resaona#le- #ut for the this e)tended e)ample we'll choose the latter! 'n particular- we'll specify a level file format- then let the user specify which file to load at runtime! There are many possi#le file formats to choose from- #ut each must contain at least enou&h information to populate a game; structR that is- we need the world dimensions and layout- the startin& position of the snake- and the direction of the snake! 0hile ' encoura&e you to e)periment with different structures- we'll use a simple file format that encodes the world as a list of strin&s and the rest of the data as inte&ers in a particular order! /ere is one possi#le file=
1 ?E 1 le!el)t7t
8K 8K 8 0 ---------------Y Y- Y 2 - Y -Y Y---------------
The first two num#ers encode the num#er of rows and columns in the file- respectively! The ne)t line contains the initial snake velocity as ])- ]y! The remainin& lines encode the &ame #oard- usin& the same characters we settled on for the 'orld ve*tor! 0e'll assume that the snake is initially of len&th one and its position is &iven #y a 2 character! There are two steps necessary to let the user choose the level layout! 8irst- we need to prompt the user for the name of the file to open- repromptin& until she chooses an actual file! Second- we need to parse the contents of the file into a game; struct! 'n this e)ample we won't check that the file is formatted correctly- thou&h in professional code we would certainly need to check this! 'f you'd like some additional practice with the streams li#rary- this would #e an e)cellent e)ercise! et's start writin& the function responsi#le for loadin& the file from disk- (nitialiUeGame! Since we need to prompt the user for a filename until she enters a valid file- we'll #e&in writin&=
void (nitialiUeGame(game;? game) { i stream input; 6hile!true$ * cout .. "2nter ilename: "; string ilename , Fet1ine!$; "2 ... 2" 4 "2 ... 2" #
The 'hile(true) loop will continuously prompt the user until she enters a valid file! /ere- we assume that Get6ine() is the version defined in the chapter on streams! ,lso- since we're now usin& the ifstream typewe'll need to -in*lude fstream) at the top of our pro&ram! "ow that the user has &iven us the a filename- we'll try openin& it usin& the .o$en() mem#er function! 'f the file opens successfully- we'll #reak out of the loop and start readin& level data=
1 ?< 1
input.open! ilename.c&str!$$; "" 5ee the *ha$ter on streams for .*Bstr(). i !input.is&open!$$ brea+; "2 ... 2" # "2 ... 2" #
'f the file did not open- however- we need to report this to the user! ,dditionally- we have to make sure to reset the stream's error state- since openin& a none)istent file causes the stream to fail! Code for this is shown here=
void (nitialiUeGame(game;? game) { ifstream in$ut; 'hile(true) { *out .Enter filename3 .; string filename = Get6ine(); in$ut.o$en(filename.*Bstr()); "" 5ee the *ha$ter on streams for .*Bstr(). if(in$ut.isBo$en()) brea+; cout .. "SorryB I can@t input.clear!$; # "2 ... 2" # ind the ile " .. ilename .. endl;
"ow we need to parse the file data into a game; struct! Since this is rather involved- we'll decompose it into a helper function called 6oad1orld- then finish (nitialiUeGame as follows=
void (nitialiUeGame(game;? game) { ifstream in$ut; 'hile(true) { *out .Enter filename3 .; string filename = Get6ine(); in$ut.o$en(filename.*Bstr()); "" 5ee the *ha$ter on streams for .*Bstr(). if(in$ut.isBo$en()) brea+; *out .5orr%, ( *anAt find the file . in$ut.*lear(); # 1oad=orld!gameB input$; # filename endl;
"otice that e)cept for the call to 6oad1orld- nothin& in the code for (nitialiUeGame actually pertains to our Snake &ame! 'n fact- the code we've written is a &eneric routine for openin& a file specified #y the user! 0e'll
1 60 1
thus #reak this function down into two functions ( =$en@ser&ile- which prompts the user for a filename- and (nitialiUeGame- which opens the specified file- then hands it off to 6oad1orld! This is shown here=
)oid <penUserFile!i streamS input$ * 6hile!true$ * cout .. "2nter ilename: "; string ilename , Fet1ine!$; input.open! ilename.c&str!$$; TT See the chapter on streams i !input.is&open!$$ return; cout .. "SorryB I can@t input.clear!$; ind the ile " .. ilename .. endl; or .c&str!$.
4 4
et's #e&in workin& on 6oad1orld! The first line of our file format encodes the num#er of rows and columns in the world- and we can read this data directly into the game; struct- as seen here=
)oid 1oad=orld!gameTS gameB i streamS input$ * input >> game.numCo6s >> game.num;ols; game.6orld.resiLe!game.numCo6s$; "2 ... 2" 4
0e've also resized the ve*tor to hold game.numCo's strings- &uaranteein& that we have enou&h strin&s to store the entire world! This simplifies the implementation- as you'll see momentarily! "e)t- we'll read the startin& velocity for the snake- as shown here=
void 6oad1orld(game;? game, ifstream? in$ut) { in$ut )) game.numCo's )) game.num4ols; game.'orld.resiUe(game.numCo's); input >> game.d# >> game.dy; # "2 ... 2"
,t this point- we've read in the parameters of the world- and need to start readin& in the actual world data! Since each line of the file contains one row of the &rid- we'll use getline for the remainin& read operations! There's a catch- however! Fecall that getline does not mi) well with the stream e)traction operator K ))L- which we've used e)clusively so far! 'n particular- the first call to getline after usin& the stream e)traction operator will return the empty strin& #ecause the newline character delimitin& the data is still waitin& to #e read! To prevent
1 61 1
this from &ummin& up the rest of our input operations- we'll call getline here on a dummy strin& to flush out the remainin& newline=
void 6oad1orld(game;? game, ifstream? in$ut) { in$ut )) game.numCo's )) game.num4ols; game.'orld.resiUe(game.numCo's); in$ut )) game.d7 )) game.d%; string dummy; getline!inputB dummy$; "2 ... 2" #
"ow we're ready to start readin& in world data! 0e'll read in game.numCo's lines from the file directly into the game.'orld ve*tor! Since earlier we resiUed the ve*tor- there already are enou&h strings to hold all the data we'll read! The readin& code is shown #elow=
void 6oad1orld(game;? game, ifstream? in$ut) { in$ut )) game.numCo's )) game.num4ols; game.'orld.resiUe(game.numCo's); in$ut )) game.d7 )) game.d%; string dumm%; getline(in$ut, dumm%); * 4 # "2 ... 2" or!int ro6 , -; ro6 . game.numCo6s; 33ro6$ getline!inputB game.6orld9ro6:$; "2 ... 2"
Fecall that somewhere in the level file is a sin&le 2 character indicatin& where the snake #e&ins! To make sure that we set up the snake correctly- after readin& in a line of the world data we'll check to see if it contains a star and- if so- we'll populate the game.sna+e deVue appropriately! Usin& the .find() mem#er function on the string simplifies this task- as shown here=
1 6; 1
void 6oad1orld(game;? game, ifstream? in$ut) { in$ut )) game.numCo's )) game.num4ols; game.'orld.resiUe(game.numCo's); in$ut )) game.d7 )) game.d%; string dumm%; getline(in$ut, dumm%); for(int ro' = 0; ro' game.numCo's; !!ro') { getline(in$ut, game.'orld[ro']); int col , game.6orld9ro6:. ind!+Sna+eTile$; i !col %, string::npos$ * pointT head; head.ro6 , ro6; head.col , col; game.sna+e.push&bac+!head$; 4 # "2 ... 2" #
The synta) for creatin& and fillin& in the $oint; data is a #it #ulky here! 0hen we cover classes in the second half of this course you'll see a much #etter way of creatin& this $oint;! 'n the meantime- we can write a helper function to clean this code up- as shown here=
pointT 0a+ePoint!int ro6B int col$ * pointT result; result.ro6 , ro6; result.col , col; return result; 4 void 6oad1orld(game;? game, ifstream? in$ut) { in$ut )) game.numCo's )) game.num4ols; game.'orld.resiUe(game.numCo's); in$ut )) game.d7 )) game.d%; string dumm%; getline(in$ut, dumm%); for(int ro' = 0; ro' game.numCo's; !!ro') { getline(in$ut, game.'orld[ro']); int *ol = game.'orld[ro'].find(+5na+e;ile); if(*ol 0= string33n$os) game.sna+e.push&bac+!0a+ePoint!ro6B col$$; # # "2 ... 2"
1 6A 1
There's one last step to take care of- and that's to ensure that we set the numEaten field to zero! This edit completes 6oad1orld and the final version of the code is shown here=
void 6oad1orld(game;? game, ifstream? in$ut) { in$ut )) game.numCo's )) game.num4ols; game.'orld.resiUe(game.numCo's); in$ut )) game.d7 )) game.d%; string dumm%; getline(in$ut, dumm%); for(int ro' = 0; ro' game.numCo's; !!ro') { getline(in$ut, game.'orld[ro']); int *ol = game.'orld[ro'].find(+5na+e;ile); if(*ol 0= string33n$os) game.sna+e.$ushBba*+(:a+e<oint(ro', *ol)); # game.num2aten , -; #
3reatU 0e've *ust finished setup and it's now time to code up the actual &ame! 0e'll #e&in #y codin& a skeleton of Cun5imulation which displays the current state of the &ame- runs the ,'- and moves the snake=
void Cun5imulation(game;? game) { "2 Xee$ loo$ing 'hile 'e havenAt eaten too mu*h. 2" 6hile!game.num2aten . +0a#Food$ * Print=orld!game$; "" 9is$la% the board Per ormUI!game$; "" /ave the A( *hoose an a*tion i !%0o)eSna+e!game$$ "" :ove the sna+e and sto$ if 'e *rashed. brea+; Pause!$; 4 DisplayCesult!game$; "" <ause so 'e *an see 'hatAs going on. "" ;ell the user 'hat ha$$ened
0e'll implement the functions referenced here out of order- startin& with the simplest and movin& to the most difficult! 8irst- we'll #e&in #y writin& <ause- which stops for a short period of time to make the &ame seem more fluid! The particular implementation of <ause we'll use is a busy loo#- a 'hile loop that does nothin& until enou&h time has elapsed! 7usy loops are frowned upon in professional code #ecause they waste CCU power- #ut for our purposes are perfectly accepta#le! The *time) header e)ports a function called *lo*+() that returns the num#er of 4clock ticks6 that have elapsed since the pro&ram #e&an! The duration of a clock tick varies from system to system- so C++ provides the constant 46=4X5B<ECB5E4 to convert clock ticks to seconds! 0e can use *lo*+ to implement a #usy loop as follows= 1! Call *lo*+() to &et the current time in clock ticks and store the result! ;! Continuously call *lo*+() and compare the result a&ainst the cached value! 'f enou&h time has passedstop loopin&!
const double +=aitTime , -.7; "" <ause 0.8 se*onds bet'een frames void <ause() { cloc+&t startTime , cloc+!$; "" *lo*+Bt is a t%$e designed to hold *lo*+ ti*+s. "2 ;his loo$ does nothing e7*e$t loo$ and *he*+ ho' mu*h time is left. Eote 2 that 'e have to t%$e*ast start;ime from *lo*+Bt to double so that the 2 division is *orre*t. ;he static_cast<double>(...) s%nta7 is the $referred 2 4!! 'a% of $erforming a t%$e*ast of this sort; see the *ha$ter on 2 inheritan*e for more information. 2" 6hile!static&cast.double>!cloc+!$ - startTime$ T ;1<;MS&P2C&S2; . +=aitTime$; #
"e)t- let's implement the <rint1orld function- which displays the current state of the world! 0e chose to represent the world as a ve*tor string) to simplify this code- and as you can see this desi&n decision pays off well=
void <rint1orld(game;? game) { or!int ro6 , -; ro6 . game.numCo6s; 33ro6$ cout .. game.6orld9ro6: .. endl; cout .. "Food eaten: " .. game.num2aten .. endl; #
This implementation of <rint1orld is fine- #ut every time it e)ecutes it adds more te)t to the console instead of clearin& what's already there! This makes it tricky to see what's happenin&! Unfortunately- standard C++ does not e)port a set of routines for manipulatin& the console! /owever- every ma*or operatin& system e)ports its own console manipulation routines- primarily for developers workin& on a command line! 8or e)ample- on a inu) system- typin& *lear into the console will clear its contents- while on 0indows the command is 465! C++ a#sor#ed C's standard li#rary- includin& the s%stem function Kheader file *stdlib)L! s%stem e)ecutes an operatin& system1specific instruction as if you had typed it into your system's command line! This function can #e very dan&erous if used incorrectly-Q #ut also &reatly e)pands the power of C++! 0e will not cover how to use s%stem in detail since it is platform1specific- #ut one particular application of s%stem is to call the appropriate operatin& system function to clear the console! 0e can thus up&rade our implementation of <rint1orld as follows=
"2 ;he string used to *lear the dis$la% before $rinting the game board. 1indo's 2 s%stems should use .465.; :a* =5 P or 6inu7 users should use .*lear. instead. 2" const string +;lear;ommand , ";1S"; void <rint1orld(game;? game) { system!+;lear;ommand.c&str!$$; for(int ro' = 0; ro' game.numCo's; !!ro') *out game.'orld[ro'] endl; *out .&ood eaten3 . game.numEaten endl; #
Q 'n particular- callin& s%stem without checkin& that the parameters have #een sanitized can let malicious users completely compromise your system! Take CS1?? for more information on what sorts of attacks are possi#le!
1 6? 1
7ecause s%stem is from the days of pure C- we have to use .*Bstr() to convert the string parameter into a C1style strin& #efore we can pass it into the function! The final 5uick function we'll write is 9is$la%Cesult- which is called after the &ame has ended to report whether the computer won or lost! This function is shown here=
void 9is$la%Cesult(game;? game) { Print=orld!game$; i !game.num2aten ,, +0a#Food$ cout .. "The sna+e ate enough ood and 6ins%" .. endl; else cout .. "<h no% The sna+e crashed%" .. endl; #
"ow- on to the two tricky functions ( <erformA(- which determines the snake's ne)t move- and :ove5na+ewhich moves the snake and processes collisions! 0e'll #e&in with <erformA(! 2esi&nin& an ,' that plays Snake intelli&ently is far #eyond the scope of this class! /owever- it is feasi#le to #uild a rudimentary ,' that plays reasona#ly well! Hur particular ,' works as follows= if the snake is a#out to collide with an o#*ect- the ,' will turn the snake out of dan&er! Htherwise- the snake will continue on its current path- #ut has a percent chance to randomly chan&e direction! et's #e&in #y writin& the code to check whether the snake will turnR that is- whether we're a#out to hit a wall or if the snake randomly decides to veer in a direction! 0e'll write a skeletal implementation of this code- then will implement the re5uisite functions! Hur initial code is
const double +TurnCate , -.8; "" H0Z *han*e to turn ea*h ste$. )oid Per ormUI!gameTS game$ * "2 &igure out 'here 'e 'ill be after 'e move this turn. 2" pointT ne#t'ead , Fet/e#tPosition!game$; "2 (f that $uts us into a 'all or 'e randoml% de*ide to, turn the sna+e. 2" i !;rashed!ne#t'eadB game$ >> Candom;hance!+TurnCate$$ * "2 ... 2" 4 4
/ere we're callin& three functions we haven't written yet ( GetEe7t<osition- which computes the position of the head on the ne)t iterationR 4rashed- which returns whether the snake would crash if its head was in the &iven positionR and Candom4han*eR which returns true with pro#a#ility e5ual to the parameter! 7efore implementin& the rest of <erformA(- let's knock these functions out so we can focus on the rest of the task at hand! 0e #e&in #y implementin& GetEe7t<osition! This function accepts as input the &ame state and returns the point that we will occupy on the ne)t frame if we continue movin& in our current direction! This function isn't particularly comple) and is shown here=
1 66 1
$oint; GetEe7t<osition(game;? game) { "2 Get the head $osition. 2" pointT result , game.sna+e. ront!$;
"2 (n*rement the head $osition b% the *urrent dire*tion. 2" result.ro6 3, game.dy; result.col 3, game.d#; return result;
The implementation of 4rashed is similarly strai&htforward! The snake has crashed if it has &one out of #ounds or if its head is on top of a wall or another part of the snake=
bool 4rashed($oint; head<os, game;? game) { return %In=orld!headPosB game$ >> game.6orld9headPos.ro6:9headPos.col: ,, +Sna+eTile >> game.6orld9headPos.ro6:9headPos.col: ,, +=allTile; #
"e)t- we need to implement Candom4han*e! 'n CS1067IJ we provide you a header file- random.h- that e)ports this function! /owever- random.h is not a standard C++ header file and thus we will not use it here! 'nstead- we will use C++'s rand and srand functions- also e)ported #y *stdlib)- to implement Candom4han*e! rand() returns a pseudorandom num#er in the ran&e O0- CAE9B:APP- where CAE9B:AP is usually ;1? ( 1! srand seeds the random num#er &enerator with a value that determines which values are returned #y rand! Hne common techni5ue is to use the time function- which returns the current system time- as the seed for srand since different runs of the pro&ram will yield different random seeds! Traditionally- you will only call srand once per pro&ram- prefera#ly durin& initialization! 0e'll thus modify (nitialiUeGame so that it calls srand in addition to its other functionality=
void (nitialiUeGame(game;? game) { TR Seed the randomiLer. The static&cast con)erts the result o time!/U11$ rom R time&t to the unsigned int reGuired by srand. This line is idiomatic ;33. RT srand!static&cast.unsigned int>!time!/U11$$$; ifstream in$ut; =$en@ser&ile(in$ut); 6oad1orld(game, in$ut);
"ow- let's implement Candom4han*e! To write this function- we'll call rand to o#tain a value in the ran&e O0- CAE9B:APP- then divide it #y CAE9B:AP ! 8.0 to &et a value in the ran&e O0- 1L! 0e can then return whether this value is less than the input pro#a#ility! This yields true with the specified pro#a#ilityR try
1 6@ 1
convincin& yourself that this works if it doesn't immediately seem o#vious! This is a common techni5ue and in fact is how the CS1067IJ Candom4han*e function is implemented!
Candom4han*e is shown here= bool Candom4han*e(double $robabilit%) { return !rand!$ T !CU/D&0UV 3 7.-$$ . probability; #
The stati*B*ast is necessary here to convert the return value of rand() from an int to a double! 8or&ettin& to do so will cause roundin& errors that make the function most certainly nonrandom! ChewU ,polo&ies for the len&thy detour ( let's &et #ack to writin& the ,'U Fecall that we've written this code so far=
void <erformA((game;? game) { "2 &igure out 'here 'e 'ill be after 'e move this turn. 2" $oint; ne7t/ead = GetEe7t<osition(game); "2 (f that $uts us into a 'all or 'e randoml% de*ide to, turn the sna+e. 2" if(4rashed(ne7t/ead, game) NN Candom4han*e(+;urnCate)) { "2 ... 2" # #
0e now need to implement the lo&ic for turnin& the snake left or ri&ht! 8irst- we'll fi&ure out in what positions the snake's head would #e if we turned left or ri&ht! Then- #ased on which of these positions are safe- we'll pick a direction to turn! To avoid code duplication- we'll modify our implementation of GetEe7t<osition so that the caller can specify the direction of motion- rather than relyin& on the game;'s stored direction! The modified version of GetEe7t<osition is shown here=
$oint; GetEe7t<osition(game;? game, int d#B int dy) { "2 Get the head $osition. 2" game; result = game.sna+e.front(); "2 (n*rement the head $osition b% the s$e*ified dire*tion. 2" result.ro' != dy; result.*ol != d#; return result;
0e'll need to modify <erformA( to pass in the proper parameters to GetEe7t<osition- as shown here=
1 6E 1
void <erformA((game;? game) { "2 &igure out 'here 'e 'ill be after 'e move this turn. 2" $oint; ne7t/ead = GetEe7t<osition(game, game.d#B game.dy); "2 (f that $uts us into a 'all or 'e randoml% de*ide to, turn the sna+e. 2" if(4rashed(ne7t/ead, game) NN Candom4han*e(+;urnCate)) { "2 ... 2" # #
"ow- let's write the rest of this code! 3iven that the snake's velocity is K game.d7- game.d%L- what velocities would we move at if we were headin& ninety de&rees to the left or ri&ht> Usin& some #asic linear al&e#ra-Q if our current headin& is alon& d) and dy- then the headin&s after turnin& left and ri&ht from our current headin& are #e &iven #y d)left Y 1dy dyleft Y d) d)ri&ht Y dy dyri&ht Y 1d) Usin& these e5ualities- we can write the followin& code- which determines what #earin&s are availa#le and whether it's safe to turn left or ri&ht=
void <erformA((game;? game) { "2 &igure out 'here 'e 'ill be after 'e move this turn. 2" $oint; ne7t/ead = GetEe7t<osition(game, game.d7, game.d%); "2 (f that $uts us into a 'all or 'e randoml% de*ide to, turn the sna+e. 2" if(4rashed(ne7t/ead, game) NN Candom4han*e(+;urnCate)) { int le tD# , -game.dy; int le tDy , game.d#; int rightD# , game.dy; int rightDy , -game.d#; "2 4he*+ if turning left or right 'ill *ause us to *rash. 2" bool can1e t , %;rashed!Fet/e#tPosition!gameB le tD#B le tDy$B game$; bool canCight , %;rashed!Fet/e#tPosition!gameB rightD#B rightDy$B game$; # # "2 ... 2"
"ow- we'll decide which direction to turn! 'f we can only turn one direction- we will choose that direction! 'f we can't turn at all- we will do nothin&! 8inally- if we can turn either direction- we'll pick a direction randomly! 0e will store which direction to turn in a #oolean varia#le called 'ill;urn6eft which is true if we will turn left and false if we will turn ri&ht! This is shown here=
Q This is the result of multiplyin& the vector Kd)- dyLT #y a rotation matri) for either +^I; or 1^I; radians!
1 6< 1
"2 4he*+ if turning left or right 'ill *ause us to *rash. 2" bool *an6eft = 04rashed(GetEe7t<osition(game, left97, left9%), game); bool *anCight = 04rashed(GetEe7t<osition(game, right97, right9%), game); bool 6illTurn1e t , alse; i !%can1e t SS %canCight$ return; TT I 6e can@t turnB don@t turn. else i !can1e t SS %canCight$ 6illTurn1e t , true; TT I 6e must turn le tB do so. else i !%can1e t SS canCight$ 6illTurn1e t , alse; TT I 6e must turn rightB do so. else 6illTurn1e t , Candom;hance!-.I$; TT 2lse pic+ randomly # # "2 ... 2"
1 @0 1
void <erformA((game;? game) { "2 &igure out 'here 'e 'ill be after 'e move this turn. 2" $oint; ne7t/ead = GetEe7t<osition(game, game.d7, game.d%); "2 (f that $uts us into a 'all or 'e randoml% de*ide to, turn the sna+e. 2" if(4rashed(ne7t/ead, game) NN Candom4han*e(+;urnCate)) { int left97 = ,game.d%; int left9% = game.d7; int right97 = game.d%; int right9% = ,game.d7; "2 4he*+ if turning left or right 'ill *ause us to *rash. 2" bool *an6eft = 04rashed(GetEe7t<osition(game, left97, left9%), game); bool *anCight = 04rashed(GetEe7t<osition(game, right97, right9%), game); bool 'ill;urn6eft = false; if(0*an6eft ?? 0*anCight) return; "" (f 'e *anAt turn, donAt turn. else if(*an6eft ?? 0*anCight) 'ill;urn6eft = true; "" (f 'e must turn left, do so. else if(0*an6eft ?? *anCight) 'ill;urn6eft = false; "" (f 'e must turn right, do so. else 'ill;urn6eft = Candom4han*e(0.K); "" Else $i*+ randoml% game.d# , 6illTurn1e tW le tD# : rightD#; game.dy , 6illTurn1e tW le tDy : rightDy; # #
'f you're not familiar with the S3 operator- the synta) is as follows=
expression W result-if-true : result-if-false
/ere- this means that we'll set game.d7 to left97 if 'ill;urn6eft is true and to right97 otherwise! 0e now have a workin& version of <erformA(! Hur resultin& implementation is not particularly dense- and most of the work is factored out into the helper functions! There is one task left ( implementin& :ove5na+e! Fecall that :ove5na+e moves the snake one step forward on its path! 'f the snake crashes- the function returns false to indicate that the &ame is over! Htherwise- the function returns true! The first thin& to do in :ove5na+e is to fi&ure out where the snake's head will #e after takin& a step! Thanks to GetEe7t<osition- this has already #een taken care of for us=
bool 0o)eSna+e!gameTS game$ { pointT ne#t'ead , Fet/e#tPosition!gameB game.d#B game.dy$; "2 ... 2" #
"ow- if we crashed into somethin& Keither #y fallin& off the map or #y hittin& an o#*ectL- we'll return false so that the main loop can terminate=
1 @1 1
bool :ove5na+e(game;? game) { $oint; ne7t/ead = GetEe7t<osition(game, game.d7, game.d%); i !;rashed!ne#t'eadB game$$ return alse; "2 ... 2" #
"e)t- we need to check to see if we ate some food! 0e'll store this in a bool varia#le for now- since the lo&ic for processin& food will come a #it later=
bool :ove5na+e(game;? game) { $oint; ne7t/ead = GetEe7t<osition(game, game.d7, game.d%); if(4rashed(ne7t/ead, game)) return false; bool isFood , !game.6orld9ne#t'ead.ro6:9ne#t'ead.col: ,, +FoodTile$; "2 ... 2" #
"ow- let's update the snake's head! 0e need to update the 'orld ve*tor so that the user can see that the snake's head is in a new s5uare- and also need to update the sna+e deVue so that the snake's head is now &iven #y the new position! This is shown here=
bool :ove5na+e(game;? game) { $oint; ne7t/ead = GetEe7t<osition(game, game.d7, game.d%); if(4rashed(ne7t/ead, game)) return false; bool is&ood = (game.'orld[ne7t/ead.ro'][ne7t/ead.*ol] == +&ood;ile); game.6orld9ne#t'ead.ro6:9ne#t'ead.col: , +Sna+eTile; game.sna+e.push& ront!ne#t'ead$; "2 ... 2" #
8inally- it's time to move the snake's tail forward one step! /owever- if we've eaten any food- we will leave the tail as1is so that the snake &rows #y one tile! 0e'll also put food someplace else on the map so the snake has a new o#*ective! The code for this is shown here=
1 @; 1
bool :ove5na+e(game;? game) { $oint; ne7t/ead = GetEe7t<osition(game, game.d7, game.d%); if(4rashed(ne7t/ead, game)) return false; bool is&ood = (game.'orld[ne7t/ead.ro'][ne7t/ead.*ol] == +&ood;ile); game.'orld[ne7t/ead.ro'][ne7t/ead.*ol] = +5na+e;ile; game.sna+e.$ushBfront(ne7t/ead); i !%isFood$ * game.6orld9game.sna+e.bac+!$.ro6:9game.sna+e.bac+!$.col: , +2mptyTile; game.sna+e.pop&bac+!$; 4 else * 33game.num2aten; PlaceFood!game$; 4 return true; #
0e're nearin& the home stretch ( all that's left to do is to implement <la*e&ood and we're doneU This function is simple ( we'll *ust sit in a loop pickin& random locations on the #oard until we find an empty spot- then will put a piece of food there! To &enerate a random location on the #oard- we'll scale rand() down to the proper ran&e usin& the modulus KZL operator! 8or e)ample- on a world with four rows and ten columns- we'd pick as a row rand() Z J and as a column *ol() Z 80! The code for this function is shown here=
void <la*e&ood(game;? game) { 6hile!true$ * int ro6 , rand!$ X game.numCo6s; int col , rand!$ X game.num;ols; "2 (f the s$e*ified $osition is em$t%, $la*e the food there. 2" i !game.6orld9ro6:9col: ,, +2mptyTile$ * game.6orld9ro6:9col: , +FoodTile; return; 4
4 #
1 @A 1
Con&ratulations on makin& it this farU 0e now have a complete workin& implementation of the Snake &ame with a computerized ,'! The maps and world are completely customiza#le and you can create your own levels if you so choose! Goreover- in the process we've covered the ST - streams li#raries- and a scatterin& of other li#raries! /owever- this code has a lot of potential for e)tensions! 'f you're up for a challen&e or *ust want to fle) your newfound C++ muscles- try playin& around with some of these ideas= 1! #ulti&le snakes! Some variants of Snake have multiple snakes each tryin& to collect as much food as possi#le! Dach snake moves independently of the others and can #e hurt #y crashin& into any snake- not *ust itself! Convertin& the a#ove code to use multiple snakes would #e a &reat way to play around with the ST and shouldn't #e too difficult if you put the snake data into an ST ve*tor! ;! ,ouncing balls! Hne Snake variant which ' am particularly fond of is Dattler Dace- which was #undled with the Gicrosoft Dntertainment Cack in 1<<0! 'n addition to snakes- this &ame featured #ouncin& #alls which moved at B?o an&les and reflected off of walls at <0o an&les! The #alls were harmless if they hit the snake's #ody- #ut were lethal if they hit the snake's head! Try addin& #ouncin& #alls to this implementation of snake! 'f you store the #alls in a ve*tor or deVue appropriately- this will not #e a difficult update! A! #ulti&le le7els! Gany versions of Snake have multiple levels! Hnce the snake &athers a certain num#er of food pieces- it shrinks down to size one and then advances to the ne)t level! See if you can think of a way to incorporate multiple levels to this Snake variant! Hne possi#ility would #e to have the user specify a 4puzzle file6 at the start of the &ame containin& a list of puzzle files- then to iterate throu&h them as the user advances! B! Im&ro7ed growing! 'n our implementation of Snake- the snake &rows #y len&th one every time it eats food! Some Snake variants have the snake &row #y pro&ressively &reater len&ths as the size of the snake increases! Try modifyin& the code so that the snake's len&th increases #y more than one!
/ere is the complete source for the Snake e)ample! Comments have #een added to the relevant sections!
-in*lude iostream) -in*lude string) -in*lude deVue) -in*lude ve*tor) -in*lude fstream) -in*lude *stdlib) "" rand, srand, s%stem -in*lude *time) "" *lo*+, *lo*+Bt, 46=4X5B<ECB5E4, time using names$a*e std; "2 <robabilit% of turning at ea*h ste$. 2" *onst double +;urnCate = 0.H; "2 ;ime to 'ait, in se*onds, bet'een frames. 2" *onst double +1ait;ime = 0.8; "2 Eumber of food $ellets that must be eaten to 'in. 2" *onst int +:a7&ood = H0; "2 4onstants for the different tile t%$es. 2" *onst *har +Em$t%;ile = A A; *onst *har +1all;ile = A-A; *onst *har +&ood;ile = AYA; *onst *har +5na+e;ile = A2A; "2 ;he string used to *lear the dis$la% before $rinting the game board. 1indo's 2 s%stems should use .465., :a* =5 P or 6inu7 users should use .*lear.. 2" *onst string +4lear4ommand = .465.; "2 A stru*t en*oding a $oint in a t'o,dimensional grid. 2" stru*t $oint; { int ro', *ol; #; "2 A stru*t *ontaining relevant game information. 2" stru*t game; { ve*tor string) 'orld; "" ;he $la%ing field int numCo's, num4ols; "" 5iUe of the $la%ing field deVue $oint;) sna+e; int d7, d%; #; int numEaten; "" ;he sna+e bod% "" ;he sna+e dire*tion "" /o' mu*h food 'eAve eaten.
"2 Ceads a line of te7t from the user. 2" string Get6ine() { string result; getline(*in, result); return result; #
1 @? 1
"2 Ceturns true 'ith $robabilit% $robabilit%. ;his 'or+s b% s*aling 2 do'n rand() b% CAE9B:AP ! 8.0 so that 'e have a value in [0, 8) and returning 2 'hether the value is less than the set $robabilit%. 2" bool Candom4han*e(double $robabilit%) { return (rand() " (CAE9B:AP ! 8.0)) $robabilit%; # "2 <la*es a $ie*e of food randoml% on the board. 2 is some free s$a*e remaining. 2" void <la*e&ood(game;? game) { 'hile(true) { int ro' = rand() Z game.numCo's; int *ol = rand() Z game.num4ols; ;his assumes that there
"2 (f there the s$e*ified $osition is em$t%, $la*e the food there. 2" if(game.'orld[ro'][*ol] == +Em$t%;ile) { game.'orld[ro'][*ol] = +&ood;ile; return; # # #
"2 4lears the dis$la% and $rints the game board. 2" void <rint1orld(game;? game) { "2 @se a s%stem *all to *lear the dis$la%. 2" s%stem(+4lear4ommand.*Bstr()); "2 <rint ea*h ro'. 2" for(int i = 0; i game.'orld.siUe(); !!i) *out game.'orld[i] endl; *out # .&ood eaten3 . game.numEaten endl;
1 @6 1
"2 Given an ifstream to a file *ontaining 4=CCE4;6R,&=C:A;;E9 'orld data, 2 loads in the 'orld. 2 2 ;he format used is as follo's3 2 6ine 83 numCo's num4ols 2 6ine H3 d7 d% 2 Cest3 1orld data 2 2 1e assume that the 'orld is *orre*tl%,siUed and that there is a single 2 A2A *hara*ter in the 'orld thatAs the starting $oint for the sna+e. 2" void 6oad1orld(game;? game, ifstream? in$ut) { "2 Cead in the number of ro's and *olumns. 2" in$ut )) game.numCo's )) game.num4ols; game.'orld.resiUe(game.numCo's); "2 Cead in the starting lo*ation. 2" in$ut )) game.d7 )) game.d%; "2 Oe*ause 'eAre going to be using getline() to read in the 'orld 2 data, 'e need to ma+e sure that 'e *onsume the ne'line *hara*ter 2 at the end of the line *ontaining the in$ut data. 1eAll use 2 getline() to handle this. 5ee the *ha$ter on streams for 'h% 2 this is ne*essar%. 2" string dumm%; getline(in$ut, dumm%); "2 Cead in the ro's. 2" for(int ro' = 0; ro' game.numCo's; !!ro') { getline(in$ut, game.'orld[ro']); "2 4he*+ to see if the 2 *hara*ter (sna+e start $osition) 2 is in this line. (f so, ma+e the sna+e. 2" int *ol = game.'orld[ro'].find(A2A); if(*ol 0= string33n$os) game.sna+e.$ushBba*+(:a+e<oint(ro', *ol)); # "2 5et numEaten to Uero , this needs to get done some'here0 2" game.numEaten = 0; # "2 /el$er fun*tion 'hi*h returns 'hether a $oint is *ontained in the game 2 grid. 2" bool (n1orld($oint;? $t, game;? game) { return $t.*ol )= 0 ?? $t.ro' )= 0 ?? $t.*ol game.num4ols ?? $t.ro' game.numCo's; #
1 @@ 1
1 @E 1
"2 <erforms A( logi* to *ontrol the sna+e. ;he behavior is as follo's3 2 8. (f 'e are going to *rash, 'e tr% to turn. 2 H. (nde$endentl%, 'e have a $er*ent *han*e to turn at ea*h ste$. 2 F. (f 'e do have to turn, 'e al'a%s turn in a safe dire*tion, and if 'e have 2 multi$le o$tions 'e $i*+ one randoml%. 2" void <erformA((game;? game) { "2 6oo+ 'here 'eAre going to be ne7t ste$. 2" $oint; ne7t5$ot = GetEe7t<osition(game, game.d7, game.d%); "2 (f this *rashes us or 'e >ust feel li+e turning, turn. 2" if(4rashed(ne7t5$ot, game) NN Candom4han*e(+;urnCate)) { "2 4om$ute 'hat dire*tion 'eAd be fa*ing if 'e turned left or 2 right. &rom linear algebra 'e have the follo'ing3 2 2 &or a left turn3 2 N7AN N0 ,8NN7N ,,) 7A = ,% 2 N%AN = N8 0NN%N ,,) %A = 7 2 2 &or a right turn3 2 N7AN N0 8NN7N ,,) 7A = % 2 N%AN = N,8 0NN%N ,,) %A = ,7 2" int left97 = ,game.d%; int left9% = game.d7; int right97 = game.d%; int right9% = ,game.d7; "2 4he*+ if turning left or right 'ill *ause us to *rash. 2" bool *an6eft = 04rashed(GetEe7t<osition(game, left97, left9%), game); bool *anCight = 04rashed(GetEe7t<osition(game, right97, right9%), game); "2 Eo' determine 'hi*h dire*tion to turn based on 'hat dire*tion 2 'eAre fa*ing. (f 'e *an *hoose either dire*tion, $i*+ one 2 randoml%. (f 'e *anAt turn, donAt. 2" bool 'ill;urn6eft; if(0*an6eft ?? 0*anCight) return; else if(*an6eft ?? 0*anCight) 'ill;urn6eft = true; else if(0*an6eft ?? *anCight) 'ill;urn6eft = false; else 'ill;urn6eft = Candom4han*e(0.K); "2 Oased on the dire*tion, turn a$$ro$riatel%. 2" game.d7 = 'ill;urn6eftS left97 3 right97; game.d% = 'ill;urn6eftS left9% 3 right9%; # #
1 @< 1
"2 (f 'e got food, $i*+ a ne' s$ot and donAt remove the tail. ;his *auses us 2 to e7tend b% one s$ot. 2" if(is&ood) { <la*e&ood(game); !!game.numEaten; # else { "2 4lear the tail and remove it from the sna+e. 2" game.'orld[game.sna+e.ba*+().ro'][game.sna+e.ba*+().*ol] = +Em$t%;ile; game.sna+e.$o$Bba*+(); # return true; # "2 <auses for a fe' millise*onds so 'e *an see 'hatAs ha$$ening. ;his is 2 im$lemented using a bus% loo$, 'hi*h is less,than,o$timal but doesnAt 2 reVuire $latform,s$e*ifi* features. 2" void <ause() { *lo*+Bt start = *lo*+(); 'hile(stati*B*ast double)(*lo*+() , start) " 46=4X5B<ECB5E4 +1ait;ime); # "2 9is$la%s the result of the game. 2" void 9is$la%Cesult(game;? game) { <rint1orld(game); if(game.numEaten == +:a7&ood) *out .Ra%0 ;he sna+e 'on0. endl; else *out .=h no0 ;he sna+e *rashed0. endl; #
1 E0 1
"2 <rom$ts the user for a filename, then o$ens the s$e*ified file. 2" void =$en@ser&ile(ifstream? in$ut) { 'hile(true) { *out .Enter level file3 .; in$ut.o$en(Get6ine().*Bstr()); if(0in$ut.fail()) return; *out .5orr%, ( *anAt o$en that file.. in$ut.*lear(); # # endl;
"2 (nitialiUes the game and loads the level file. 2" void (nitialiUeGame(game;? game) { "2 5eed the randomiUer. 2" srand(stati*B*ast int)(time(E@66))); ifstream in$ut; =$en@ser&ile(in$ut); 6oad1orld(game, in$ut); # "2 Cuns the simulation and dis$la%s the result. 2" void Cun5imulation(game;? game) { "2 Xee$ loo$ing 'hile 'e havenAt eaten too mu*h. 2" 'hile(game.numEaten +:a7&ood) { <rint1orld(game); <erformA((game); "2 :ove the sna+e and abort if 'e *rashed. 2" if(0:ove5na+e(game)) brea+; <ause();
# 9is$la%Cesult(game);
"2 ;he main $rogram. (nitialiUes the 'orld, then runs the simulation. 2" int main() { game; game; (nitialiUeGame(game); Cun5imulation(game); return 0; #
The ST comprises several different container types- from the linear ve*tor to the associative ma$! To provide a unified means of accessin& and modifyin& elements in different container types- the ST uses iterators- o#*ects that traverse ran&es of data! 0hile the #enefits of iterators mi&ht not #e apparent ri&ht now- when we cover al1 &orithms in a later chapter you will see e)actly how much power and fle)i#ility iterators afford! This chapter e)1 plores a num#er of issues pertainin& to iterators- #e&innin& with #asic synta) and endin& with the iterator hier1 archy and iterator adapters! A /ord on SafetyF Synta%F and 1eadability The iterators you've #een e)posed to in CS1067IJ are $ava1style iterators- which support functions called ne7t and hasEe7t to return values and identify whether they may safely advance! Unfortunately- ST iteratorsthou&h providin& similar functionality- have a completely different synta)! ST iterators are desi&ned to look like raw C++ pointers Ksee the chapter on C strin&s for more detailsL- and like pointers do not perform any #ounds checkin&! /owever- as with every other part of the ST - this desi&n decision leads to impressive per1 formance &ains! 'terators are optimized for speed- and in some cases you can increase the performance of your code #y switchin& to iterator loops over traditional for loops! A /ord on Tem&lates ,ll ST iterators have a common interface ( that is- operations on an iterator for a deVue string) have the same synta) as operations on an iterator for a set int)! This commonality paves the way for ST al&orithmswhich we'll cover in an upcomin& chapter! /owever- #ecause iterators are so commonly used in template func1 tions- you are likely to encounter some very nasty compiler messa&es if you make synta) errors with iterators! , sin&le mistake can cause a cascade of catastrophic compiler complaints that can really ruin your day! 'f you en1 counter these messa&es- #e prepared to sift throu&h them for a while #efore you find the root of your pro#lem! "ow- without further ado- let's start talkin& a#out iteratorsU ,asic STL Iterators Father than *umpin& head1first into full1fled&ed iterator synta)- we'll start off at the #asics and take smaller steps until we end up with the idiomatic ST iterator loop! ,ll ST container classes- e)cept for sta*+ and Vueue- define the iterator type!Q 8or e)ample- to declare an iterator for a ve*tor int)- we'd use the synta)
)ector.int>::iterator myItr; "" 4orre*t, but iterator is uninitialiUed.
"ote that unlike in the CS1067IJ li#raries- the word iterator is in lowercase! ,lso- keep in mind that this iterator can only iterate over a ve*tor int)R if we wanted to iterate over a ve*tor string) or deVue int)- we'd have to declare iterators of the type ve*tor string)33iterator and deVue int)33iterator- respectively!
sta*+
and
Vueue
1 E; 1
ST iterators are desi&ned to work like C and C++ pointers! Thus- to &et the value of the element pointed at #y an iterator- you 4dereference6 it usin& the 2 operator! Unlike CS1067IJ iterators- ST iterators can modify the contents of the container they are iteratin& over! /ere is some code usin& iterators to read and write values=
"2 Assumes m%(tr is of t%$e ve*tor int)33iterator 2" int value = RmyItr; "" Cead the element $ointed at b% the iterator. RmyItr = 8FG; "" 5et the element $ointed to b% the iterator to 8FG.
0hen workin& with containers of o#*ects- *lasses- or stru*ts- you can use the ,) operator to select a data mem#er or mem#er function of an o#*ect pointed at #y an iterator! 8or e)ample- here's some code to print out the len&th of a strin& in a ve*tor string)=
"2 Assumes m%(tr is of t%$e ve*tor string)33iterator 2" *out myItr->length!$ endl; "" <rint length of the *urrent string.
Usin& an uninitialized iterator causes unde"ined beha!ior- which commonly manifests as a crash! 7efore you work with an iterator- make sure to initialize it to point to the elements of a container! Commonly you'll use the ST containers' begin mem#er functions- which return iterators to the first elements of the containers! 8or e)1 ample=
ve*tor int)33iterator itr = my5ector.begin!$; "" (nitialiUe the iterator 2itr = 8FG; "" m%De*tor[0] is no' set to 8FG.
"ote that unlike CS1067IJ iterators' ne7t function- &ettin& the value of an ST iterator usin& 2 does not ad1 vance it to the ne)t item in the container! To advance an ST iterator- use the !! operator- as shown #elow!
ve*tor int)33iterator itr = m%De*tor.begin(); 2itr = 8FG; "" m%De*tor[0] is no' 8FG. 33itr; "" Advan*e to ne7t element. 2itr = JH; "" m%De*tor[8] is no' JH.
.ou mi&ht #e wonderin& why the a#ove code uses !!itr rather than itr!!! 8or technical reasons involvin& operator overloadin& Kcovered in a later chapter of the course readerL- althou&h #oth !!itr and itr!! advance the iterator forward one position- the first version is sli&htly faster than the second! 0e now know how to set up an iterator- read and write values- and advance the iterator forward- #ut we have not yet descri#ed how to tell when an iterator has iterated over all of the elements in a container! 0ith CS1067IJ iterators- you can easily tell when you've hit the end of a container #y checkin& to see if hasEe7t returns false! /owever- ST iterators don't have a hasEe7t function or even rudimentary functionality like it! 0ith ST iterators you need two iterators to define a ran&e ( one for the #e&innin& and one for the end! 0hile this makes the synta) a #it trickier- it makes ST iterators more fle)i#le than CS1067IJ iterators #ecause it's pos1 si#le to iterate over an ar#itrary ran&e instead of the entire container! Dach ST container class defines an end function that returns an iterator to the element one #ast the end of the container! Cut another way- end returns the first iterator that is not in the container! 0hile this seems confusin&it's actually 5uite useful #ecause it lets you use iterators to define ran&es of the type Ostart- stopL! .ou can see this visually #elow= Start _
7DE J8 8E7A D7J7 Y8YY Y-8D
Stop _
1 EA 1
To #est see how to use the end function- consider the followin& code snippet- which is the idiomatic 4loop over a container6 for loop=
for(ve*tor int)33iterator itr = m%De*tor.begin(); itr %, my5ector.end!$; !!itr) *out 2itr endl;
/ere- we simply crawl over the container #y steppin& forward one element at a time until the iterator reaches end! "otice that the end condition is itr 0= m%De*tor.end()- whether the iterator traversin& the ran&e has hit the end of the se5uence! 'f you'll remem#er from our discussion of the ve*tor- the synta) to remove elements from the ve*tor is m%De*tor.erase(m%De*tor.begin() ! n)- where n represented the inde) of the element to remove! The reason this code works correctly is #ecause you can add inte&er values to ve*tor iterators to o#tain iterators that many positions past the current iterator! Thus to &et an iterator that points to the nth element of the ve*tor- we simply &et an iterator to the #e&innin& of the ve*tor with begin and add n to it! This same trick works with a deVue! Iterator :enerality ,s mentioned in the previous chapter- the deVue class supports the same functionality as the ve*tor! This holds true for iterators- and in fact deVue iterators have identical synta) and semantics to ve*tor iterators! 8or e)ample- here's some #asic code to print out all elements of a deVue=
for(deGue.int>::iterator itr = m%9eVue.begin(); itr 0= m%9eVue.end(); !!itr) *out 2itr endl; deVues and ve*tors are implemented differently ( the ve*tor with a sin&le conti&uous array- the deVue with
many chained arrays! 2espite these differences- the iterator loops to crawl over the two containers have e)actly the same structure! Somehow each iterator 4knows6 how to &et from one element to the ne)t- even if those ele1 ments aren't stored in consecutive memory locations! This is the real #eauty of ST iterators ( no matter how the data is stored- the iterators will access them correctly and with minimal synta) chan&es on your end! This is the ma&ic of o#erator o!erloading- a techni5ue we'll cover in the second half of this te)t! Esing Iterators to 9efine 1anges The ST relies heavily on iterators to define ran&es within containers! ,ll of the e)amples we've seen so far have iterated e)clusively over the ran&e Obegin()- end()L- #ut it is possi#le and often useful to iterate over smaller ran&es! 8or e)ample- suppose you want to write a loop that will print the first ten values of a deVue to a file! Usin& iterators- this can #e accomplished easily as follows=
for(deVue int)33iterator itr = m%9eVue.begin(); itr 0= myDeGue!$.begin 3 7-; "" ;en ste$s do'n from the beginning. !!itr) m%5tream 2itr endl;
Similarly- all containers support functions that let you access- manipulate- or add a ran&e of data defined #y iter1 ators! 8or e)ample- each container class provides an insert function that accepts a ran&e of iterators and in1 serts all values in that ran&e into the container! /ere's an e)ample=
1 EB 1
"2 4reate a ve*tor, fill it in 'ith the first E@:B(E;5 integers. 2" ve*tor int) m%De*tor; for(ve*tor int)33siUeBt%$e h = 0; h E@:B(E;5; !!h) m%De*tor.$ushBba*+(h); "2 4o$% the first five elements of the ve*tor into the deVue 2" deVue int) m%9eVue; myDeGue.insert!myDeGue.begin!$B "" 5tart lo*ation of 'here to insert my5ector.begin!$B my5ector.begin!$ 3 I$; "" Dalues to insert
Dven thou&h the ve*tor's iterators are of type ve*tor int)33iterator and not deVue int)33iteratorthe code will compile! This is your first real &limpse of the ma&ic of the ST = the fact that all iterators have the same interface means that the deVue can accept iterators from any container- not *ust other deVues! 0hat's even more interestin& is that you can specify a ran&e of iterators as ar&uments to the constructors of the associative containers Kma$ and set- which are covered in the ne)t chapterL! 0hen the new container is con1 structed- it will contain all of the elements specified #y the ran&e! This ne)t code snippet fills in a set with the contents of a ve*tor=
ve*tor int) m%De*tor; "2 ... initialiUe m%De*tor ... 2" set int) m%5et!my5ector.begin!$B my5ector.end!$$;
This is really where the 4template6 in 4Standard Template i#rary6 #e&ins to show its stren&th- and the upcom1 in& chapter on al&orithms will demonstrate e)actly how powerful the common iterator interface can #e! Iterating ,ackwards ,t some point you mi&ht want to traverse the elements of a container #ackwards! ,ll ST containers define a type called reverseBiterator that represents an iterator that responds to the normal !! operator #ackwards! 8or e)ample- the statement !!m%Ceverse(tr would result in m%Ceverse(tr pointin& to the element that came be"ore the current one! Similarly- containers have functions rbegin and rend that act as reversed ver1 sions of the traditional begin and end! "ote- however- that rbegin does not point one past the end as does end ( instead it points to the very last element in the container! Similarly- rend points one position be"ore the first element- not to the same element as begin! /ere's some code showin& off reverse iterators=
"2 <rint a ve*tor ba*+'ards 2" for()ector.int>::re)erse&iterator itr = m%De*tor.rbegin(); itr 0= m%De*tor.rend(); !!itr) *out 2itr endl;
Iterator Categories 'f you'll recall from the discussion of the ve*tor and deVue insert functions- to specify an iterator to the nth element of a ve*tor- we used the synta) m%De*tor.begin() ! n! ,lthou&h this synta) is le&al in con*unc1 tion with ve*tor and deVue- it is ille&al to use ! operator with iterators for other container classes like ma$ and set! ,t first this may seem stran&e ( after all- there's nothin& intuitively wron& with movin& a set iterator for1 ward multiple steps- #ut when you consider how the set is internally structured the reasons #ecome more o#vi1 ous! Unlike ve*tor and deVue- the elements in a ma$ or set are not stored se5uentially Kusually they're kept in a #alanced #inary treeL! Conse5uently- to advance an iterator n steps forward- the ma$ or set iterator must take n individual steps forward! Contrast this with a ve*tor iterator- where advancin& forward n steps is a simple addition Ksince all of the ve*tor's elements are stored conti&uouslyL! Since the runtime comple)ity of
1 E? 1
advancin& a ma$ or set iterator forward n steps is linear in the size of the *ump- whereas advancin& a ve*tor iterator is a constant1time operation- the ST disallows the ! operator for ma$ and set iterators to prevent su#tle sources of inefficiency! 7ecause not all ST iterators can efficiently or le&ally perform all of the functions of every other iterator- ST iterators are cate&orized #ased on their relative power! ,t the hi&h end are random-access iterators that can per1 form all of the possi#le iterator functions- and at the #ottom are the in#ut and out#ut iterators which &uarantee only a minimum of functionality! There are five different types of iterators- each of which is discussed in short detail #elow! 2ut&ut Iterators! Hutput iterators are one of the two weakest types of iterators! 0ith an output iterat1 or- you can write values usin& the synta) 2m%(tr = value and can advance the iterator forward one step usin& the !! operator! /owever- you cannot read a value from an output iterator usin& the synta) value = 2m%(tr- nor can you use the != or Q operators! In&ut Iterators! 'nput iterators are similar to output iterators e)cept that they read values instead of writin& them! That is- you can write code alon& the lines of value = 2m%(tr- #ut not 2m%(tr = value! Goreover- input iterators cannot iterate over the same ran&e twice! !orward Iterators! 8orward iterators com#ine the functionality of input and output iterators so that most intuitive operations are well1defined! 0ith a forward iterator- you can write #oth 2m%(tr = value and value = 2m%(tr! 8orward iterators- as their name su&&ests- can only move forward! Thus !!m%(tr is le&al- #ut ,,m%(tr is not! ,idirectional Iterators! 7idirectional iterators are the iterators e)posed #y ma$ and set and encom1 pass all of the functionality of forward iterators! ,dditionally- they can move #ackwards with the decre1 ment operator! Thus it's possi#le to write ,,m%(tr to &o #ack to the last element you visited- or even to traverse a list in reverse order! /owever- #idirectional iterators cannot respond to the ! or != operators! 1andomCAccess Iterators! 2on't &et tripped up #y the name ( random1access iterators don't move around randomly! Fandom1access iterators &et their name from their a#ility to move forward and #ack1 ward #y ar#itrary amounts at any point! These are the iterators employed #y ve*tor and deVue and represent the ma)imum possi#le functionality- includin& iterator1from1iterator su#traction- #racket syn1 ta)- and incrementation with ! and !=!
'f you'll notice- each class of iterators is pro&ressively more powerful than the previous one ( that is- the iterators form a functionality hierarchy! This means that when a li#rary function re5uires a certain class of iterator- you can provide it any iterator that's at least as powerful! 8or e)ample- if a function re5uires a forward iterator- you can provide either a forward- #idirectional- or random1access iterator! The iterator hierarchy is illustrated #elow= Random%A&&'(( It'rator( itr != distan*e; itr ! distan*e; itr8 itrH; itr[m%(nde7]; )idir'&tiona* It'rator( ,,itr; For+ard It'rator( In,-t It'rator( val = 2itr; !!itr; O-t,-t It'rator( 2itr = val; !!itr;
1 E6 1
string Iterators
The C++ string class e)ports its own iterator type and conse5uently is a container *ust like the ve*tor or ma$! ike ve*tor and deVue iterators- string iterators are random1access iterators- so you can write e)pres1 sions like m%5tring.begin() ! n to &et an iterator to the nth element of a string! Gost of the string mem#er functions that re5uire a start position and a len&th can also accept two iterators that define a ran&e! 8or e)ample- to replace characters three and four in a string with the strin& 4ST -6 you can write
m%5tring.re$la*e(myString.begin!$ 3 DB myString.begin!$ 3 I, `5;6`);
The string class also has several mem#er functions similar to those of the ve*tor- so #e sure to consult a ref1 erence for more information! Iterator Ada&ters The ST uses the idea of an iterator ran&e e)tensively- and anywhere that a ran&e of elements is e)pected the ST will accept it usin& a pair of iterators! 'nterestin&ly- however- the ST never verifies that the iterators it re1 ceives actually correspond to iterators over a container! 'n fact- it's possi#le to #uild o#*ects which look like iter1 ators ( for e)ample- they can #e dereferenced to a value with 2 and advanced forward with !! ( #ut which don't actually store elements inside an ST container! 0e can then plu& these o#*ects into the ST to 4trick6 the ST into performin& comple) operations #ehind1the1scenes! Such o#*ects are called iterator ada#ters and are one of the cornerstones of advanced ST pro&rammin&! To use the iterator adapters- you'll need to -in*lude the iterator) header! Hne of the more common iterator adapters is ostreamBiterator- which writes values to a stream! 8or e)1 ample- consider the followin& code which uses an ostreamBiterator to print values to *out=
ostream&iterator.int> myItr!coutB " "$; 2m%(tr = 8FG; "" <rints 8FG to *out !!m%(tr; 2m%(tr = JH; "" <rints JH to *out !!m%(tr
'f you compile and run this code- you will notice that the num#ers 1A@ and B; &et written to the console- separ1 ated #y spaces! ,lthou&h it loo*s like you're manipulatin& the contents of a container- you're actually writin& characters to the *out stream!
ostreamBiterator is a template type that re5uires you to specify what type of element you'd like to write to the stream! 'n the constructor- you must also specify an ostream to write to- which can #e any output streamincludin& *out- ofstreams and stringstreams! The final ar&ument to the constructor specifies an optional
strin& to print out #etween elements! .ou may omit this if you want the contents to run toðer! ,nother useful iterator adapter is the ba*+BinsertBiterator! 0ith ba*+BinsertBiterator- you can ap1 pend elements to a container usin& iterator synta)! 8or e)ample- the followin& code creates a ve*tor int) and uses a ba*+BinsertBiterator to fill it in=
1 E@ 1
ve*tor int) m%De*tor; "2 (nitiall% em$t% 2" bac+&insert&iterator.)ector.int> > itr!my5ector$; "" Eote that tem$late argument "" is ve*tor int). for(int i = 0; i 80; !!i) { Ritr , i; "" .1rite. to the ba*+BinsertBiterator, a$$ending the value. !!itr; # for(ve*tor int)33iterator itr = m%De*tor.begin(); itr 0= m%De*tor.end(); !!itr) *out 2itr endl; "" <rints numbers Uero through nine
,lthou&h we never e)plicitly added any elements to the ve*tor- throu&h the ma&ic of iterator adapters we were a#le to populate the ve*tor with data! The synta) ba*+BinsertBiterator ve*tor int) ) is a #it clunky- and in most cases when you're usin& ba*+BinsertBiterators you'll only need to create a temporary o#*ect! 8or e)ample- when usin& ST al1 &orithms- you'll most likely want to create a ba*+BinsertBiterator only in the conte)t of an al&orithm! To do this- you can use the ba*+Binserter function- which takes in a container and returns an initialized ba*+BinsertBiterator for use on that o#*ect! 'nternally- ba*+BinsertBiterator calls $ushBba*+ whenever it's dereferenced- so you can't use ba*+Bin, sertBiterators to insert elements into containers that don't have a $ushBba*+ mem#er function- such as ma$ or set! ,ll of these e)amples are interestin&- #ut why would you ever want to use an iterator adapter> ,fter all- you can *ust write values directly to *out instead of usin& an ostreamBiterator- and you can always call $ushBba*+ to insert elements into containers! 7ut iterator adapters have the advanta&e that they are iterators ( that is- if a function e)pects an iterator- you can pass in an iterator adapter instead of a re&ular iterator! Suppose- for e)1 ample- that you want to use an ST al&orithm to perform a computation and print the result directly to *out! Unfortunately- ST al&orithms aren't desi&ned to write values to *out ( they're written to store results in ran&es defined #y iterators! 7ut #y usin& an iterator adapter- you can trick the al&orithm into 4thinkin&6 it's storin& val1 ues #ut in reality is printin& them to *out! 'terator adapters thus let you customize the #ehavior of ST al1 &orithms #y chan&in& the way that they read and write data! The followin& ta#le lists some of the more common iterator adapters and provides some useful conte)t! .ou'll likely refer to this ta#le most when writin& code that uses al&orithms!
ba*+BinsertBiterator 4ontainer) ba*+BinsertBiterator ve*tor int) ) itr(m%De*tor); ba*+BinsertBiterator deVue *har) ) itr = ba*+Binserter(m%9eVue);
,n output iterator that stores elements #y callin& $ushBba*+ on the spe1 cified container! .ou can declare ba*+BinsertBiterators e)plicitly- or can create them with the function ba*+Binserter!
,n output iterator that stores elements #y callin& $ushBfront on the spe1 cified container! Since the container must have a $ushBfront mem#er function- you cannot use a frontBinsertBiterator with a ve*tor! ,s with ba*+BinsertBiterator- you can create frontBinsertBiterat, ors with the the frontBinserter function!
insertBiterator 4ontainer) insertBiterator set int) ) itr(m%5et, m%5et.begin()); insertBiterator set int) ) itr = inserter(m%5et, m%5et.begin());
,n output iterator that stores its elements #y callin& insert on the specified container to insert elements at the indicated position! .ou can use this iterat1 or type to insert into any container- especially set! The special function in, serter &enerates insertBiterators for you!
ostreamBiterator ;%$e) ostreamBiterator int) itr(*out, . .); ostreamBiterator *har) itr(*out); ostreamBiterator double) itr(m%5tream, .Tn.);
,n output iterator that writes elements into an output stream! 'n the con1 structor- you must initialize the ostreamBiterator to point to an os, tream- and can optionally provide a separator strin& written after every ele1 ment!
istreamBiterator ;%$e) istreamBiterator int) itr(*in); istreamBiterator int) end(tr; "" Ceads from *in "" 5$e*ial end value
,n input iterator that reads values from the specified istream when derefer1 enced! 0hen istreamBiterators reach the end of their streams Kfor e)1 ample- when readin& from a fileL- they take on a special 4end6 value that you can &et #y creatin& an istreamBiterator with no parameters! istreamBiterators are suscepti#le to stream failures and should #e used with care!
ostreambufBiterator *har) ostreambufBiterator *har) itr(*out); "" 1rite to *out
,n output iterator that writes raw character data to an output stream! Unlike ostreamBiteratorwhich can print values of any typeostreambufBiterator can only write individual characters! ostream, bufBiterator is usually used in con*unction with istreambufBiterat, or!
istreambufBiterator *har) istreambufBiterator *har) itr(*in); "" Cead data from *in istreambufBiterator *har) end(tr; "" 5$e*ial end value
,n input iterator that reads unformatted data from an input stream! istre, ambufBiterator always reads in character data and will not skip over whitespace! ike istreamBiterator- istreambufBiterators have a special iterator constructed with no parameters which indicates 4end of stream!6 istreambufBiterator is used primarily to read raw data from a file for processin& with the ST al&orithms!
1 E< 1
This chapter covers most of the iterator functions and scenarios you're likely to encounter in practice! 0hile there are many other interestin& iterator topics- most of them concern implementation techni5ues and thou&h fas1 cinatin& are far #eyond the scope of this class! /owever- there are a few topics that mi&ht #e worth lookin& intosome of which ''ve listed here= 1! ad)ance and distance= 7ecause not all iterators support the != operator- the ST includes a nifty function called advan*e that efficiently advances any iterator #y the specified distance! Usin& a tech1 ni5ue known as tem#late meta#rogramming- advan*e will always choose the fastest possi#le means for advancin& the iterator! 8or e)ample- callin& advan*e on a ve*tor iterator is e5uivalent to usin& the != operator- while advancin& a set iterator is e5uivalent to a for loop that uses !!! Similarly- there is a function called distan*e that efficiently computes the num#er of elements in a ran&e defined #y two iterators! ;! re)erse&iterator= reverseBiterator is an iterator adapter that converts an iterator movin& in one direction to an iterator movin& in the opposite direction! The semantics of reverseBiterator are a #it tricky ( for e)ample- constructin& a reverseBiterator from a re&ular ST iterator results in the reverseBiterator pointin& to the element one be"ore the element the ori&inal iterator points at ( #ut in many cases reverseBiterator can #e 5uite useful! A! The ,oost Iterators= The 7oost C++ i#raries have many iterator adapters that perform a wide variety of tasks! 8or e)ample- the filterBiterator type iterates over containers #ut skips over elements that don't match a certain predicate function! ,lso- the transformBiterator reads and writes elements only after first applyin& a transformation to them! 'f you're interestin& in superchar&in& your code with iterator adapters- definitely look into the 7oost li#raries! -ractice -roblems 1! Some ST containers contain mem#er functions to return iterators to specific elements! ,s a sentinel value- these functions return the value of the container's end() function! 0hy do you think this is> ;! 0rite a function 9u$li*ateCeversed that accepts a ve*tor int) and returns a new ve*tor int) with the same values as the ori&inal ve*tor #ut in reverse order! A! 0hat iterator cate&ory does ba*+BinsertBiterator #elon& to> B! Suppose you want to write a template function that iterates over a container and dou#les each element in1place! 0hat is the least powerful iterator cate&ory that would #e re5uired for this function to work> ?! Usin& iterator adapters- write a function 6oadAll;o+ens that- &iven a filename- returns a ve*tor con1 sistin& of all of the tokens in that file! 8or our purposes- define a token to #e a series of characters separ1 ated #y any form of whitespace! 0hile you can do this with a standard ifstream and a 'hile looptry to use istreamBiterators instead! M
Three chapters a&o we talked a#out ve*tor and deVue- the ST 's two mana&ed array classes! /owever- the ST offers many other containers- two of which- set and ma$- we will cover in this chapter! 7ecause set and ma$ are associative containers- they re5uire a decent understandin& of iterators and iterator synta)! ,t the endwe'll 5uickly talk a#out two special container classes- the multima$ and multiset- which have no counter1 parts in the CS1067IJ ,2T li#rary! typede The containers introduced in this chapter make e)tensive use of iterators and you may find yourself typin& out len&thy type declarations fre5uently! 8or e)ample- if you have a set string)- the return type of its insert function will #e $air set string)33iterator, bool)! This can #ecome a nuisance at times and you may want to use the t%$edef keyword to simplify your code! t%$edef is a sort of 4*onst for types6 that lets you define shorthands for types that already e)ist! The synta) for t%$edef is
t%$edef type-name replacement
8or e)ample- you could define (ntDe*(tr as a shorthand for ve*tor int)33iterator #y writin&
t%$edef ve*tor int)33iterator (ntDe*(tr;
Keep t%$edef in the #ack of your mind as you read the rest of this chapter ( you will undou#tedly find a host of uses for it as you continue your *ourney into the ST ! set The ST set- like the CS1067IJ 5et- is an associative container representin& an a#stract collection of o#*ects! .ou can test a set for inclusion- and can also insert and remove elements! /owever- unlike the CS1067IJ 5etthe ST set does not have intrinsic support for union- intersection- and difference- thou&h in the upcomin& chapter on al&orithms we'll see how you can o#tain this functionality! ike the CS1067IJ 5et- when storin& non1primitive types- the set re5uires you to specify a call#ack function that compares two values of the type #ein& stored! Unfortunately- the synta) to provide a comparison call#ack for the set either uses complicated- error1prone synta) or re5uires an understandin& of operator overloadin&- a lan&ua&e feature we haven't covered yet! Thus- for now- you should avoid storin& custom data types in sets! The most #asic operation you can perform on a set is insertion usin& the insert function! Unlike the deVue and ve*tor insert functions- you do not need to specify a location for the new element #ecause the set auto1 matically orders its elements! /ere is some sample code usin& insert=
set int) m%5et; mySet.insert!7DE$; mySet.insert!J8$; mySet.insert!7DE$; "" Eo' *ontains3 8FG "" Eo' *ontains3 JH 8FG (in that order) "" Eo' *ontains3 JH 8FG (note no du$li*ates)
1 <; 1
To check whether the set contains an element- you can use the find function! find searches the set for an element and- if found- returns an iterator to it! Htherwise- find returns the value of the container's end function as a sentinel indicatin& the element wasn't found! 8or e)ample- &iven the set initialized a#ove=
if(mySet. ind!7DE$ 0= m%5et.end()) *out .;he set *ontains 8FG.. endl; "" ;his is $rinted. endl; "" Also $rinted.
'nstead of find- you can also use the *ount function- which returns 1 if the element is contained in the set and 0 otherwise! Usin& C++'s automatic conversion of nonzero values into true and zero values to false- you can sometimes write cleaner code usin& *ount! 8or e)ample=
if(mySet.count!7DE$) *out .8FG is in the set.. endl; "" <rinted if(%mySet.count!I--$) *out .K00 is not in the set.. endl; "" <rinted
'f *ount is simpler than find- why use find> The reason is that sometimes you'll want an iterator to an ele1 ment of a set for reasons other than determinin& mem#ership! .ou mi&ht want to erase the value- for e)ampleor perhaps you'll want to o#tain iterators to a ran&e of values for use with an ST al&orithm! Thus- while *ount is 5uite useful- find is much more common in practice! .ou can remove elements from a set usin& erase! There are several versions of erase- one of which removes the value pointed at #y the specified iterator- and another that removes the specified value from the set if it e)1 ists! 8or e)ample=
mySet.erase!7DE$; "" Cemoves 8FG, if it e7ists. set int)33iterator itr = m%5et.find(JH); if(itr 0= m%5et.end()) mySet.erase!itr$;
The ST set stores its elements in sorted order- meanin& that if you iterate over a set you will see the values it contains in sorted order! 8or e)ample- if we have a set containin& the first five odd inte&ers- the followin& code will print them out in ascendin& order=
for(set int)33iterator itr = m%5et.begin(); itr 0= m%5et.end(); !!itr) *out 2itr endl;
7ecause sets store their elements in sorted order- it is possi#le to perform 5ueries on an ST set that you can1 not make on a CS1067IJ 5et! 'n particular- the set e)ports two functions- lo'erBbound and u$$erBboundthat can #e used to iterate over the elements in a set that are within a certain ran&e! lo'erBbound accepts a value- then returns an iterator to the first element in the set &reater than or e5ual to that value! u$$erBbound similarly accepts a value and returns an iterator to the first element in the set that is strictly &reater than the spe1 cified element! 3iven a closed ran&e Olo'er- u$$erP- we can iterate over that ran&e #y usin& lo'erBbound to &et an iterator to the first element no less than lo'er and iteratin& until we reach the value returned #y u$$erBbound- the first element strictly &reater than u$$er! 8or e)ample- the followin& loop iterates over all elements in the set in the ran&e O10- 100P=
set int)33iterator sto$ = mySet.upper&bound!7--$; for(set int)33iterator itr = mySet.lo6er&bound!7-$; itr 0= sto$; !!itr) "2 ... $erform tas+s... 2"
1 <A 1
, word of caution= you should not modify items pointed to #y set iterators! Since sets are internally stored in sorted order- if you modify a value in a set in1place- you risk ruinin& the internal orderin&! 0ith the elements no lon&er in sorted order- the set's searchin& routines may fail- leadin& to difficult1to1track #u&s! 'f you want to apply a transformation to a set's elements- unless you're certain the transformation will leave the elements in forward1sorted order- you should stron&ly consider creatin& a second set and storin& the result there! 'n factsome implementations of the ST won't even let you modify the elements of a set- an issue we'll address in the chapter on ST al&orithms! The followin& ta#le lists some of the most important set functions- thou&h there are more! The entries in this ta#le are also applica#le for the ma$- al#eit with the appropriate modifications Ksee the ne)t few sections for more informationL! &e ha!en(t co!ered const yet, so "or now it(s sa"e to ignore it) &e also ha!en(t co!ered const_iterators, but "or now you can just treat them as iterators that can(t write any !alues)
Constructor= set ;)()
set int) m%5et;
Constructs an empty set! Constructor= set ;)(*onst set ;)? other) set int) m%=ther5et = m%5et; Constructs a set that's a copy of another set! Constructor= set ;)((n$ut(terator start, set int) m%5et(m%De*.begin(), m%De*.end());
(n$ut(terator sto$)
Constructs a set containin& copies of the elements in the ran&e Ostart- sto$L! ,ny duplicates are discarded- and the elements are sor1 ted! "ote that this function accepts iterators from any source!
int numEntries = m%5et.siUe();
Feturns an iterator to the start of the set! 7e careful when modifyin& elements in1place!
iterator end() *onstBiterator end() 'hile(itr 0= m%5et.end()) { ... #
Feturns an iterator to the element one past the end of the final element of the set!
$air iterator, bool) insert(*onst ;? value) void insert((n$ut(terator begin, (n$ut(terator end) m%5et.insert(J); m%5et.insert(m%De*.begin(), m%De*.end());
The first version inserts the specified value into the set! The return type is a $air containin& an iterator to the element and a bool indicat1 in& whether the element was inserted successfully KtrueL or if it already e)isted KfalseL! The second version inserts the specified ran&e of elements into the set- i&norin& duplicates!
1 <B 1
set functions- contd!
iterator find(*onst ;? element) *onstBiterator find(*onst ;? element) *onst siUeBt%$e *ount(*onst ;? item) *onst
Feturns an iterator to the specified element if it e)ists- and end other1 wise!
if(m%5et.*ount(0)) { ... #
Feturns 1 if the specified element is contained in the set- and 0 oth1 erwise!
siUeBt%$e erase(*onst ;? element) void erase(iterator itr); void erase(iterator start, iterator sto$); if(m%5et.erase(0)) {...# "" 0 'as erased m%5et.erase(m%5et.begin()); m%5et.erase(m%5et.begin(), m%5et.end());
Femoves an element from the set! 'n the first version- the specified element is removed if found- and the function returns 1 if the element was removed and 0 if it wasn't in the set! The second version re1 moves the element pointed to #y itr! The final version erases ele1 ments in the ran&e Ostart- sto$L!
iterator lo'erBbound(*onst ;? value) itr = m%5et.lo'erBbound(K);
Feturns an iterator to the first element that is &reater than or e5ual to the specified value! This function is useful for o#tainin& iterators to a ran&e of elements- especially in con*unction with u$$erBbound!
iterator u$$erBbound(*onst ;? value) itr = m%5et.u$$erBbound(800);
Feturns an iterator to the first element that is &reater than the spe1 cified value! 7ecause this element must #e strictly &reater than the specified value- you can iterate over a ran&e until the iterator is e5ual to u$$erBbound to o#tain all elements less than or e5ual to the para1 meter!
pair 7efore introducin& ma$- let us first 5uickly discuss $air- a template stru*t that stores two elements of mi)ed types! $air Kdefined in utilit%)L accepts two template ar&uments and is declared as $air ;%$e=ne, ;%$e;'o)! $air has two fields- named first and se*ond- which store the values of the two elements of the pairR first is a varia#le of type ;%$e=ne- se*ond of type ;%$e;'o! .ou can create a pair e)plicitly usin& this synta)=
pair.intB string> m%<air; myPair. irst = m%(nteger; myPair.second = m%5tring;
,lternatively- you can initialize the $air's mem#ers in the constructor as follows=
$air int, string) m%<air!myIntegerB myString$;
'n some instances- you will need to create a $air on1the1fly to pass as a parameter Kespecially to the ma$'s in, sertL! .ou can therefore use the ma+eB$air function as follows=
$air int, string) m%<air = ma+e&pair!7DEB "string%"$;
1 <? 1
'nterestin&ly- even thou&h we didn't specify what type of $air to create- the ma+eB$air function was a#le to deduce the type of the $air from the types of the elements! This has to do with how C++ handles function tem1 plates and we'll e)plore this in more detail later! map The ma$ is one of the ST 's most useful containers! ike the CS1067IJ :a$- the ST ma$ is an associative container which lets you 5uery which value is associated with a &iven key! Unlike the CS1067IJ :a$- the ST ma$ lets you choose what key type you want to use! That is- the ST ma$ can map strings to ints- ints to strings- or even ve*tor int)s to deVue double)s! ,lso unlike the CS1067IJ :a$- the ST ma$ stores its elements in sorted order! That is- if you iterate over the keys in a ma$ string, int)- you'll &et all of the string keys #ack in alpha#etical order! ,s a conse5uence- if you want to use nonstandard types as keys in a ma$ ( for e)ample- your own custom stru*ts ( you will need to provide a comparison function! ,&ain- like set- providin& this function is 5uite difficult and we will not discuss how until we cover operator overloadin&! To declare a ma$- use the followin& synta)=
map.MeyTypeB 5alueType> m%:a$;
8or e)ample- to create a ma$ similar to the CS1067IJ :a$ int)- you'd write ma$ string, int)- a mappin& from strings to ints! Unfortunately- while the ST ma$ is powerful- its synta) is considera#ly more complicated than that of other containers #ecause its functions and iterators need to accept- return- and manipulate two values! The ma$'s iter1 ators act as pointers to $air *onst Xe%;%$e, Dalue;%$e)s- pair of an immuta#le key and a muta#le value! 8or e)ample- to iterate over a ma$ string, int) and print out all its keyIvalue pairs- you can use the follow1 in& loop=
for(ma$ string, int)33iterator itr = m%:a$.begin(); itr 0= m%:a$.end(); !!itr) *out itr-> irst .3 . itr->second endl;
"ote that the key is itr,)first and the value is itr,)se*ond! 0hile you cannot chan&e the value of the key durin& iteration-Q you can modify its associated value! ,s with the set- you can use find to return an iterator to an element in a ma$ &iven a key! 8or e)ample- to check to see if 4ST 6 is a key in a ma$ string, int)- you can use this synta)=
if(my0ap. ind!"ST1"$ 0= m%:a$.end()) { ... #
,lternatively- you can use the *ount function as you would with a set! 7ecause the ma$ stores everythin& as $airs- insertin& a new value into a map is a synta) headache! .ou must manually create the pair to insert usin& the ma+eB$air function discussed earlier! Thus- if you wanted to insert the keyIvalue pair K4ST 6- 1A@L into a ma$ string, int)- you must use this synta)=
m%:a$.insert(ma+e&pair!"ST1"B 7DE$);
Unlike the CS1067IJ :a$- if you try to overwrite the value of an e)istin& item with the insert function- the ST ma$ won't modify the value! Thus- if you write
Q ,s with the set- chan&in& the values of the keys in a ma$ could destroy the internal orderin& and render the ma$ non1 functional! /owever- unlike the set- if you try to modify a key with an iterator- you'll &et a compile1time error!
1 <6 1
m%:a$.insert(ma+eB$air(.5;6., 8FG)); "" (nserted m%:a$.insert(ma+eB$air(.5;6., JH)); "" 1hoo$s0 Eot over'ritten.
The value associated with the key 4ST 6 will still #e the value 1A@! To check whether your insertion actually created a new value- the ma$'s insert function returns a $air iterator, bool) that contains information a#out the result of the operation! The first element of the $air is an iterator to the keyIvalue pair you *ust inser1 ted- and the second element is a bool indicatin& whether the insert operation set the value appropriately! 'f this value is false- the value stored in the ma$ was not updated! Thus- you can write code like this to insert a keyIvalue pair and manually update the value if the key already e)isted=
"2 ;r% to insert normall%. 2" pair.map.stringB int>::iteratorB bool> result = m%:a$.insert(ma+eB$air(.5;6., 8FG)); "2 (f insertion failed, manuall% set the value. 2" i !%result.second$ result. irst->second , 7DE;
'n the last line- the e)pression result.first,)se*ond is the value of the e)istin& entry- since res, ult.first yields an iterator pointin& to the entry- so result.first,)se*ond is the value field of the iterat1 or to the entry! ,s you can see- the $air can make for tricky- unintuitive code! There is an alternate synta) you can use to insert and access elements involvin& the #racket operator! ,s with the CS1067IJ ma$- you can access individual elements #y writin& m%:a$[+e%]! 'f the element doesn't already e)ist- it will #e created! 8urthermore- if the key already e)ists- the value will #e updated! 8or e)ample- you can use the #racket operator to insert a new element into a ma$ string, int) as follows=
m%:a$[.5;6.] = 8FG;
.ou can also use the #racket operator to read a value from the ma$! ,s with insertion- if the element doesn't e)1 ist- it will #e created! 8or e)ample=
int m%Dalue = m%:a$[.5;6.];
'f the #racket notation is so much more convenient- why even #other with insert> ,s with everythin& in the ST - the reason is efficiency! Suppose you have a ma$ string, ve*tor int) )! 'f you insert a keyIvalue pair usin& m%:a$.insert(ma+eB$air(.5;6., m%De*tor)) the newly created ve*tor int) will #e ini1 tialized to the contents of m%De*tor! 0ith m%:a$[.5;6.] = m%De*tor- the #racket operation will first cre1 ate an empty ve*tor int)- then assi&n m%De*tor on top of it! ,s you'll see later in the chapter on class con1 struction- this second version is noticea#ly slower than the first! ,s an ST pro&rammer- you are workin& with professional li#raries! 'f you want to &o for raw speed- yes- you will have to deal with more complicated synta)#ut if you want to *ust &et the code up and runnin&- then the ST won't have any reservations! multimap and multiset The ST provides two special 4multicontainer6 classes- multima$ and multiset- that act as ma$s and sets e)cept that the values and keys they store are not necessarily uni5ue! That is- a multiset could contain several copies of the same value- while a multima$ mi&ht have duplicate keys associated with different values!
multima$ and multiset Kdeclared in ma$) and set)- respectivelyL have identical synta) to ma$ and sete)cept that some of the functions work sli&htly differently! 8or e)ample- the *ount function will return the num#er of copies of an element an a multicontainer- not *ust a #inary zero or one! ,lso- while find will still re1
turn an iterator to an element if it e)ists- the element it points to is not &uaranteed to #e the only copy of that ele1
1 <@ 1
ment in the multicontainer! 8inally- the erase function will erase all copies of the specified key or element- not *ust the first it encounters! Hne function that's 5uite useful for the multicontainers is eVualBrange! eVualBrange returns a $air iter, ator, iterator) that represents the span of entries e5ual to the specified value! 8or e)ample- &iven a mul, tima$ string, int)- you could use the followin& code to iterate over all entries with key 4ST 6=
"2 5tore the result of the eVualBrange 2" pair.multimap.stringB int>::iteratorB multimap.stringB int>::iterator> m%<air = my0ulti0ap.eGual&range!"ST1"$; "2 (terate over it0 2" for(multima$ string, int)33iterator itr = m%<air.first; itr 0= m%<air.se*ond; !!itr) *out itr,)first .3 . itr,)se*ond endl;
,s you can see- you mi&ht find yourself needin& to t%$edef your iterator types a #it more with the multicon1 tainers- #ut nonetheless they are 5uite useful! #ore to $%&lore 'n this chapter we covered ma$ and set- which com#ined with ve*tor and deVue are the most commonly1used ST containers! /owever- there are several others we didn't cover- a few of which mi&ht #e worth lookin& into! /ere are some topics you mi&ht want to read up on= 1! list= ve*tor and deVue are se5uential containers that mimic #uilt1in arrays! The list containerhowever- models a se5uence of elements without indices! list supports several nifty operations- such as mer&in&- sortin&- and splicin&- and has 5uick insertions at almost any point! 'f you're plannin& on us1 in& a linked list for an operation- the list container is perfect for you! ;! The ,oost Containers= The 7oost C++ i#raries are a collection of functions and classes developed to au&ment C++'s native li#rary support! 7oost offers several new container classes that mi&ht #e worth lookin& into! 8or e)ample- multiBarra% is a container class that acts as a Grid in any num#er of di1 mensions! ,lso- the unorderedBset and unorderedBma$ act as replacements to the set and ma$ that use hashin& instead of tree structures to store their data! 'f you're interested in e)plorin& these con1 tainers- head on over to www!#oost!or&!
Some of these pro#lems are pro&rammin& e)ercises- others *ust cool thin&s to think a#out! Clay around with them a #it to &et some practice with ma$ and setU 1! Usin& the timin& code su&&ested in the practice pro#lems from the first chapter on ST containers- time the speed differences of the ma$'s insert function versus the #racket synta) in a ma$ int, int)then repeat the trial for ma$ int, string)! 0hat does this tell you a#out the time it takes to copy o#1 *ects> ;! 0rite a function Eumber9u$li*ateEntries that accepts a ma$ string, string) and returns the num#er of duplicate !alues in the ma$ Kthat is- the num#er of keyIvalue pairs in the map with the same valueL! M A! 0rite a function (nvert:a$ that accepts as input a multima$ string, string) and returns a mul, tima$ string, string) where each pair Kkey- valueL in the source map is represented #y KvaluekeyL in the &enerated map! Can you think of any applications for a function like this> B! ,s mentioned earlier- you can use a com#ination of lo'erBbound and u$$erBbound to iterate over elements in the closed interval Omin- ma)P! 0hat com#ination of these two functions could you use to iterate over the interval Omin- ma)L> 0hat a#out Kmin- ma)P and Kmin- ma)L> ?! 0rite a function 4ount6etters that accepts as input an ifstream and a ma$ *har, int) #y refer1 ence- then updates the ma$ such that each character that appears at least once in the file is mapped to the num#er of times that the character appears in the file! .ou can assume that the ma$ is initially empty! ,s a useful 8.'- unlike the CS1067IJ :a$- if you access a none)istent key in the ST ma$ usin& the #racket operators- the value associated with the key defaults to zero! That is- if you have a ma$ string, int) named m%:a$ that doesn't contain the key .5;6.- then m%:a$[.5;6.] will yield zero Kand also add .5;6. as a keyL! ,lso note that to read a sin&le character from a file- it's prefera#le to use the get() mem#er function from the stream rather than the stream e)traction operator )) since get() does not i&nore whitespace! M 6! KChallen&e pro#lemUL 0rite a function <rint:at*hing<refi7es that accepts a set string) and a string containin& a prefi) and prints out all of the entries of the set that #e&in with that prefi)! .our function should only iterate over the entires it finally prints out! .ou can assume the prefi) is nonemptyconsists only of alphanumeric characters- and should treat prefi)es case1sensitively! <+int% In a set<string>, strings are sorted le7icogra#hically, so all strings that start with EabcF will come be"ore all strings that start with Eabd)F= M
Computer science is often e5uated with pro&rammin& and software en&ineerin&! Gany a computer science stu1 dent has to deal with the occasional 4Hh- you're a computer science ma*orU Can you make me a we#site>6 or 4Computer science- eh> 0hy isn't my 'nternet workin&>6 This is hardly the case and computer science is a much #roader discipline that encompasses many fields- such as artificial intelli&ence- &raphics- and #iocomputa1 tion! Hne particular su#discipline of computer science is com#utability theory! Since computer science involves so much pro&rammin&- a &ood 5uestion is e)actly what we can command a computer to do! 0hat sorts of pro#1 lems can we solve> /ow efficiently> 0hat pro#lems can(t we solve and why not> Gany of the most important results in computer science- such as the undecida#ility of the haltin& pro#lem- arise from computa#ility theory! 7ut how e)actly can we determine what can #e computed with a computer> Godern computers are phenomen1 ally comple) machines! 8or e)ample- here is a hi&h1level model of the chipset for a mo#ile 'ntel processor= O'n1 telP
Godelin& each of these components is e)ceptionally tricky- and tryin& to devise any sort of proof a#out the cap1 a#ilities of such a machine would #e all #ut impossi#le! 'nstead- one approach is to work with automata- a#1 stract mathematical models of computin& machines Kthe sin&ular of automata is the plural of automatonL! Some types of automata are realiza#le in the physical world Kfor e)ample- deterministic and nondeterministic finite automata- as you'll see #elowL- while others are not! 8or e)ample- the Turing machine- which computer scient1 ists use as an overappro)imation of modern computers- re5uires infinite stora&e space- as does the weaker #ushdown automaton!
1 100 1
,lthou&h much of automata theory is purely theoretical- many automata have direct applications to software en1 &ineerin&! 8or e)ample- most production compilers simulate two particular types of automata Kcalled #ushdown automata and nondeterministic "inite automataL to analyze and convert source code into a form reada#le #y the compiler's semantic analyzer and code &enerator! Fe&ular e)pression matchers- which search throu&h te)t strin&s in search of patterned input- are also fre5uently implemented usin& an automaton called a deterministic "inite automaton! 'n this e)tended e)ample- we will introduce two types of automata- deterministic "inite automata and nondeterministic "inite automata- then e)plore how to represent them in C++! 0e'll also e)plore how these auto1 mata can #e used to simplify difficult strin&1matchin& pro#lems! 9eterministic !inite Automata Cerhaps the simplest form of an automaton is a deterministic "inite automaton- or 28,! ,t a hi&h1level- a 28, is similar to a flowchart ( it has a collection of states *oined #y various transitions that represent how the 28, should react to a &iven input! 8or e)ample- consider the followin& 28,=
start
.0 1 .! 1
0 . 1
." 0, 1
This 28, has four states- la#eled 50- 51- 5;- and 5A- and a set of la#eled transitions #etween those states! 8or e)1 ample- the state 50 has a transition la#eled + to 51 and a transition la#eled 1 to 5;! Some states have transitions to themselvesR for e)ample- 5; transitions to itself on a 1- while 5A transitions to itself on either a + or 1! "ote that as shorthand- the transition la#eled +F 1 indicates two different transitions- one la#eled with a + and one la#eled with a 1! The 28, has a desi&nated state state- in this case 50- which is indicated #y the arrow la#eled start! "otice that the state 5A has two rin&s around it! This indicates that 5A is an acce#ting state- which will have si&1 nificance in a moment when we discuss how the 28, processes input! Since all of the transitions in this 28, are la#eled either + or 1- this 28, is said to have an al#habet of a+- 1b! , 28, can use any nonempty set of sym#ols as an alpha#etR for e)ample- the atin or 3reek alpha#ets are per1 fectly accepta#le for use as alpha#ets in a 28,- as is the set of inte&ers #etween B; and 1A@! 7y definitionevery state in a 28, is re5uired to have a transition for each sym#ol in its alpha#et! 8or e)ample- in the a#ove 28,- each state has e)actly two transitions- one la#eled with a + and the other with a 1! "otice that state 5A has only one transition e)plicitly drawn- #ut #ecause the transition is la#eled with two sym#ols we treat it as two dif1 ferent transitions! The 28, is a simple computin& machine that accepts as input a strin& of characters formed from its alpha#etprocesses the strin&- and then halts #y either acce#ting the strin& or rejecting it! 'n essence- the 28, is a device for discriminatin& #etween two types of input ( input for which some criterion is true and input for which it is false! The 28, starts in its desi&nated start state- then processes its input character1#y1character #y transitionin& from its current state to the state indicated #y the transition! Hnce the 28, has finished consumin& its input- it accepts the strin& if it ends in an acceptin& stateR otherwise it re*ects the input!
1 101 1
To see e)actly how a 28, processes input- let us consider the a#ove 28, simulated on the input ++11! 'nitiallywe #e&in in the start state- as shown here=
start
.0 1 .! 1
0 . 1
." 0, 1 0 . 1
Since the first character of our strin& is a +- we follow the transition to state 51- as shown here=
start
.0 1 .! 1
." 0, 1
The second character of input is also a +- so we follow the transition la#eled with a + and end up #ack in state 51leavin& us in this state=
start
.0 1 .! 1
0 . 1
." 0, 1 0 . 1
"e)t- we consume the ne)t input character- a 1- which causes us to follow the transition la#eled 1 to state 5A=
start
.0 1 .! 1
." 0, 1
1 10; 1
The final character of input is also a 1- so we follow the transition la#eled 1 and end #ack up in 5A=
start
.0 1 .! 1
0 . 1
." 0, 1
0e are now done with our input- and since we have ended in an acceptin& state- the 28, accepts this input! 0e can similarly consider the action of this 28, on the strin& 111! 'nitially the machine will start in state 50then transition to state 5; on the first input! The ne)t two inputs each cause the 28, to transition #ack to state 5 ;so once the input is e)hausted the 28, ends in state 5;- so the 28, re*ects the input! 0e will not prove it here#ut this 28, accepts all strin&s that have at least one + and at least one 1! Two important details re&ardin& 28,s deserve some mention! 8irst- it is possi#le for a 28, to have multiple ac1 ceptin& states- as is the case in this 28,=
(ta rt
0 .0 0 . 1 0 0 ." 1
1 .!
,s with the previous 28,- this 28, has four states- #ut notice that three of them are marked as acceptin&! This leads into the second important detail re&ardin& 28,s ( the 28, only accepts its input if the 28, ends in an ac1 ceptin& state when it runs out o" in#ut! Simply transitionin& into an acceptin& state does not cause the 28, to accept! 8or e)ample- consider the effect of runnin& this 28, on the input +1+1! 0e #e&in in the start state- as shown here=
(ta rt
0 .0 0 . 1 0 0 ." 1
1 .!
Cha#ter G% E7tended E7am#le% Finite Automata 0e first consume a +- sendin& us to state 51=
1 10A 1
(ta rt
0 .0 0 . 1 0 0 ." 1
1 .!
(ta rt
0 .0 0 . 1 0 0 ." 1
1 .!
(ta rt
0 .0 0 . 1 0 0 ." 1
1 .!
(ta rt
0 .0 0 . 1 0 0 ." 1
1 .!
1 10B 1
Since we are out of input and are not in an acceptin& state- this 28, re*ects its input- even thou&h we transitioned throu&h every sin&le acceptin& state! 'f you want a fun challen&e- convince yourself that this 28, accepts all strin&s that contain an odd num#er of +s or an odd num#er of 1s Kinclusive HFL! 1e&resenting a 9!A , 28, is a simple model of computation that can easily #e implemented in software or hardware! 8or any 28,we need to store five pieces of information=Q 1! ;! A! B! ?! The set of states used #y the 28,! The 28,'s alpha#et! The start state! The state transitions! The set of acceptin& states!
Hf these five- the one that deserves the most attention is the fourth- the set of state transitions! Tisually- we have displayed these transitions as arrows #etween circles in the &raph! /owever- another way to treat state trans1 itions is as a ta#le with states alon& one a)is and sym#ols of the alpha#et alon& the other! 8or e)ample- here is a transition ta#le for the 28, descri#ed a#ove= State 50 51 5; 5A + 51 50 5A 5; 1 5; 5A 50 51
To determine the state to transition to &iven a current state and an input sym#ol- we look up the row for the cur1 rent state- then look at the state specified in the column for the current input! 'f we want to implement a pro&ram that simulates a 28,- we can represent almost all of the necessary informa1 tion simply #y storin& the transition ta#le! The two a)es encode all of the states and alpha#et sym#ols- and the entries of the ta#le represent the transitions! The information not stored in this ta#le is the set of acceptin& states and the desi&nated start state- so provided that we #undle this information with the ta#le we have a full descrip1 tion of a 28,! To concretely model a 28, usin& the ST - we must think of an optimal way to model the transition ta#le! Since transitions are associated with pairs of states and sym#ols- one option would #e to model the ta#le as an ST ma$ mappin& a state1sym#ol pair to a new state! 'f we represent each sym#ol as a *har and each state as an int Ki!e! 50 is 0- 51 is 1- etc!L- this leads to a state transition ta#le stored as a ma$ $air int, *har), int)! 'f we also track the set of acceptin& states as a set int)- we can encode a 28, as follows=
stru*t 9&A { map.pair.intB char>B int> transitions; set.int> acceptingStates; int startState; #;
Q 'n formal literature- a 28, is often characterized as a 5uintuple KN- c- 50- d- 8L of the states- alpha#et- start state- trans1 ition ta#le- and set of acceptin& states- respectively! Take CS1?B if you're interested in learnin& more a#out these won1 derful automata- or CS1BA if you're interested in their applications!
1 10? 1
8or the purposes of this e)ample- assume that we have a function which fills this 9&A struct will relevant data! "ow- let's think a#out how we mi&ht &o a#out simulatin& the 28,! To do this- we'll write a function 5imu, late9&A which accepts as input a 9&A stru*t and a strin& representin& the input- simulates the 28, when run on the &iven input- and then returns whether the input was accepted! 0e'll #e&in with the followin&=
bool SimulateDFU!DFUS dB string input$ { "2 ... 2" #
0e need to maintain the state we're currently in- which we can do #y storin& it in an int! 0e'll initialize the current state to the startin& state- as shown here=
bool 5imulate9&A(9&A? d, string in$ut) { int currState , d.startState; "2 ... 2" #
"ow- we need to iterate over the strin&- followin& transitions from state to state! Since the transition ta#le is rep1 resented as a ma$ from $air int, *har)s- we can look up the ne)t state #y usin& ma+eB$air to construct a pair of the current state and the ne)t input- then lookin& up its correspondin& value in the ma$! ,s a simplifyin& assumption- we'll assume that the input strin& is composed only of characters from the 28,'s alpha#et! This leads to the followin& code=
bool 5imulate9&A(9&A? d, string in$ut) { int *urr5tate = d.start5tate; or!string::iterator itr , input.begin!$; itr %, input.end!$; 33itr$ currState , d.transitions9ma+e&pair!currStateB Ritr$:; "2 ... 2" #
Hnce we've consumed all the input- we need to check whether we ended in an acceptin& state! 0e can do this #y lookin& up whether the *urr5tate varia#le is contained in the a**e$ting5tates set in the 9&A struct- as shown here=
bool 5imulate9&A(9&A? d, string in$ut) { int *urr5tate = d.start5tate; for(string33iterator itr = in$ut.begin(); itr 0= in$ut.end(); !!itr) *urr5tate = d.transitions[ma+eB$air(*urr5tate, 2itr)]; return d.acceptingStates. ind!currState$ %, d.acceptingStates.end!$; #
This function is remarka#ly simple #ut correctly simulates the 28, run on some input! ,s you'll see in the ne)t section on applications of 28,s- the simplicity of this implementation lets us harness 28,s to solve a suite of pro#lems surprisin&ly efficiently! A&&lications of 9!As The C++ string class e)ports a handful of searchin& functions Kfind- findBfirstBoffindBlastBnotBof- etc!L that are useful for locatin& specific strin&s or characters! /owever- it's surprisin&ly tricky to search strin&s for specific patterns of characters! The canonical e)ample is searchin& for email ad1 dresses in a strin& of te)t! ,ll email addresses have the same structure ( a name field followed #y an at si&n K[L
1 106 1
and a domain name! 8or e)ample- htiek9cs!stanford!edu and this!is!not!my!real!address9e)ample!com are val1 id email addresses! 'n &eneral- we can specify the formattin& of an email address as follows=Q
The name field- which consists of nonempty alphanumeric strin&s separated #y periods! Ceriods can only occur #etween alphanumeric strin&s- never #efore or after! Thus hello!world9e)ample!com and cp1 p!is!really!cool9e)ample!com are le&al #ut !oops9e)ample!com- oops!9e)ample!com- and oops!!oops9e)ample!com are not! The host field- which is structured similarly to the a#ove e)cept that there must #e at least two se5uences separated #y a dot!
"ow- suppose that we want to determine whether a strin& is a valid email address! Usin& the searchin& functions e)ported #y the string class this would #e difficult- #ut the pro#lem is easily solved usin& a 28,! 'n particu1 lar- we can desi&n a 28, over a suita#le alpha#et that accepts a strin& if and only if the strin& has the a#ove formattin&! The first 5uestion to consider is what alpha#et this 28, should #e over! 0hile we could potentially have the 28, operate over the entire ,SC'' alpha#et- it's easier if we instead &roup toðer related characters and use a simplified alpha#et! 8or e)ample- since email addresses don't distin&uish #etween letters and num#ers- we can have a sin&le sym#ol in our alpha#et that encodes any alphanumeric character! 0e would need to maintain the period and at1si&n in the alpha#et since they have semantic si&nificance! Thus our alpha#et will #e a a- .- Zbwhere a represents alphanumeric characters- . is the period character- and Z is an at1si&n! 3iven this alpha#et- we can represent all email addresses usin& the followin& 28,=
aB .B Z .B Z
.0 .!
. a .B Z .B Z Z
./
.B
.0
a a
."
.#
. a
.$
This 28, is considera#ly trickier than the ones we've encountered previously- so let's take some time to &o over what's happenin& here! The machine starts in state 50- which represents the #e&innin& of input! Since all email addresses have to have a nonempty name field- this state represents the #e&innin& of the first strin& in the name! The first character of an email address must #e an alphanumeric character- which if read in state 50 cause us to transition to state 51! States 51 and 5; check that the start of the input is somethin& appropriately formed from al1 phanumeric characters separated #y periods! Feadin& an alphanumeric character while in state 51 keeps the ma1 chine there Kthis represents a continuation of the current wordL- and readin& a dot transitions the machine to 5 ;! 'n 5;- readin& anythin& other than an alphanumeric character puts the machine into the 4trap state-6 state 5 @which represents that the input is invalid! "ote that once the machine reaches state 5 @ no input can &et the ma1 chine out of that state and that 5@ isn't acceptin&! Thus any input that &ets the machine into state 5@ will #e re*ec1 ted! State 5A represents the state of havin& read the at1si&n in the email address! /ere readin& anythin& other than an alphanumeric character causes the machine to enter the trap state!
Q This is a simplified version of the formattin& of email addresses! 8or a full specification- refer to F8Cs ?A;1 and ?A;;!
1 10@ 1
States 5B and 5? are desi&ned to help catch the name of the destination server! ike 51- 5B represents a state where we're readin& a 4word6 of alphanumeric characters and 5? is the state transitioned to on a dot! 8inally- state 56 represents the state where we've read at least one word followed #y a dot- which is the acceptin& state! ,s an e)1 ercise- trace the action of this machine on the inputs valid!address9email!com and invalid9not!com9ouch! "ow- how can we use this 28, in code> Suppose that we have some way to populate a 9&A struct with the in1 formation for this 28,! Then we could check if a strin& contains an email address #y convertin& each character in the strin& into its appropriate character in the 28, alpha#et- then simulatin& the 28, on the input! 'f the 28, re*ects the input or the strin& contains an invalid character- we can si&nal that the strin& is invalid- #ut otherwise the strin& is a valid email address! This can #e implemented as follows=
bool (sEmailAddress(string in$ut) { 9&A email4he*+er = 6oadEmail9&A(); "" (m$lemented else'here "2 ;ransform the string one *hara*ter at a time. 2" for(string33iterator itr = in$ut.begin(); itr 0= in$ut.end(); !!itr) { "2 isalnum is e7$orted b% **t%$e) and *he*+s if the in$ut is an 2 al$hanumeri* *hara*ter. 2" if(isalnum(2itr)) 2itr = AaA; "2 (f 'e donAt have al$hanumeri* data, 'e have to be a dot or at,sign or 2 the in$ut is invalid. 2" else if(2itr 0= A.A ?? 2itr 0= A[A) return false; # return 5imulate9&A(email4he*+er, in$ut); #
This code is remarka#ly concise- and provided that we have an implementation of 6oadEmail9&A the function will work correctly! ''ve left out the implementation of 6oadEmail9&A since it's somewhat tedious- #ut if you're determined to see that this actually works feel free to try your hand at implementin& it! <ondeterministic !inite Automata , &eneralization of the 28, is the nondeterministic "inite automaton- or "8,! ,t a hi&h level- 28,s and "8,s are 5uite similar ( they #oth consist of a set of states connected #y la#eled transitions- of which some states are desi&nated as acceptin& and others as re*ectin&! /owever- "8,s differ from 28,s in that a state in an "8, can have any num#er of transitions on a &iven input- includin& zero! 8or e)ample- consider the followin& "8,=
(t a
rt
. 0 .0 1 ."
.!
0, 1
.#
/ere- the start state is 50 and acceptin& states are 5; and 5B! "otice that the start state 50 has two transitions on + ( one to 51 and one to itself ( and two transitions on 1! ,lso- note that 5A has no defined transitions on +- and states 5; and 5B have no transitions at all!
1 10E 1
There are several ways to interpret a state havin& multiple transitions! The first is to view the automaton as choosin& one of the paths nondeterministically Khence the nameL- then acceptin& the input if some set of choices results in the automaton endin& in an acceptin& state! ,nother- more intuitive way for modelin& multiple trans1 itions is to view the "8, as #ein& in several different states simultaneously- at each step followin& every trans1 ition with the appropriate la#el in each of its current states! To see this- let's consider what happens when we run the a#ove "8, on the input ++11! ,s with a 28,- we #e&in in the start state- as shown here=
(t a
rt
. 0 .0 1 ."
.!
0, 1
.#
0e now process the first character of input K+L and find that there are two transitions to follow ( the first to 50 and the second to 51! The "8, thus ends up in #oth of these states simultaneously- as shown here=
(t a
rt
. 0 .0 1 ."
.!
0, 1
.#
"e)t- we process the second character K+L! 8rom state 50- we transition into 50 and 51- and from state 51 we trans1 ition into 5;! 0e thus end up in states 50- 51- and 5;- as shown here=
(t a
rt
. 0 .0 1 ."
.!
0, 1
.#
0e now process the third character of input- which is a 1! 8rom state 50 we transition to states 50 and 5A! 0e are also currently in states 51 and 5;- #ut neither of these states has a transition on a 1! 0hen this happens- we simply drop the states from the set of current states! Conse5uently- we end up in states 50 and 5A- leavin& us in the followin& confi&uration=
(t a
rt
. 0 .0 1 ."
.!
0, 1
.#
1 10< 1
8inally- we process the last character- a 1! State 50 transitions to 50 and 51- and state 51 transitions to state 5B! 0e thus end up in this final confi&uration=
(t a
rt
. 0 .0 1 ."
.!
0, 1
.#
Since the "8, ends in a confi&uration where at least one of the active states is an acceptin& state K5 BL- the "8, accepts this input! ,&ain as an e)ercise- you mi&ht want to convince yourself that this "8, accepts all and only the strin&s that end in either ++ or 11! Im&lementing an <!A Fecall from a#ove the definition of the 9&A struct=
stru*t 9&A { ma$ $air int, *har), int) transitions; set int) a**e$ting5tates; int start5tate; #;
/ere- the transition ta#le was encoded as a ma$ $air int, *har), int) since for every com#ination of a state and an alpha#et sym#ol there was e)actly one transition! To &eneralize this to represent an "8,- we need to #e a#le to associate an ar#itrary num#er of possi#le transitions! This is an ideal spot for an ST multima$which allows for duplicate keyIvalue pairs! This leaves us with the followin& definition for an "8, type=
stru*t E&A { multimap $air int, *har), int) transitions; set int) a**e$ting5tates; int start5tate; #;
/ow would we &o a#out simulatin& this "8,> ,t any &iven time- we need to track the set of states that we are currently in- and on each input need to transition from the current set of states to some other set of states! , nat1 ural representation of the current set of states is Khopefully unsurprisin&lyL as a set int)! 'nitially- we start with this set of states *ust containin& the start state! This is shown here=
bool Simulate/FU!/FUS n aB string input$ * TR Trac+ our set o states. =e begin in the start state. RT set.int> currStates; currStates.insert!n a.startState$; 4 "2 ... 2"
"e)t- we need to iterate over the strin& we've received as input- followin& transitions where appropriate! This at least re5uires a simple for loop- which we'll write here=
1 110 1
bool 5imulateE&A(E&A? nfa, string in$ut) { "2 ;ra*+ our set of states. 1e begin in the start state. 2" set int) *urr5tates; *urr5tates.insert(nfa.start5tate); * 4 # or!string::iterator itr , input.begin!$; itr %, input.end!$; 33itr$ "2 ... 2"
"ow- for each character of input in the strin&- we need to compute the set of ne)t states Kif anyL to which we should transition! To simplify the implementation of this function- we'll create a second set int) correspond1 in& to the ne)t set of states the machine will #e in! This eliminates pro#lems caused #y addin& elements to our set of states as we're iteratin& over the set and updatin& it! 0e thus have
bool 5imulateE&A(E&A? nfa, string in$ut) { "2 ;ra*+ our set of states. 1e begin in the start state. 2" set int) *urr5tates; *urr5tates.insert(nfa.start5tate); for(string33iterator itr = in$ut.begin(); itr 0= in$ut.end(); !!itr) { set.int> ne#tStates; "2 ... 2" # "2 ... 2" #
"ow that we have space to put the ne)t set of machine states- we need to fi&ure out what states to transition to! Since we may #e in multiple different states- we need to iterate over the set of current states- computin& which states they transition into! This is shown here=
bool 5imulateE&A(E&A? nfa, string in$ut) { "2 ;ra*+ our set of states. 1e begin in the start state. 2" set int) *urr5tates; *urr5tates.insert(nfa.start5tate); for(string33iterator itr = in$ut.begin(); itr 0= in$ut.end(); !!itr) { set int) ne7t5tates; or!set.int>::iterator state , currStates.begin!$; state %, currStates.end!$; 33state$ * "2 ... 2" 4 # # "2 ... 2"
1 111 1
3iven the state #ein& iterated over #y state and the current input character- we now want to transition to each state indicated #y the multima$ stored in the E&A struct! 'f you'll recall- the ST multima$ e)ports a function called eVualBrange which returns a $air of iterators into the multima$ that delineate the ran&e of elements with the specified key! This function is e)actly what we need to determine the set of new states we'll #e enterin& for each &iven state ( we simply 5uery the multima$ for all elements whose key is the pair of the specified state and the current input- then add all of the destination states to our ne)t set of states! This is shown here=
bool 5imulateE&A(E&A? nfa, string in$ut) { "2 ;ra*+ our set of states. 1e begin in the start state. 2" set int) *urr5tates; *urr5tates.insert(nfa.start5tate); for(string33iterator itr = in$ut.begin(); itr 0= in$ut.end(); !!itr) { set int) ne7t5tates; for(set int)33iterator state = *urr5tates.begin(); state 0= *urr5tates.end(); !!state) { TR Fet all states that 6e transition to rom this current state. RT pair.multimap.pair.intB char>B int>::iteratorB multimap.pair.intB char>B int>::iterator> transitions , n a.transitions.eGual&range!ma+e&pair!RstateB Ritr$$; TR Udd these ne6 states to the ne#tStates set. RT or!; transitions. irst %, transitions.second; 33transitions. irst$ TR transitions. irst is the current iteratorB and its second ield R is the )alue !ne6 state$ in the ST1 multimap. RT ne#tStates.insert!transitions. irst->second$; # # #
8inally- once we've consumed all input- we need to check whether the set of states contains any states that are also in the set of acceptin& states! 0e can do this #y simply iteratin& over the set of current states- then checkin& if any of them are in the acceptin& set! This is shown here and completes the implementation of the function=
1 11; 1
bool 5imulateE&A(E&A? nfa, string in$ut) { "2 ;ra*+ our set of states. 1e begin in the start state. 2" set int) *urr5tates; *urr5tates.insert(nfa.start5tate); for(string33iterator itr = in$ut.begin(); itr 0= in$ut.end(); !!itr) { set int) ne7t5tates; for(set int)33iterator state = *urr5tates.begin(); state 0= *urr5tates.end(); !!state) { "2 Get all states that 'e transition to from this *urrent state. 2" $air multima$ $air int, *har), int)33iterator, multima$ $air int, *har), int)33iterator) transitions = nfa.transitions.eVualBrange(ma+eB$air(2state, 2itr)); "2 Add these ne' states to the ne7t5tates set. 2" for(; transitions.first 0= transitions.se*ond; !!transitions.first) "2 transitions.first is the *urrent iterator, and its se*ond field 2 is the value (ne' state) in the 5;6 multima$. 2" ne7t5tates.insert(transitions.first,)se*ond);
# #
or!set.int>::iterator itr , currStates.begin!$;itr %, currStates.end!$; 33itr$ i !n a.acceptingStates.count!Ritr$$ return true; return alse;
Compare this function to the implementation of the 28, simulation! There is su#stantially more code heresince we have to track multiple different states rather than *ust a sin&le state! /owever- this e)tra comple)ity is counter#alanced #y the simplicity of desi&nin& "8,s compared to 28,s! 7uildin& a 28, to match a &iven pat1 tern can #e much trickier than #uildin& an e5uivalent "8, #ecause it's difficult to model 4&uessin&6 #ehavior with a 28,! /owever- #oth functions are a useful addition to your pro&rammin& arsenal- so it's &ood to see how they're implemented! #ore to $%&lore This e)tended e)ample introduced the 28, and "8, and demonstrated how to implement them- #ut didn't con1 sider some of the more interestin& al&orithms that operate on them! 'f you're interested in an al&orithmic and im1 plementation challen&e- consider lookin& into the followin&= Subset Construction= 28,s and "8,s are e5uivalently powerful- meanin& that for every "8, that is an 28, that accepts and re*ects the same inputs and vice1versa! The proof of this fact hin&es on the subset construction- a way of transformin& an "8, into an e5uivalent 28,! The construction is remarka#ly simple and ele&ant and would #e a &reat way to play around with the ST ! 9!A #inimi3ation= Gultiple 28,s can accept and re*ect precisely the same sets of input! ,s an e)1 treme case- any 28, where no state is an acceptin& state is e5uivalent to a one1state 28, that does not have any acceptin& states- since neither can possi#ly accept any inputs! 'nterestin&ly- for every 28,there is a uniAue 28, with the fewest num#er of states that is e5ual to that 28,! There is a rather strai&htforward al&orithm for eliminatin& redundant states in a 28, that is an e)cellent way to practice your ST skillsR consult 0ikipedia or a te)t on automata theory KIntroduction to Automata Theory, Languages, and Com#utation, Third Edition #y /opcroft- Gotwani- and Ullman is an e)cellent place to lookL!
The ST al&orithms are an amazin& collection of template functions that work on ran&es defined #y iterators! They encompass a wide scope of functionality- ena#lin& you to levera&e heavily off pree)istin& code! 0hile ST al&orithms do not introduce any new functionality to your code- they nonetheless provide incredi#le speed #onuses durin& desi&n and at runtime! /owever- ST al&orithms can #e complicated and re5uire some time to ad*ustin& to! This chapter discusses the #asic synta) for ST al&orithms- some of their pro&rammin& conven1 tions- and the more common al&orithms you'll encounter in practice! A /ord on Com&iler $rrors Gore so than with any other portion of the ST - when workin& with ST al&orithms the compiler errors you will encounter can #e completely incomprehensi#le! 0hen you pass invalid parameters to an ST al&orithm- the compiler may report errors in the code for the ST al&orithms rather than in the code you wrote! 'f this happenssearch the errors carefully and look for messa&es like 4durin& template instantiation of6 or 4while instantiatin&!6 These phrases identify the spot where the compiler choked on your code! Fead over your code carefully and make sure that your parameters are valid! 2id you provide an output iterator instead of an input iterator> 2oes your comparison function accept the ri&ht parameter types> 2ecipherin& these types of errors is a skill you can only learn with time! A /ord on 0eader !iles ST al&orithms are in either the algorithm) or numeri*) header files! ,l&orithms from numeri*) tend to #e #ased more on computational pro&rammin&- while al&orithms from algorithm) tend to #e more &ener1 al1purpose! 't's common to see #oth headers at the top of professional code- so don't #e confused #y the nu, meri*)! 'f you receive compiler errors a#out undefined functions- make sure you've included #oth these head1 ers! ,asic STL Algorithms The #est way to understand ST al&orithms is to see them in action! 8or e)ample- the followin& code snippet prints the sum of all of the elements in a set int)=
*out accumulate!mySet.begin!$B mySet.end!$B -$ endl;
'n that tiny line of code- we're iteratin& over the entire set and summin& the values toðer! This e)emplifies ST al&orithms ( performin& hu&e tasks in a tiny space! The a**umulate function- defined in the numeri*) header- takes three parameters ( two iterators that define a ran&e of elements and an initial value to use in the summation ( and returns the sum of the elements in that ran&e plus the #ase value!Q "otice that nothin& a#out this function re5uires that the elements #e in a set ( you could *ust as well pass in iterators from a ve*tor- or even istreamBiterator int)s! ,lso note that there's no re5uirement that the iterators define the ran&e of an entire container! 8or e)ample- here's code to print the sum of all the elements in a set int) #etween B; and 1A@- inclusive=
*out a**umulate(mySet.lo6er&bound!J8$, mySet.upper&bound!7DE$, 0) endl;
Q There is also a version of a**umulate that accepts four parameters- as you'll see in the chapter on functors!
1 11B 1
7ehind the scenes- a**umulate is implemented as a template function that accepts two iterators and simply uses a loop to sum toðer the values! /ere's one possi#le implementation of a**umulate=
tem$late t%$ename (n$ut(terator, t%$ename ;%$e) inline ;%$e a**umulate((n$ut(terator start, (n$ut(terator sto$, ;%$e initial) { 6hile!start %, stop$ * initial 3, Rstart; 33start; 4 return initial; #
0hile some of the synta) specifics mi&ht #e a #it confusin& Knota#ly the template header and the inline keywordL- you can still see that the heart of the code is *ust a standard iterator loop that continues advancin& the start iterator forward until it reaches the destination! There's nothin& ma&ic a#out a**umulate- and the fact that the function call is a sin&le line of code doesn't chan&e the fact that it still uses a loop to sum all the values to1 ðer! 'f ST al&orithms are *ust functions that use loops #ehind the scenes- why even #other with them> There are several reasons- the first of which is sim#licity! 0ith ST al&orithms- you can levera&e off of code that's already #een written for you rather than reinventin& the code from scratch! This can #e a &reat time1saver and also leads into the second reason- correctness! 'f you had to rewrite all the al&orithms from scratch every time you needed to use them- odds are that at some point you'd slip up and make a mistake! .ou mi&ht- for e)ample- write a sort1 in& routine that accidentally uses when you meant ) and conse5uently does not work at all! "ot so with the ST al&orithms ( they've #een thorou&hly tested and will work correctly for any &iven input! The third reason to use al&orithms is s#eed! 'n &eneral- you can assume that if there's an ST al&orithm that performs a task- it's &o1 in& to #e faster than most code you could write #y hand! Throu&h advanced techni5ues like template specializa1 tion and template metapro&rammin&- ST al&orithms are transparently optimized to work as fast as possi#le! 8i1 nally- ST al&orithms offer clarity! 0ith al&orithms- you can immediately tell that a call to a**umulate adds up num#ers in a ran&e! 0ith a for loop that sums up values- you'd have to read each line in the loop #efore you understood what the code did! Algorithm <aming Con7entions There are over fifty ST al&orithms and memorizin& them all would #e a chore- to say the least! 8ortunatelymany of them have common namin& conventions so you can reco&nize al&orithms even if you've never en1 countered them #efore! The suffi) Bif on an al&orithm Kre$la*eBif- *ountBif- etc!L means the al&orithm will perform a task on ele1 ments only if they meet a certain criterion! 8unctions endin& in Bif re5uire you to pass in a predicate function that accepts an element and returns a bool indicatin& whether the element matches the criterion! 8or e)amplethe ST *ount al&orithm accepts a ran&e of iterators and a value- then returns the num#er of times that the value appears in that ran&e! *ountBif- on the other hand- accepts a ran&e of iterators and a predicate and returns the num#er of times the predicate evaluates to true in that ran&e! ,l&orithms containin& the word *o$% KremoveB*o$%- $artialBsortB*o$%- etc!L will perform some task on a ran&e of data and store the result in the location pointed at #y an e)tra iterator parameter! 0ith *o$% functionsyou'll specify all the normal data for the al&orithm plus an e)tra iterator specifyin& a destination for the result! 0e'll cover what this means from a practical standpoint later!
1 11? 1
'f an al&orithm ends in Bn KfillBnF generateBn- etcB- then it will perform a certain operation n times! These functions are useful for cases where the num#er of times you perform an operation is meanin&ful- rather than the ran&e over which you perform it! 1eordering Algorithms There are a lar&e assortment of ST al&orithms at your disposal- so for this chapter it's useful to discuss the dif1 ferent al&orithms in terms of their #asic functionality! The first ma*or &roupin& of al&orithms we'll talk a#out are the reordering algorithms- al&orithms that reorder #ut preserve the elements in a container! Cerhaps the most useful of the reorderin& al&orithms is sort- which sorts elements in a ran&e in ascendin& order! 8or e)ample- the followin& code will sort a ve*tor int) from lowest to hi&hest=
sort!my5ector.begin!$B my5ector.end!$$; sort re5uires that the iterators you pass in #e random1access iterators- so you cannot use sort to sort a ma$ or set! /owever- since ma$ and set are always stored in sorted order- this shouldn't #e a pro#lem!
7y default- sort uses the operator for whatever element types it's sortin&- #ut you can specify a different com1 parison function if you wish! 0henever you write a comparison function for an ST al&orithm- it should accept two parameters representin& the elements to compare and return a bool indicatin& whether the first element is strictly less than the second element! 'n other words- your call#ack should mimic the operator! "ote that this is different than the comparison functions you've seen in CS1067IJ- which return an int specifyin& the relation #etween the two elements! 8or e)ample- suppose we had a ve*tor $la*e;)- where $la*e; was defined as
stru*t $la*e; { int 7; int %; #;
Then we could sort the ve*tor only if we wrote a comparison function for $la*e;s!Q 8or e)ample=
bool 4om$are<la*es($la*e; one, $la*e; t'o) { if(one.7 0= t'o.7) return one.7 t'o.7; return one.% t'o.%; # sort(m%<la*eDe*tor.begin(), m%<la*eDe*tor.end(), ;omparePlaces);
.ou can also use custom comparison functions even if a default already e)ists! 8or e)ample- here is some code that sorts a ve*tor string) #y len&th- i&norin& whether the strin&s are in alpha#etical order=
bool 4om$are5tring6ength(string one, string t'o) { return one.length() t'o.length(); # sort(m%De*tor.begin(), m%De*tor.end(), ;ompareString1ength); Q 0hen we cover operator overloadin& in the second half of this te)t- you'll see how to create functions that sort will use automatically!
1 116 1
Hne last note on comparison functions is that they should either accept the parameters #y value or #y 4reference to *onst!6 Since we haven't covered *onst yet- for now your comparison functions should accept their para1 meters #y value! Htherwise you can &et some pretty ferocious compiler errors! ,nother useful reorderin& function is randomBshuffle- which randomly scram#les the elements of a contain1 er!Q 7ecause the scram#lin& is random- there's no need to pass in a comparison function! /ere's some code that uses randomBshuffle to scram#le a ve*tor's elements=
random&shu le!my5ector.begin!$B my5ector.end!$$;
,s with sort- the iterators must #e random1access iterators- so you can't scram#le a set or ma$! Then a&ainsince they're sorted containers- you shouldn't want to do this in the first place! The last ma*or al&orithm in this cate&ory is rotate- which cycles the elements in a container! 8or e)ample- &iv1 en the input container K0- 1- ;- A- B- ?L- rotatin& the container around position A would result in the container K;A- B- ?- 0- 1L! The synta) for rotate is anomalous in that it accepts three iterators delineatin& the ran&e and the new front- #ut in the order begin- middle- end! 8or e)ample- to rotate a ve*tor around its third position- we would write
rotate(v.begin(), ).begin!$ 3 8, v.end());
Searching Algorithms Commonly you're interested in checkin& mem#ership in a container! 8or e)ample- &iven a ve*tor- you mi&ht want to know whether or not it contains a specific element! 0hile the ma$ and set naturally support findve*tors and deVues lack this functionality! 8ortunately- you can use ST al&orithms to correct this pro#lem! To search for an element in a container- you can use the find function! find accepts two iterators delineatin& a ran&e and a value- then returns an iterator to the first element in the ran&e with that value! 'f nothin& in the ran&e matches- find returns the second iterator as a sentinel! 8or e)ample=
if( ind!my5ector.begin!$B my5ector.end!$B 7DE$ 0= m%De*tor.end()) "2 ... ve*tor *ontains 8FG ... 2"
,lthou&h you can le&ally pass ma$ and set iterators as parameters to find- you should avoid doin& so! 'f a container class has a mem#er function with the same name as an ST al&orithm- you should use the mem#er function instead of the al&orithm #ecause mem#er functions can use information a#out the container's internal data representation to work much more 5uickly! ,l&orithms- however- must work for all iterators and thus can't make any optimizations! ,s an e)ample- with a set containin& one million elements- the set's find mem#er function can locate elements in around twenty steps usin& #inary search- while the ST find function could take up to one million steps to linearly iterate over the entire container! That's a sta&&erin& difference and really should hit home how important it is to use mem#er functions over ST al&orithms! $ust as a sorted ma$ and set can use #inary search to outperform the linear ST find al&orithm- if you have a sorted linear container Kfor e)ample- a sorted ve*torL- you can use the ST al&orithm binar%Bsear*h to per1 form the search in a fraction of the time! 8or e)ample=
"2 Assume m%De*tor is sorted. 2" if(binary&search(m%De*tor.begin(), m%De*tor.end(), 8FG)) { "2 ... &ound 8FG ... 2" #
Q 'nternally- randomBshuffle calls the C li#rary function rand to reorder the elements! Thus you should seed the random1 izer usin& srand #efore callin& randomBshuffle!
1 11@ 1
,lso- as with sort- if the container is sorted usin& a special comparison function- you can pass that function in as a parameter to binar%Bsear*h! /owever- make sure you're consistent a#out what comparison function you use- #ecause if you mi) them up binar%Bsear*h mi&ht not work correctly! "ote that binar%Bsear*h doesn't return an iterator to the element ( it simply checks to see if it's in the contain1 er! 'f you want to do a #inary search in order to &et an iterator to an element- you can use the lo'erBbound al1 &orithm which- like the ma$ and set lo'erBbound functions- returns an iterator to the first element &reater than or e5ual to the specified value! "ote that lo'erBbound mi&ht hand #ack an iterator to a different element than the one you searched for if the element isn't in the ran&e- so #e sure to check the return value #efore usin& it! ,s with binar%Bsear*h- the container must #e in sorted order for lo'erBbound al&orithm to work correctly! Set Algorithms 'f you'll recall- the set container class does not support intersection- union- or su#set! /owever- the ST al1 &orithms provide a useful set of functions that can perform these operations! Unlike the CS1067IJ 5et func1 tions- the ST al&orithms will work on any sorted container type- not *ust a set- so you can intersect two ve*, tors- a set and a deVue- or even two ma$s! Set al&orithms need a place to store the result of the set operations- which you specify #y providin& an iterator to the start of the ran&e that will hold the new values! 7e careful- thou&h- #ecause this destination must have enou&h space to store the result of the operation or the al&orithm will write out of #ounds! Unfortunatelythou&h- this forms a sort of circular dependency ( you need to have enou&h space availa#le to store the result of the al&orithm #efore invokin& the al&orithm- #ut you can't know how much space you need until you've run the al&orithmU To #reak this cycle- you can use iterator adapters in con*unction with the set al&orithms! 'f you want to take the union of two sets- rather than preallocatin& enou&h space for the result- *ust specify an insertBiterator as the destination iterator and the container storin& the result of the operation will automat1 ically &row to contain all of the resultin& elements! There are four ma*or set al&orithms= setBunion- which computes the union of two setsR setBinterse*tionwhich computes the intersectionR setBdifferen*e- which creates a set of all elements in the first container e)1 cept for those in the second container- and setBs%mmetri*Bdifferen*e- the set of all elements in either the first or second container #ut not #oth! 7elow is some code showcasin& setBunion- althou&h any of the a#ove four functions can #e su#stituted in=
set int) result; set&union!set<ne.begin!$B set<ne.end!$B "" All of the elements in set=ne setT6o.begin!$B setT6o.end!$B "" All of the elements in set;'o inserter!resultB result.begin!$$); "" 5tore in result.
Two other useful al&orithms are in*ludes and eVual! in*ludes accepts two sorted iterator ran&es and re1 turns whether all elements in the second ran&e are contained in the first ran&eR this is e5uivalent to testin& wheth1 er one ran&e is a su#set of another! The eVual al&orithm takes in three iterators ( one ran&e of iterators and an iterator to the #e&innin& of a different ran&e ( and returns whether the ran&es are e5ual! 7e careful when usin& eVual ( the al&orithm e)pects the ran&es to have the same num#er of elements and will read &ar#a&e data if there isn't enou&h room in the second se5uenceU /ere's code demonstratin& in*ludes and eVuals=
if(includes!set<ne.begin!$B set<ne.end!$B setT6o.begin!$B setT6o.end!$$) "2 ... set;'o is a subset of set=ne ... 2" if(set=ne.siUe() == set;'o.siUe () ?? eGual!set<ne.begin!$B set<ne.end!$B setT6o.begin!$$) "2 ... set=ne == set;'o ... 2"
The ST provides several al&orithms for removin& elements from containers! /owever- removal al&orithms have some idiosyncrasies that can take some time to ad*ust to! 2espite their name- removal al&orithms do not actually remove elements from containers! This is somewhat counterintuitive #ut makes sense when you think a#out how al&orithms work! ,l&orithms accept iterators- not containers- and thus do not know how to erase elements from containers! Femoval functions work #y shufflin& down the contents of the container to overwrite all elements that need to #e erased! Hnce finished- they return iterators to the first element not in the modified ran&e! So for e)ample- if you have a ve*tor initialized to 0- 1;- A- A- A- B and then remove all instances of the num#er A- the resultin& ve*tor will contain 0- 1- ;- B- A- A- B and the function will return an iterator to one spot past the first B! 'f you'll notice- the elements in the iterator ran&e startin& at begin and endin& with the element one past the four are the se5uence 0- 1- ;- B ( e)actly the ran&e we wanted! To truly remove elements from a container with the removal al&orithms- you can use the container class mem#er function erase to erase the ran&e of values that aren't in the result! 8or e)ample- here's a code snippet that re1 moves all copies of the num#er 1A@ from a ve*tor=
m%De*tor.erase(remo)e!my5ector.begin!$B my5ector.end!$B 7DE$, m%De*tor.end());
"ote that we're erasin& elements in the ran&e O2- endL- where 2 is the value returned #y the remove al&orithm! There is another useful removal function- removeBif- that removes all elements from a container that satisfy a condition specified as the final parameter! 8or e)ample- usin& the is$un*t function from the header file **t%$e)- we can write a 5tri$<un*tuation function that returns a copy of a strin& with all the punctuation removed=Q
string 5tri$<un*tuation(string in$ut) { in$ut.erase(remo)e&i !input.begin!$B input.end!$B ispunct$, in$ut.end()); return in$ut; #
K'sn't it amazin& how much you can do with a sin&le line of code> That's the real #eauty of ST al&orithms!L 'f you're shaky a#out how to actually remove elements in a container usin& remove- you mi&ht want to consider the removeB*o$% and removeB*o$%Bif al&orithms! These al&orithms act *ust like remove and removeBife)cept that instead of modifyin& the ori&inal ran&e of elements- they copy the elements that aren't removed into another container! 0hile this can #e a #it less memory efficient- in some cases it's e)actly what you're lookin& for! #iscellaneous Algorithms There are a num#er of other useful al&orithms- of which three- *o$%- forBea*h and transform- we'll cover in this section!
copy accepts three parameters ( a ran&e of iterators delineatin& an input and an iterator specifyin& a destina1
tion ( then copies the elements from the input ran&e into the destination! ,s with the set al&orithms- the destina1 tion iterator must point to the start of a ran&e capa#le of holdin& all of the specified elements! 8or e)ample-
Q Hn some compilers- this code will not compile as written! See the later section on compati#ility issues for more inform1 ation!
1 11< 1
here's a code snippet showin& how to overwrite the #e&innin& of a ve*tor with elements in a set that are &reat1 er than or e5ual to 1A@=
copy!mySet.lo6er&bound!7DE$B mySet.end!$B my5ector.begin!$$; for_each accepts as input an input ran&e and a call#ack function- then calls the function on each element in the ran&e! "ote that forBea*h doesn't modify the values of the elements in the ran&eR rather it simply mimics an iterator loop that calls a function for each element in the ran&e! Currently- forBea*h mi&ht not seem all that
useful- especially since the function to call must take e)actly one parameter- #ut when we cover functors in the second half of this #ook forBea*h will #ecome an invalua#le weapon in your C++ arsenal! /ere is some code that iterates over a ve*tor int) and prints out each element=
void <rint5ingle(nt(int value) { *out value endl; # or&each!my5ector.begin!$B my5ector.end!$B PrintSingleInt$;
,nother useful function is transform- which applies a function to a ran&e of elements and stores the result in the specified destination! transform accepts four parameters ( two iterators delineatin& an input ran&e- an out1 put iterator specifyin& a destination- and a call#ack function- then stores in the output destination the result of ap1 plyin& the function to each element in the input ran&e! ,s with the set al&orithms- transform assumes that there is sufficient stora&e space in the ran&e pointed at #y the destination iterator- so make sure that you have sufficient space #efore transformin& a ran&e!
transform is particularly ele&ant when com#ined with functors- #ut even without them is useful for a whole ran&e of tasks! 8or e)ample- consider the tolo'er function- a C li#rary function declared in the header **t%$e) that accepts a *har and returns the lowercase representation of that character! Com#ined with transform- this lets us write 4onvert;o6o'er4ase from strutils.h in two lines of code- one of which is a return statement=Q string 4onvert;o6o'er4ase(string te7t) { trans orm!te#t.begin!$B te#t.end!$B te#t.begin!$B tolo6er$; return te7t; #
"ote that after specifyin& the ran&e te7t.begin()- te7t.end() we have another call to te7t.begin()! This is #ecause we need to provide an iterator that tells transform where to put its output! Since we want to overwrite the old contents of our container with the new values- we specify te7t.begin() another time to in1 dicate that transform should start writin& elements to the #e&innin& of the strin& as it &enerates them! There is no re5uirement that the function you pass to transform return elements of the same type as those stored in the container! 't's le&al to transform a set of strings into a set of doubles- for e)ample! The followin& ta#le lists some of the more common ST al&orithms! 't's #y no means an e)haustive list- and you should consult a reference to &et a complete list of all the al&orithms availa#le to you!
Q ,&ain- see the later section on compati#ility issues if you're &ettin& compiler errors when writin& this code!
1 1;0 1
;%$e a**umulate((n$ut(tr start, (n$ut(tr sto$, ;%$e value)
bool binar%Bsear*h(Candom(tr start, Cerforms #inary search on the sorted ran&e specified #y Ostart- sto$L Candom(tr sto$, and returns whether it finds the element value! 'f the elements are *onst ;%$e? value)
sorted usin& a special comparison function- you must specify the func1 tion as the final parameter! Copies the elements in the ran&e Ostart- sto$L into the output ran&e startin& at out$ut5tart! *o$% returns an iterator to one past the end of the ran&e written to! Feturns the num#er of elements in the ran&e O start- sto$L e5ual to value!
=ut(tr *o$%((n$ut(tr start, (n$ut(tr sto$, =ut(tr out$ut5tart) siUeBt *ount((n$ut(tr start, (n$ut(tr end, *onst ;%$e? value)
siUeBt *ountBif((n$ut(tr start, Feturns the num#er of elements in the ran&e Ostart- sto$L for which (n$ut(tr end, fn returns true! Useful for determinin& how many elements have a <redi*ate&un*tion fn)
certain property!
bool eVual((n$ut(tr start8, (n$ut(tr sto$8, (n$ut(tr startH) $air Candom(tr, Candom(tr) eVualBrange(Candom(tr start, Candom(tr sto$, *onst ;%$e? value)
Feturns whether elements contained in the ran&e defined #y Ostart8- sto$8L and the ran&e #e&innin& with start; are eVual! 'f you have a special comparison function to compare two elements- you can specify it as the final parameter! Feturns two iterators as a $air that defines the su#1ran&e of elements in the sorted ran&e Ostart- sto$L that are e5ual to value! 'n other words- every element in the ran&e defined #y the returned iterators is e5ual to value! .ou can specify a special comparison function as a fi1 nal parameter! Sets every element in the ran&e Ostart- sto$L to value! Sets the first num elements- startin& at start- to value! Feturns an iterator to the first element in Ostart- sto$L that is e5ual to value- or sto$ if the value isn't found! The ran&e doesn't need to #e sorted! Feturns an iterator to the first element in Ostart- sto$L for which fn is true- or sto$ otherwise! Calls the function fn on each element in the ran&e Ostart- sto$L! Calls the zero1parameter function fn once for each element in the ran&e Ostart- sto$L- storin& the return values in the ran&e! Calls the zero1parameter function fn n times- storin& the results in the ran&e #e&innin& with start! Feturns whether every element in the sorted ran&e OstartH- sto$HL is also in Ostart8- sto$8L! 'f you need to use a special comparison function- you can specify it as the final parameter!
void fill(&or'ard(tr start, &or'ard(tr sto$, *onst ;%$e? value) void fillBn(&or'ard(tr start, siUeBt num, *onst ;%$e? value) (n$ut(tr find((n$ut(tr start, (n$ut(tr sto$, *onst ;%$e? value) (n$ut(tr findBif((n$ut(tr start, (n$ut(tr sto$, <redi*ate&un* fn) &un*tion forBea*h((n$ut(tr start, (n$ut(tr sto$, &un*tion fn) void generate(&or'ard(tr start, &or'ard(tr sto$, Generator fn); void generateBn(=ut$ut(tr start, siUeBt n, Generator fn); bool in*ludes((n$ut(tr (n$ut(tr (n$ut(tr (n$ut(tr start8, sto$8, startH, sto$H)
1 1;1 1
Feturns whether the ran&e of elements defined #y Os8- sHL is le)ico1 &raphically less than Ot8- tHLR that is- if the first ran&e precedes the second in a 4dictionary orderin&!6 Feturns an iterator to the first element &reater than or e5ual to the ele1 ment elem in the sorted ran&e Ostart- sto$L! 'f you need to use a special comparison function- you can specify it as the final parameter! Feturns an iterator to the lar&est value in the ran&e Ostart- sto$L! 'f you need to use a special comparison function- you can specify it as the final parameter! Feturns an iterator to the smallest value in the ran&e O start- sto$L! 'f you need to use a special comparison function- you can specify it as the final parameter!
bool ne7tB$ermutation(Oidir(tr start, 3iven a ran&e of elements Ostart- sto$L- modifies the ran&e to con1 Oidir(tr sto$) tain the ne)t le)ico&raphically hi&her permutation of those elements!
The function then returns whether such a permutation could #e found! 't is common to use this al&orithm in a do ... 'hile loop to iter1 ate over all permutations of a ran&e of data- as shown here=
sort(range.begin(), range.end()); do { "2 ... $ro*ess ... 2" # 'hile(ne#t&permutation!range.begin!$B range.end!$$); bool $revB$ermutation(Oidir(tr start, 3iven a ran&e of elements Ostart- sto$L- modifies the ran&e to con1 Oidir(tr sto$) tain the ne)t le)ico&raphically lower permutation of those elements!
Fandomly reorders the elements in the ran&e Ostart- sto$L! Femoves all elements in the ran&e Ostart- sto$L that are e5ual to value! This function will not remove elements from a container! To shrink the container- use the container's erase function to erase all values in the ran&e OretDalue- end()L- where retDalue is the return value of remove! Femoves all elements in the ran&e Ostart- sto$L for which fn returns true! See remove for information a#out how to actually remove ele1 ments from the container!
void re$la*e(&or'ard(tr start, Feplaces all values in the ran&e Ostart- sto$L that are e5ual to toCe, &or'ard(tr sto$, $la*e with re$la*e1ith! *onst ;%$e? toCe$la*e, *onst ;%$e? re$la*e1ith) void re$la*eBif(&or'ard(tr start, Feplaces all elements in the ran&e Ostart- sto$L for which fn returns &or'ard(tr sto$, true with the value 'ith! <redi*ate&un*tion fn, *onst ;%$e? 'ith) &or'ard(tr rotate(&or'ard(tr start, &or'ard(tr middle, &or'ard(tr sto$)
Fotates the elements of the container such that the se5uence Omiddle- sto$L is at the front and the ran&e Ostart- middleL &oes from the new middle to the end! rotate returns an iterator to the new position of start!
1 1;; 1
&or'ard(tr sear*h(&or'ard(tr &or'ard(tr &or'ard(tr &or'ard(tr start8, sto$8, startH, sto$H)
(n$ut(tr setBdifferen*e( (n$ut(tr start8, (n$ut(tr sto$8, (n$ut(tr startH, (n$ut(tr sto$H, =ut(tr dest) (n$ut(tr setBinterse*tion( (n$ut(tr start8, (n$ut(tr sto$8, (n$ut(tr startH, (n$ut(tr sto$H, =ut(tr dest) (n$ut(tr setBunion( (n$ut(tr start8, (n$ut(tr sto$8, (n$ut(tr startH, (n$ut(tr sto$H, =ut(tr dest) (n$ut(tr setBs%mmetri*Bdifferen*e( (n$ut(tr start8, (n$ut(tr sto$8, (n$ut(tr startH, (n$ut(tr sto$H, =ut(tr dest) void s'a$(Dalue? one, Dalue? t'o) &or'ard(tr s'a$Branges(&or'ard(tr start8, &or'ard(tr sto$8, &or'ard(tr startH) =ut$ut(tr transform((n$ut(tr start, (n$ut(tr sto$, =ut$ut(tr dest, &un*tion fn) Candom(tr
,pplies the function fn to all of the elements in the ran&e Ostart- sto$L and stores the result in the ran&e #e&innin& with dest! The return value is an iterator one past the end of the last value written! Feturns an iterator to the first element in the sorted ran&e parameter!
u$$erBbound(Candom(tr start, Ostart- sto$L that is strictly &reater than the value val! 'f you need Candom(tr sto$, to specify a special comparison function- you can do so as the final *onst ;%$e? val)
A /ord on Com&atibility The ST is 'SH1standardized alon& with the rest of C++! 'deally- this would mean that all ST implementations are uniform and that C++ code that works on one compiler should work on any other compiler! Unfortunatelythis is not the case! "o compilers on the market fully adhere to the standard- and almost universally compiler writers will make minor chan&es to the standard that decrease porta#ility! Consider- for e)ample- the 4onvert;o6o'er4ase function from earlier in the section=
string 4onvert;o6o'er4ase(string te7t) { transform(te7t.begin(), te7t.end(), te7t.begin(), tolo'er); return te7t; #
1 1;A 1
This code will compile in Gicrosoft Tisual Studio- #ut not in Jcode or the popular inu) compiler &++! The reason is that there are two tolo'er functions ( the ori&inal C tolo'er function e)ported #y **t%$e) and a more modern tolo'er function e)ported #y the lo*ale) header! Unfortunately- Jcode and &++ cannot dif1 ferentiate #etween the two functions- so the call to transform will result in a compiler error! To fi) the pro#1 lem- you must e)plicitly tell C++ which version of tolo'er you want to call as follows=
string 4onvert;o6o'er4ase(string te7t) { transform(te7t.begin(), te7t.end(), te7t.begin(), ::tolo6er); return te7t; #
/ere- the stran&e1lookin& 33 synta) is the sco#e-resolution o#erator and tells C++ that the tolo'er function is the ori&inal C function rather than the one e)ported #y the lo*ale) header! Thus- if you're usin& Jcode or &++ and want to use the functions from **t%$e)- you'll need to add the 33! ,nother spot where compati#ility issues can lead to trou#le arises when usin& ST al&orithms with the ST set! Consider the followin& code snippet- which uses fill to overwrite all of the elements in an ST set with the value 1A@=
fill(m%5et.begin(), m%5et.end(), 8FG);
This code will compile in Tisual Studio- #ut will not under &++! Fecall from the second chapter on ST con1 tainers that manipulatin& the contents of an ST set in1place can destroy the set's internal orderin&! Tisual Stu1 dio's implementation of set will nonetheless let you modify set contents- even in situations like the a#ove where doin& so is unsafe! &++- however- uses an ST implementation that treats all set iterators as read1only! Conse5uently- this code won't compile- and in fact will cause some particularly nasty compiler errors! 0hen portin& C++ code from one compiler to another- you mi&ht end up with ine)plica#le compiler errors! 'f you find some interestin& C++ code online that doesn't work on your compiler- it doesn't necessarily mean that the code is invalidR rather- you mi&ht have an overly strict compiler or the online code mi&ht use an overly leni1 ent one! #ore to $%&lore 0hile this chapter lists some of the more common al&orithms- there are many others that are useful in a variety of conte)ts! ,dditionally- there are some useful CIC++ li#rary functions that work well with al&orithms! 'f you're interested in ma)imizin& your al&orithmic firepower- consider lookin& into some of these topics= 1! .cctype>= This chapter #riefly mentioned the **t%$e) header- the C runtime li#rary's character type li#rary! **t%$e) include support for cate&orizin& characters Kfor e)ample- isal$ha to return if a character is a letter and is7digit to return if a character is a valid he)adecimal di&itL and formattin& conversions Ktou$$er and tolo'erL! ;! .cmath>= The C mathematics li#rary has all sorts of nifty functions that perform arithmetic operations like sin- sVrt- and e7$! Consider lookin& into these functions if you want to use transform on your containers! A! ,oost Algorithms= ,s with most of the C++ Standard i#rary- the 7oost C++ i#raries have a whole host of useful ST al&orithms ready for you to use! Hne of the more useful 7oost al&orithm sets is the strin& al&orithms- which e)tend the functionality of the find and re$la*e al&orithms on strings from dealin& with sin&le characters to dealin& with entire strin&s!
,l&orithms are ideally suited for solvin& a wide variety of pro#lems in a small space! Gost of the followin& pro1 &rammin& pro#lems have short solutions ( see if you can whittle down the space and let the al&orithms do the work for youU 1! 0rite a function <rintDe*tor that accepts a ve*tor int) as a parameter and prints its contents sep1 arated #y space characters! <+int% This can be accom#lished in one line @ use an ostream_iterator and copy) 4ou will want to see the solution to this #roblem)= M ;! Usin& removeBif and a custom call#ack function- write a function Cemove5hort1ords that accepts a ve*tor string) and removes all strin&s of len&th A or less from it! A! 'n n1dimensional space- the distance from a point K 71, 7;, 7A, ))), 7nL to the ori&in is ; ; ; 0rite a function 9istan*e;o=rigin that accepts a ve*tor double) rep1 71 7; ; 7 A !!! 7 n ! resentin& a point in space and returns the distance from that point to the ori&in! 2o not use any loops ( let the al&orithms do the heavy liftin& for you! <+int% 2se the transform algorithm and a custom callbac* "unction to sAuare all o" the elements o" the vector)= B! 0rite a function Oiased5ort that accepts a ve*tor string) #y reference and sorts the ve*tor le)1 ico&raphically- e)cept that if the ve*tor contains the strin& 4Ge 8irst-6 that strin& is always at the front of the sorted list! M ?! 0rite a function 4riti*s<i*+ that accepts a ma$ string, double) of movies and their ratin&s K#etween 0!0 and 10!0L and returns a set string) of the names of the top ten movies in the ma$! 'f there are fewer than ten elements in the ma$- then the resultin& set should contain every strin& in the ma$! <+int% Demember that all elements in a map<string, double> are stored internally as pair<string, double>= 6! 'mplement the *ount al&orithm for ve*tor int)s! .our function should have the prototype
int *ount(ve*tor int)33iterator start, ve*tor int)33iterator sto$, int ele, ment) and should return the num#er of elements in the ran&e Ostart- sto$L that are e5ual to ele, ment! M @! Usin& the generateBn al&orithm- the rand function- and a ba*+BinsertBiterator- show how to populate a ve*tor with a specified num#er of random values! Then use a**umulate to compute the
avera&e of the ran&e! E! Show how to use a com#ination of *o$%- istreambufBiterator- and ostreambufBiterator to open a file and print its contents to *out!
1 1;? 1
<! , monoal#habetic substitution ci#her is a simple form of encryption! 0e #e&in with the letters of the al1 pha#et- as shown here=
A O 4 9 E & G / ( \ X 6 : E = < W C 5 ; @ D 1 P R ]
0e then scram#le these letters randomly- yieldin& a new orderin& of the alpha#et! Hne possi#ility is as follows=
X D 9 W \ 1 A R E E & 4 6 C / @ P ( = G ; ] < : 5 O
This new orderin& thus defines a mappin& from each letter in the alpha#et to some other letter in the al1 pha#et- as shown here=
A X O D 4 9 9 W E \ & 1 G A / R ( E \ E X & 6 4 : 6 E C = / < @ W P C ( 5 = ; G @ ; D ] 1 < P : R 5 ] O
To encrypt a source strin&- we simply replace each character in the strin& with its correspondin& encryp1 ted character! 8or e)ample- the strin& 4The cookies are in the frid&e6 would #e encoded as follows=
; F / N E O 4 D = ' = ' X F ( / E O 5 < A M C I E O ( / E C ; F / N E O & = C I ( / 9 H G U E O
Gonoalpha#etic su#stitution ciphers are surprisin&ly easy to #reak ( in fact- most daily newspapers in1 clude a daily puzzle that involves decipherin& a monoalpha#etic su#stitution cipher ( #ut they are still useful for low1level encryption tasks such as postin& spoilers to we#sites Kwhere viewin& the spoiler e)1 plicitly re5uires the reader to decrypt the te)tL! Usin& the randomBshuffle al&orithm- implement a function :onoal$habeti*5ubstitutionEn, *r%$t that accepts a source strin& and encrypts it with a random monoalpha#etic su#stitution cipher!
A man, a #lan, a caret, a ban, a myriad, a sum, a lac, a liar, a hoo#, a #int, a catal#a, a gas, an oil, a bird, a yell, a !at, a caw, a #a7, a wag, a ta7, a nay, a ram, a ca#, a yam, a gay, a tsar, a wall, a car, a luger, a ward, a bin, a woman, a !assal, a wol", a tuna, a nit, a #all, a "ret, a watt, a bay, a daub, a tan, a cab, a datum, a gall, a hat, a tag, a :a#, a say, a jaw, a lay, a wet, a gallo#, a tug, a trot, a tra#, a tram, a torr, a ca#er, a to#, a ton*, a toll, a ball, a "air, a sa7, a minim, a tenor, a bass, a #asser, a ca#ital, a rut, an amen, a ted, a cabal, a tang, a sun, an ass, a maw, a sag, a jam, a dam, a sub, a salt, an a7on, a sail, an ad, a wadi, a radian, a room, a rood, a ri#, a tad, a #ariah, a re!el, a reel, a reed, a #ool, a #lug, a #in, a #ee*, a #arabola, a dog, a #at, a cud, a nu, a "an, a #al, a rum, a nod, an eta, a lag, an eel, a bati*, a mug, a mot, a na#, a ma7im, a mood, a lee*, a grub, a gob, a gel, a drab, a citadel, a total, a cedar, a ta#, a gag, a rat, a manor, a bar, a gal, a cola, a #a#, a yaw, a tab, a raj, a gab, a nag, a #agan, a bag, a jar, a bat, a way, a #a#a, a local, a gar, a baron, a mat, a rag, a ga#, a tar, a decal, a tot, a led, a tic, a bard, a leg, a bog, a burg, a *eel, a doom, a mi7, a ma#, an atom, a gum, a *it, a baleen, a gala, a ten, a don, a mural, a #an, a "aun, a ducat, a #agoda, a lob, a ra#, a *ee#, a ni#, a gul#, a loo#, a deer, a leer, a le!er, a hair, a #ad, a ta#ir, a door, a moor, an aid, a raid, a wad, an alias, an o7, an atlas, a bus, a madam, a jag, a saw, a mass, an anus, a gnat, a lab, a cadet, an em, a natural, a ti#, a caress, a #ass, a baronet, a minima7, a sari, a "all, a ballot, a *not, a #ot, a re#, a carrot, a mart, a #art, a tort, a gut, a #oll, a gateway, a law, a jay, a sa#, a :ag, a tat, a hall, a gamut, a dab, a can, a tabu, a day, a batt, a water"all, a #atina, a nut, a "low, a lass, a !an, a mow, a nib, a draw, a regular, a call, a war, a stay, a gam, a ya#, a cam, a ray, an a7, a tag, a wa7, a #aw, a cat, a !alley, a drib, a lion, a saga, a #lat, a catni#, a #ooh, a rail, a calamus, a dairyman, a bater, a canal @ Panama. ( 2an /oey OCic<6P 't is fittin& to conclude our whirlwind tour of the ST with an e)ample showcasin& e)actly how concise and powerful well1written ST code can #e! This e)ample is shorter than the others in this #ook- #ut should non1 etheless illustrate how the different li#rary pieces all fit toðer! Hnce you've finished readin& this chapter- you should have a solid understandin& of how the ST and streams li#raries can come toðer #eautifully to ele&1 antly solve a pro#lem! -alindromes , #alindrome is a word or phrase that is the same when read forwards or #ackwards- such as 4racecar6 or 4Galayalam!6 't is customary to i&nore spaces- punctuation- and capitalization when readin& palindromes- so the phrase 4Gr! Hwl ate my metal worm6 would count as a palindrome- as would 43o han& a salamiU ''m a lasa&na ho&!6 Suppose that we want to write a function (s<alindrome that accepts a string and returns whether or not the strin& is a palindrome! 'nitially- we'll assume that spaces- punctuation- and capitalization are all si&nificant in the strin&- so 4Carty trap6 would not #e considered a palindrome- thou&h 4Cart y traC6 would! 2on't worry ( we'll loosen this restriction in a #it! "ow- we want to verify that the strin& is the same when read forwards and #ack1 wards! There are many possi#le ways to do this! Crior to learnin& the ST - we mi&ht have written this function as follows=
1 1;E 1
bool (s<alindrome(string in$ut) { for(int + = 0; + in$ut.siUe() " H; !!+) if(in$ut[+] 0= in$ut[in$ut.length() , 8 Q +]) return false; return true; #
That is- we simply iterate over the first half of the strin& checkin& to see if each character is e5ual to its respect1 ive character on the other half of the strin&! There's nothin& wron& with the approach- #ut it feels too mechanical! The hi&h1level operation we're modelin& asks whether the first half of the strin& is the same forwards as the second half is #ackwards! The code we've written accomplishes this task- #ut has to e)plicitly walk over the characters from start to finish- manually checkin& each pair! Usin& the ST - we can accomplish the same result as a#ove without e)plicitly spellin& out the details of how to check each character! There are several ways we can harness the ST to solve this pro#lem! 8or e)ample- we could use the ST re, verse al&orithm to create a copy of the strin& in reverse order- then check if the strin& is e5ual to its reverse! This is shown here=
bool (s<alindrome(string in$ut) { string re)ersed , input; re)erse!input.begin!$B input.end!$$; return re)ersed ,, input; #
This approach works- #ut re5uires us to create a copy of the strin& and is therefore less efficient than our ori&inal implementation! Can we somehow emulate the functionality of the initial for loop usin& iterators> The answer is yes- thanks to reverseBiterators! Fecall that every ST container class e)ports a pair of functions called rbegin and rend returnin& reverseBiterators- iterators that start at the end of the container and end at the #e&innin&! This lets us iterate over the strin& contents #ackwards! ,lso recall that the ST eVual al&orithm ac1 cepts three inputs ( two iterators delineatin& a ran&e and a third iterator indicatin& the start of a second ran&e ( then returns whether the two ran&es are e5ual! Com#ined with reverseBiterators- this yields the followin& one-line im#lementation of (s<alindrome=
bool (s<alindrome(string in$ut) { return eGual!input.begin!$B input.begin!$ 3 input.siLe!$ T 8B input.rbegin!$$; #
This is a remarka#ly simple approach that is identical to what we've written earlier #ut much less ver#ose! Hf course- it doesn't correctly handle capitalization- spaces- or punctuation- #ut we can take care of that with only a few more lines of code! et's #e&in #y strippin& out everythin& from the strin& e)cept for alpha#etic characters! 8or this task- we can use the ST removeBif al&orithm- which accepts as input a ran&e of iterators and a pre1 dicate- then modifies the ran&e #y removin& all elements for which the predicate returns true! ike its partner al1 &orithm remove- removeBif doesn't actually remove the elements from the se5uence Ksee the last chapter for more detailsL- so we'll need to erase the remainin& elements afterwards! 7ecause we want to eliminate all characters from the strin& that are not alpha#etic- we need to create a predicate function that accepts a character and returns whether it is not a letter! The header file **t%$e) e)ports a help1 ful function called isal$ha that returns whether a character is a letter! This is the opposite what we want- so we'll create our own function which returns the ne&ation of isal$ha=Q
fun*tional)
li#rary in the second half of this #ook- you'll see a simpler way to do this!
1 1;< 1
0e can now strip out nonalpha#etic characters from our input strin& as follows=
bool (s<alindrome(string in$ut) { input.erase!remo)e&i !input.begin!$B input.end!$B Is/otUlpha$B input.end!$$; return eVual(in$ut.begin(), in$ut.begin() ! in$ut.siUe() " H, in$ut.rbegin()); #
8inally- we need to make sure that the strin& is treated case1insensitively- so inputs like 4F,CDcar6 are accepted as palindromes! Usin& the code developed in the chapter on al&orithms- we can convert the strin& to uppercase after strippin& out everythin& e)cept characters- yieldin& this final version of (s<alindrome=
bool (s<alindrome(string in$ut) { in$ut.erase(removeBif(in$ut.begin(), in$ut.end(), (sEotAl$ha), in$ut.end()); trans orm!input.begin!$B input.end!$B input.begin!$B ::toupper$; return eVual(in$ut.begin(), in$ut.begin() ! in$ut.siUe() " H, in$ut.rbegin());
# This function is remarka#le in its ele&ance and terseness! 'n three lines o" code we've stripped out all of the characters in a strin& that aren't letters- converted what's left to upper case- and returned whether the strin& is the same forwards and #ackwards! This is the ST in action- and ' hope that you're #e&innin& to appreciate the power of the techni5ues you've learned over the past few chapters! 7efore concludin& this e)ample- let's consider a variant on a palindrome where we check whether the words in a phrase are the same forwards and #ackwards! 8or e)ample- 42id mom pop> Gom didU6 is a palindrome #oth with respect to its letters and its words- while 4This is this6 is a phrase that is not a palindrome #ut is a word1pal1 indrome! ,s with re&ular palindromes- we'll i&nore spaces and punctuation- so 4't's an its6 counts as a word1pal1 indrome even thou&h it uses two different forms of the word itsIit's! The machinery we've developed a#ove works well for entire strin&sR can we modify it to work on a word1#y1word #asis> 'n some aspects this new pro#lem is similar to the ori&inal! 0e still to i&nore spaces- punctuation- and capitaliz1 ation- #ut now need to treat words rather than letters as meanin&ful units! There are many possi#le al&orithms for checkin& this property- #ut one solution stands out as particularly &ood! The idea is as follows= 1! Clean up the input= strip out everythin& e)cept letters and s#aces- then convert the result to upper case! ;! 7reak up the input into a list of words! A! Feturn whether the list is the same forwards and #ackwards! 'n the first step- it's important that we preserve the spaces in the ori&inal input so that we don't lose track of word #oundaries! 8or e)ample- we would convert the strin& 4/ello> /elloU> /D H>6 into 4/D H /D H /D H6 instead of 4/D H/D H/D H6 so that we can recover the individual words in the second step! Usin& a com#ination of the isal$ha and iss$a*e functions from **t%$e) and the convert1to1upper1case code used a#ove- we can preprocess the input as shown here=
1 1A0 1
bool (sEotAl$ha=r5$a*e(*har *h) { return %isalpha!ch$ SS %isspace!ch$; #
bool (s1ord<alindrome(string in$ut) { input.erase!remo)e&i !input.begin!$B input.end!$B Is/otUlpha<rSpace$B input.end!$$; trans orm!input.begin!$B input.end!$B input.begin!$B ::toupper$; "2 ... 2" #
,t this point the strin& in$ut consists of whitespace1delimited strin&s of uniform capitalization! 0e now need to tokenize the input into individual words! This would #e tricky were it not for stringstream! Fecall that when readin& a string out of a stream usin& the stream e)traction operator K))L- the stream treats whitespace as a delimiter! Thus if we funnel our strin& into a stringstream and then read #ack individual strin&s- we'll end up with a tokenized version of the input! Since we'll #e dealin& with an ar#itrarily1lon& list of strin&s- we'll store the resultin& list in a ve*tor string)- as shown here=
bool (s1ord<alindrome(string in$ut) { in$ut.erase(removeBif(in$ut.begin(), in$ut.end(), (sEotAl$ha=r5$a*e), in$ut.end()); transform(in$ut.begin(), in$ut.end(), in$ut.begin(), 33tou$$er); stringstream to+eniLer!input$; )ector.string> to+ens; "2 ... 2"
# "ow- what is the easiest way to read strin&s out of the stream until no strin&s remain> 0e could do this manu1 ally- as shown here=
bool (s1ord<alindrome(string in$ut) { in$ut.erase(removeBif(in$ut.begin(), in$ut.end(), (sEotAl$ha=r5$a*e), in$ut.end()); transform(in$ut.begin(), in$ut.end(), in$ut.begin(), 33tou$$er); stringstream to+eniUer(in$ut); ve*tor string) to+ens; 6hile!true$ * string to+en; to+eniLer >> to+en; i !to+eniLer. ail!$$ brea+; to+ens.push&bac+!to+en$;
4 #
This code is correct- #ut it's #ulky and unsi&htly! The pro#lem is that it's *ust too mechanical! 0e want to insert all of the tokens from the stringstream into the ve*tor- #ut as written it's not clear that this is what's happen1 in&! 8ortunately- there is a much- much easier way to solve this pro#lem thanks to istreamBiterator! Fecall that istreamBiterator is an iterator adapter that lets you iterate over an input stream as if it were a ran&e of
1 1A1 1
data! Usin& istreamBiterator to wrap the stream operations and the ve*tor's insert function to insert a ran&e of data- we can rewrite this entire loop in one line as follows=
bool (s1ord<alindrome(string in$ut) { in$ut.erase(removeBif(in$ut.begin(), in$ut.end(), (sEotAl$ha=r5$a*e), in$ut.end()); transform(in$ut.begin(), in$ut.end(), in$ut.begin(), 33tou$$er); stringstream to+eniUer(in$ut); ve*tor string) to+ens; to+ens.insert!to+ens.begin!$B TT Insert at beginning... istream&iterator.string>!to+eniLer$B TT Ceading rom to+eniLer... istream&iterator.string>!$$; TT To the end o the stream.
Fecall that two istreamBiterators are necessary to define a ran&e- and that an istreamBiterator con1 structed with no ar&uments is a special 4end of stream6 iterator! This one line of code replaces the entire loop from the previous implementation- and provided that you have some familiarity with the ST this second version is also easier to read! The last step in this process is to check if the se5uence of strin&s is the same forwards and #ackwards! 7ut we already know how to do this ( we *ust use eVual and a reverseBiterator! Dven thou&h the ori&inal imple1 mentation applied this techni5ue to a string- we can use the same pattern here on a ve*tor string) #ecause all the container classes are desi&ned with a similar interface! Femarka#le- isn't it> The final version of (s1ord<alindrome is shown here=
bool (s1ord<alindrome(string in$ut) { in$ut.erase(removeBif(in$ut.begin(), in$ut.end(), (sEotAl$ha=r5$a*e), in$ut.end()); transform(in$ut.begin(), in$ut.end(), in$ut.begin(), 33tou$$er); stringstream to+eniUer(in$ut); ve*tor string) to+ens; to+ens.insert(to+ens.begin(), "" (nsert at the beginning of the ve*tor istreamBiterator string)(to+eniUer), "" Ceading from to+eniUer istreamBiterator string)()); return eGual!to+ens.begin!$B to+ens.begin!$ 3 to+ens.siLe!$ T 8B to+ens.rbegin!$$;
Concluding 1emarks This 5uick interlude hopefully &ives you a taste for what's possi#le with the ST ! The skills you've developed over the past chapters will follow you throu&h the rest of your C++ pro&rammin& career and armed with only the knowled&e you've ac5uired so far you can tackle a wide array of pro&rammin& pro#lems! 7ut this is *ust the #e1 &innin& of C++ and it's now time to chan&e &ears and see what else C++ can offer us! 2on't worry ( althou&h the second half of this #ook focuses more on lan&ua&e features than li#raries- we will periodically revisit the ST to &ive you a feel for what this li#rary can do when #acked #y the full support of the C++ lan&ua&e!
-art Two
C++ Core Language !eatures
C++ is a general #ur#ose #rogramming language with a bias towards systems #rogramming that is a better C) su##orts data abstraction) su##orts object-oriented #rogramming) su##orts generic #rogramming ( 7*arne Stroustrup- inventor of C++ OStr0<!;P C++ has a rich set of core lan&ua&e features that makes it amon& the most e)pressive pro&rammin& lan&ua&es in modern use! ,s a systems pro&rammin& lan&ua&e- C++ &ives you direct access to the computer's memory- al1 lowin& you to fine1tune your code to take ma)imum advanta&e of availa#le hardware! ,s an o#*ect1oriented lan1 &ua&e- C++ supports a num#er of inheritance schemata and has a wide array of tools and annotations you can use to define classes! ,s a &eneric pro&rammin& lan&ua&e- C++ affords an enormous de&ree of fle)i#ility in &eneric class desi&n and has one of the most powerful template systems of any pro&rammin& lan&ua&e! 'n this section- we will e)plore C++'s key lan&ua&e features with the &oal of understandin& how to use C++ #oth as a tool for solvin& pro#lems and as a lan&ua&e for precisely and naturally e)pressin& the pieces of that pro#1 lem! 'deally- #y the time you have finished readin& over this section you will have an intuitive understandin& for how C++ is put toðer and will #e a#le to use your newfound lan&ua&e skills to make your pro&rams efficientro#ust- and maintaina#le! 7efore we #e&in discussin& the specifics of any one lan&ua&e features- let us take a few minutes to &o over some pieces of C++ desi&n philosophy that will inform our later discussion! The .eroC27erhead -rinci&le Hne of C++'s ma*or desi&n &oals is the :ero-o!erhead #rinci#le- that C++ code should only pay for the lan&ua&e features it actually uses! 'n other words- if you compile any C++ pro&ram- the resultin& e)ecuta#le should #e as efficient as if the C++ lan&ua&e consists solely of lan&ua&e features used in your pro&ram! The zero1overhead principle has important implications for C++ pro&rams! 8irst- #ecause you only pay runtime costs for lan&ua&e features you actually use- you should feel free to pro&ram in the style that you feel most com1 forta#le with! Crovided you understand the costs of the lan&ua&e features you use- you can &et a precise sense for how efficiently your pro&rams will perform! .ou mi&ht not know what virtual private multiple inheritance is- #ut provided you don't use it you don't need to worry a#out it makin& your implementation of a maze1solvin& al&orithm run less efficiently! Second- the zero1overhead principle means that it's perfectly accepta#le to learn only a su#set of C++'s lan&ua&e features! C++ is an enormous lan&ua&e and even veteran C++ pro&rammers learn new tricks from time to time-Q #ut #ecause you don't pay for what you don't use you shouldn't lose any sleep over potential holes in your C++ knowled&e #ase!
Q Hne of the &reat *oys of teachin& CS106 and writin& up this course reader has #een discoverin& new possi#ilities with1 in the C++ pro&rammin& lan&ua&e! .ou're not the only one still learnin& C++U
1 1AB 1
Hf course- the zero1overhead principle means that C++ can look stran&e or ver#ose! ,t times you'll need to e)1 plicitly indicate that you want to use a certain lan&ua&e feature whereas other lan&ua&es would have this as the default! 8or e)ample- mem#er functions in C++ #y default are not polymorphic Ksee the chapter on inheritance for more informationL #ecause polymorphic function calls are e)pensive- whereas in $ava all mem#er functions KmethodsL are polymorphic unless indicated otherwise! ,s you read throu&h these ne)t few chapters- keep the zero1overhead principle in mind and see how it influences C++'s desi&n! Com&ileCTime Cost 7s. 1untime Cost C++ places a premium on runtime efficiency and tries as much as possi#le to shift difficult computations from runtime to compile1time! C++ is desi&ned such that comple) operations such as field accesses- multiple inherit1 ance- and virtual functions can #e &reatly optimized at compile1time- which in part is why C++ is one of the most efficient &eneral1purpose pro&rammin& lan&ua&es! /owever- this philosophy at times makes C++ tricky to work with #ecause the compiler has to #e a#le to infer su#stantial information from the source code! ,s you'll see- this means that certain lan&ua&e features can seem at times pedantic or counterintuitive! Hf course- this e)1 tra work means that your pro&rams run much- much faster than e5uivalent pro&rams written in other lan&ua&es#ut #e prepared to offer sacrifices on the altar of compiler appeasement! C Com&atibility C++ inherited much of its core synta) and semantics from the C pro&rammin& lan&ua&e- a lan&ua&e desi&ned for operatin& system development! /istorically- C++ has tried to ma)imize compati#ility with C! ,t times this can #e e)tremely useful- since li#raries written in pure C can almost always #e directly inte&rated into C++ pro*ects! Goreover- C pro&rammers who want to use some of the simpler features of C++ can do so without spendin& too much time learnin& all of C++! 7ut C compati#ility is also the source of much criticism of C++! C++ allows for many inherently unsafe conver1 sions Kfor e)ample- conversions #etween pointer and inte&er typesL and &ives pro&rammers direct and at times dan&erous access to low1level memory! ,utomatic copyin& of stru*ts in C translates into potentially dan&er1 ous default copy #ehavior for C++ *lasses and stru*ts- and C's representation of character strin&s is a persist1 ent source of pro&rammer error and security vulnera#ilities! ,s you read over the ne)t few chapters- #e aware that many of the 5uirks you'll notice a#out C++ stem from this sort of #ackwards compati#ility! -rereIuisites The first few chapters in this section KCointers and Feferences- C Strin&s- the CreprocessorL can #e read with no prior #ack&round! The rest of this section- however- e)pects a familiarity with classes! 'n particular- you should know what the followin& are- when they should #e used- and- where applica#le- how to implement them=
The *lass keyword! The $ubli* and $rivate access specifiers! Constructors and destructors! Gem#er functions!
'f you are unfamiliar with any of these terms- refer to Programming Abstractions in C++ #y Dric Fo#erts and $ulie %elenski for an introduction!
C++ supports special primitive data types called #ointers and re"erences that alias other varia#les or memory locations! 0ith pointers and references- you can access other varia#les indirectly or refer to #locks of memory &enerated at runtime! Used correctly- pointers and references can perform wonders! ,s you'll see in CS1067IJ- pointers are crucial in data structures like trees- linked lists- and &raphs! Cointers are also used e)tensively in systems pro&rammin&since they provide a way to write values to specific memory re&ions! /owever- pointer power comes at a hi&h price- and it would not #e much of a stretch to claim that almost all pro&ram crashes are caused #y pointer and reference errors! This chapter acts as a 5uick introduction to pointers and references! .ou will pro#a#ly want to refer to the CS1067IJ course reader for more information! /hat is a 1eference6 ,s you have learned in CS1067IJ- C++ has two parameter1passin& mechanisms! The first- #ass-by-!aluepasses parameters #y initializin& the parameter as a copy of the ar&ument! 8or e)ample- consider the followin& function=
void 9o5omething(int 7) { 7!!; "" 7 is $assed b% value, so no *hanges to outside 'orld o**ur. #
/ere- when we call the 9o5omething function- the parameter 7 is initialized as a copy of the ar&ument to the function- so the line 7!! will not affect the value of any varia#les outside the function! That is- &iven the follow1 in& code=
int 7 = 8FG; *out 7 endl; "" <rints 8FG 9o5omething(7); *out 7 endl; "" <rints 8FG
7oth the first and second *out statements will print the value 1A@! The second version of parameter1passin& in C++ is #ass-by-re"erence- where ar&uments to the function can #e modified #y the function! ,s an e)ample- the followin& function takes its parameter #y reference- so local chan&es propa&ate to the caller=
void :utate<arameter(intS 7) { 7!!; "" 7 'ill be u$dated in the *alling fun*tion #
Usin& :utate<arameter in the followin& snippet will cause the first *out statement to print 1A@- #ut the second to print 1AE=
1 1A6 1
int 7 = 8FG; *out 7 endl; "" <rints 8FG :utate<arameter(7); *out 7 endl; "" <rints 8FI
"otice that the synta) for passin& 7 #y reference was to declare 7 as a int? 7 rather than simply int 7! 'nter1 estin&ly- you can use this synta) in other places in your C++ pro&rams to declare varia#les called re"erences which- like parameters that are passed #y reference- transparently modify other pro&ram varia#les! To see how references work- consider the followin& code snippet=
int m%Dariable = 8FG; intS m%Ceferen*e = m%Dariable; "" m%Ceferen*e is a referen*e to m%Dariable
'n this code snippet- we initially declare a varia#le of type int called m%Dariable- then initialize it to 1A@! 'n the ne)t line- we create a varia#le m%Ceferen*e of type int? and initialize it to the varia#le m%Dariable! 8rom this point on- m%Ceferen*e acts a reference to m%Dariable and like a reference parameter in a functionany chan&es to m%Ceferen*e will update the value of m%Dariable! 8or e)ample- consider the followin& code=
*out m%Dariable *out m%Ceferen*e m%Ceferen*e = JH; *out m%Dariable *out m%Ceferen*e endl; endl; endl; endl; "" "" "" "" "" <rints 8FG <rints 8FG, the value of m%Dariable (ndire*tl% u$dates m%Dariable <rints JH <rints JH
/ere- we print out the values of m%Dariable and m%Ceferen*e- which are the same #ecause m%Ceferen*e is a reference to m%Dariable! "e)t- we assi&n m%Ceferen*e the value B;- and #ecause m%Ceferen*e is a ref1 erence- it updates the value of m%Dariable- as shown with the final two *outs! 't is perfectly le&al to have several references to the same varia#le- as shown here=
int m%(nt = JH; intS ref8 = m%(nt, SrefH = m%(nt; "" ref8, refH no' referen*e m%(nt
"otice that when declarin& several references on the same line- it is necessary to prefi) each of them with an am1 persand! This is a 5uirk that helps make the synta) more consistent with that of the C pro&rammin& lan&ua&e! 8or&ettin& this rule is the source of many a de#u&&in& ni&htmare! 7ecause references are aliases for other varia#les- there are several restrictions on references that are not true of other types! 8irst- references must #e initialized when they are declared! 8or e)ample- the followin& code is il1 le&al=
int? m%Cef; "" Error3 need to initialiUe the referen*e0
This restriction at times can #e a #it ve)in&- #ut in the lon& run will save you a &ood deal of trou#le! 7ecause references always must #e initialized- when workin& with references you can #e more certain that the varia#le they're referrin& to actually e)ists! Second- #ecause references are aliases- you cannot initialize a reference usin& a constant value or with the return value of a function! 8or e)ample- #oth of the followin& are ille&al=
int? m%Cef8 = 8FG; "" Error3 4anAt initialiUe a referen*e to a *onstant. int? m%CefH = Get(nteger(); "" Error3 4anAt initialiUe to a return value.
1 1A@ 1
The reason for these restrictions is simple! Suppose that we could le&ally initialize m%Cef to 1A@! 0hat would happen if we wrote the followin&>
m%Cef = JH;
Since m%Cef is a reference- when we chan&e the value of m%Cef- we are actually chan&in& the value of some other varia#le! 7ut if m%Cef is initialized to 1A@- then this code would #e e5uivalent to writin&
8FG = JH;
0hich is clearly nonsensical!Q Similarly- we cannot initialize m%Cef to the return value of a function- since oth1 erwise we could try to write code to the effect of
Get(nteger() = JH;
0hich is ille&al! Technically speakin&- a reference can only #e initialized with what is known as an l!alue- somethin& that can le&ally #e put on the left1hand side of an assi&nment statement! 8or e)ample- any varia#le is an lvalue- since varia#les can #e assi&ned to! Constant literals like 1A@- on the other hand- are not lvalues #ecause we cannot put a constant on the left1hand side of an assi&nment statement! Hne final point a#out references is that once a reference has #een initialized- it is impossi#le to chan&e where that reference refers to! That is- once a reference has #een #ound to a value- we cannot 4re#ind6 the reference to some other o#*ect! 8or e)ample- consider the followin& code=
int m%(nt = 8FG, m%=ther(nt = 8FG; int? m%Cef = m%(nt, ?m%=therCef = m%=ther(nt; m%Cef = m%=therCef;
'n the last line- we assi&n m%Cef the value of m%=therCef! This does not cause m%Cef to #e&in referrin& to the same varia#le as m%=therCefR instead- it takes the value of the varia#le referenced #y m%=therCef and stores it in the varia#le referenced #y m%Cef! -ointers Cut simply- a #ointer is a varia#le that stores a data type and a memory address! 8or e)ample- a pointer mi&ht encode that an int is stored at memory address 0)B@2AE7A0- or that there is a double at 0)000AB;E0! ,t a hi&her level- you can think of a pointer as a more powerful version of a reference! ike references- pointers al1 low you to indirectly refer to another varia#le or memory location! Unlike a reference- however- it is possi#le to chan&e what o#*ect a pointer aliases! This versatility is #oth one of the most useful and one of the most ve)in& aspects of pointers- since you will have to take care to distin&uish #etween the pointer and its #ointee- the o#*ect at which it points! To declare a pointer- we use the synta) ;%$e2 variableEame- where ;%$e is the type of varia#le the pointer will point to and variableEame is the name of the newly1created varia#le! 8or e)ample- to create a pointer to an int- you could write
intR m%<ointer;
Q 'nterestin&ly- however- some older pro&rammin& lan&ua&es Knota#ly 8HFTF,"L used to allow assi&nments like thiswhich would lead to all sorts of de#u&&in& headaches!
1 1AE 1
The whitespace around the star in this declaration is unimportant ( the declarations int 2 m%<ointer and int 2m%<ointer are also valid! 8eel free to use whichever of these you feel is most intuitive- #ut #e sure that you're a#le to read all three #ecause each arises in professional code! 'n this course reader we will use the synta) ;%$e2 $tr! Cointers need not point solely to primitive types! /ere's code that creates a varia#le called m%5tr<tr that points to a C++ string=
stringR m%5tr<tr;
,s with references- when declarin& multiple pointers in a sin&le declaration- you will need to repeat the star for each varia#le! 8or e)ample- consider the followin& code snippet=
int2 m%<tr8, m%<trH; "" 6egal, but in*orre*t.
/ere- m%<tr8 is declared as an int 2 pointer to inte&er- #ut the varia#le m%<trH- despite its name- is *ust a plain old int! The star indicatin& a pointer only sticks onto the first varia#le it finds- so if you declare multiple pointers- you need to preface each with a star- as in
int2 m%<tr8, Rm%<trH; "" 6egal and *orre*t.
Initiali3ing a -ointer 0hen you create a pointer usin& the a#ove steps- you are simply declarin& a varia#le! The pointer does not point to anythin&! ike all other primitive types- the pointer will contain &ar#a&e data and could #e pointin& anywhere in memory! Thus- #efore you attempt to work with a pointer- you must #e sure to initialize it! "otice how point1 ers differ in this case from references ( it is ille&al to leave a reference uninitialized- #ut it is le&al K#ut unsafeL to leave pointers uninitialized! 'nitializin& a pointer simply means assi&nin& it the address Kmemory locationL of the o#*ect you want it to point to! 0hen this happens- the pointer is said to #oint to the varia#le at the address- called the #ointee! 0hile there are many ways to set up pointees- perhaps the simplest is to have the pointer point to a local varia#le declared on the stack! 8or e)ample- consider this code snippet=
int m%(nteger = 8FG; int2 m%(nt<ointer;
0e'd like to have m%(nt<ointer point to the varia#le m%(nteger! "ow- if m%(nt<ointer were a referencewe could simply write m%(nt<ointer = m%(nteger! /owever- #ecause m%(nt<ointer is a pointer and not a reference- this code would #e ille&al! 0hen workin& with pointers- to cause a pointer to point to a locationyou must e)plicitly assi&n the pointer the address of the o#*ect to point to! That is- if we want m%(nt<ointer to point to m%(nteger- we need to somehow &et the address of the m%(nteger varia#le! To do this- we can use C++'s address-o" o#erator- the ? operator! 8or e)ample- here is the code we could use to initialize m%(nt, <ointer to point to m%(nteger=
m%(nt<ointer = SmyInteger; "" m%(nt<ointer no' $oints to m%(nteger.
't is an unfortunate choice of synta) that C++ uses ? #oth to define a varia#le as a reference type and to &et the address of a varia#le! The difference has to do with conte)t ( if ? is used to declare a varia#le- it makes that varia#le a reference! Htherwise- ? is used to take the address of a varia#le! /ere we see one ma*or difference #etween pointers and references! 0hen workin& with references- to initialize a reference to refer to a varia#le- we simply used a strai&ht assi&nment statement! /owever- when usin& point1 ers- we have to e)plicitly take the address of the o#*ect we're pointin& at! 0hy the difference> The main reason
1 1A< 1
is that when workin& with pointers- there is an e)plicit difference #etween the pointer varia#le and the o#*ect #e1 in& pointed at! Feferences are in some sense not 4true6 varia#les #ecause usin& a reference always refers to the o#*ect the reference aliases rather than the reference itself! That is- if we have the followin& code=
int m%(nt; int? m%Cef = m%(nt; m%Cef!!;
'n the final line- m%Cef!!- we are not incrementin& m%Cef- #ut rather the o#*ect that it refers to! Cointers- on the other hand- are true varia#les that can #e reassi&ned- modified- and transformed without chan1 &in& the value of the o#*ect that they point at! 8or e)ample- &iven the followin& code=
int m%(nt8, m%(ntH; int2 m%<tr; m%<tr = ?m%(nt8; m%<tr = ?m%(ntH;
'n the last two lines we first make m%<tr point to m%(nt8- then reassi&n it to point to m%(ntH! "oticehowever- that #y sayin& m%<tr = ?m%(nt8 and m%<tr = ?m%(ntH- we did not chan&e the value of the o#*ect that m%<tr points at! 'nstead- we simply chan&ed what o#*ect the m%<tr varia#le was pointin& at! 'f we wanted to chan&e the value of the o#*ect pointed at #y m%<tr- we would have to dere"erence it- as e)plained in a later section! ,s with references- pointers are statically1typed and without e)plicitly su#vertin& the type system can only #e made to point to o#*ects of a certain type! 8or e)ample- the followin& code is ille&al #ecause it tries to make a int2 point to a double=
double m%9ouble = H.G8IHI; int2 m%(nt<tr = ?m%9ouble; "" Error3 4anAt store a double2 in an int2
,nother way to initialize a pointer is to have the pointer #e&in pointin& to the same location as another pointer! 8or e)ample- if we have a pointer named m%<tr pointin& to some location and we want to create a second point1 er m%=ther<tr- we can le&ally set m%=ther<tr to point to the same location as m%<tr! This is known as #ointer assignment and- fortunately- has clean synta)! 8or e)ample- here's code that makes two pointers each point to the same inte&er #y usin& pointer assi&nment=
int m%(nteger = 8FG; int2 m%<tr, 2m%=ther<tr; m%<tr = ?m%(nteger; "" Assign m%<tr the address of m%(nteger m%=ther<tr = m%<tr; "" m%=ther<tr no' $oints to the same ob>e*t as m%<tr
"ote that when settin& up m%<tr to point to m%(nteger- we assi&ned it the value ?m%(nteger- the address of the m%(nteger varia#le! /owever- when settin& up m%=ther<tr- we simply assi&ned it the value of m%<tr! /ad we written m%=ther<tr = ?m%<tr- we would have &otten a compilation error- since ?m%<tr is the loca1 tion of the m%<tr varia#le- not the location of its pointee! 'n &eneral- use strai&ht assi&nment when assi&nin& pointers to each other- and use the address1of operator when assi&nin& pointers the addresses of other varia#les! 9ereferencing a -ointer 0e now can initialize pointers- #ut how can we access the o#*ect #ein& pointed at> This re5uires a #ointer dere"erence- which follows a pointer to its destination and yields the o#*ect #ein& pointed at!
1 1B0 1
To dereference a pointer- you preface the name of the pointer with a star- as in 2m%(nt<ointer! The derefer1 enced pointer then acts identically to the varia#le it's pointin& to! 8or e)ample- consider the followin& code snip1 pet=
int m%(nteger = 8FG; int2 m%<tr = ?m%(nteger; RmyPtr , J8; "" 9ereferen*e m%<tr to get m%(nteger, then store JH. *out m%(nteger endl; "" <rints JH
'n the first two lines- we create a varia#le called m%(nteger and a pointer m%<tr that points to it! 'n the third line- 2m%<tr = JH- we dereference the pointer m%<tr to &et a reference to pointee- in this case m%(ntegerand assi&n it the value B;! 'n the final line we print the value B;- since we indirectly overwrote the contents of m%(nteger in the previous line! ,dmittedly- the star notation with pointers can &et a #it confusin& since it means either 4declare a pointer6 or 4dereference a pointer!6 ,s with the ? operator- with a little practice you'll #e a#le to differentiate #etween the two! 7efore you deference a pointer- you must make sure that you've set it up correctly! Consider the followin& e)1 ample=
int m%(nteger = 8FG; int2 m%(nt<ointer; "" Eote3 m%(nt<ointer 'asnAt initialiUed0 2m%(nt<ointer = JH; *out m%(nteger endl;
This code is identical to the a#ove e)ample- e)cept that we've for&otten to initialize m%(nt<ointer! ,s a res1 ult- the line 2m%(nt<ointer will result in unde"ined beha!ior! 0hen m%(nt<ointer is created- like any other primitive type- it initially holds a &ar#a&e value! ,s a result- when we write 2m%(nt<ointer = JH- the pro1 &ram will almost certainly crash #ecause m%(nt<ointer points to an ar#itrary memory address! ,lmost all #u&s that cause pro&rams to crash stem from dereferencin& invalid pointers! .ou will invaria#ly run into this pro#lem when workin& with C++ code- so remem#erin& to initialize your pointers will &reatly reduce the potential for error! The ,) 2&erator 0hen workin& with pointers to o#*ects Ki!e! string2L- the pointer dereference operator sometimes can #ecome a source of confusion! 8or e)ample- consider the followin& pro&ram- which tries to create a pointer to a string and then return the len&th of the string #ein& pointed at=
string m%5tring = .;his is a string0.; string2 m%<tr = ?m%5tring; "" (nitialiUe m%<tr; *out 2m%<tr.length() endl; "" 2rror: 5ee belo'.
This code at first mi&ht seem valid ( we initialize m%<tr to point to m%5tring- and then try to print out the len&th of the o#*ect #ein& pointed at! Unfortunately- this code is invalid #ecause the 2 operator does not #ind ti&htly enou&h! The pro#lem is that C++ interprets 2m%<tr.length() as 2(m%<tr.length())R that isdereferencin& the e)pression m%<tr.length()! This is a pro#lem #ecause m%<tr.length() is ille&al ( m%<tr is a pointer to a string- not a string itself- and we cannot apply the dot operator to it! The proper version of the a#ove code looks like this=
1 1B1 1
/ere- we've put parentheses around the e)pression 2m%<tr so that C++ treats (2m%<tr).length() correctly! 7ut this code is #ulky and difficult to read ( it overly stresses that m%<tr is a pointer rather than the fact that we're accessin& the length mem#er function! 8ortunately- C++ has a special operator called the arrow o#erator that can #e useful in these circumstances! $ust as you can use the dot operator to access properties of re&ular o#*ects- you can use the arrow operator to access properties of an o#*ect #ein& pointed at! /ere is another ver1 sion of the a#ove code- rewritten usin& the arrow operator=
string m%5tring = .;his is a string0.; string2 m%<tr = ?m%5tring; "" (nitialiUe m%<tr; *out myPtr->length!$ endl; "" @se arro' to sele*t length.
-ointer Com&arison Sometimes it is useful to compare two pointers to see if they are e5ual or une5ual! To do so- you can use the == and 0= operators- as shown here=
int m%(nt8, m%(ntH; int2 m%<tr8 = ?m%(nt8, 2m%<trH = ?m%(ntH; if(myPtr7 ,, myPtr8) "" 4he*+ if the $ointers $oint to the same lo*ation *out .<ointers are eVual. endl; if(myPtr7 %, myPtr8) "" 4he*+ if the $ointers $oint to different lo*ations *out .<ointers are uneVual. endl;
"ote that the == and 0= operators check to see if the #ointers are e5ual- not the #ointees! Thus if two pointers point to different o#*ects with the same value- == will return false! To see if the pointers point to o#*ects with the same value- compare the values of the dereferenced pointers rather than the pointers themselves! 8or e)ample=
i !RmyPtr7 ,, RmyPtr8$ "2 ... <ointers $oint to ob>e*ts 'ith the same value ... 2"
<ull -ointers 'n some cases- you may want to indicate that a pointer does not actually point to anythin&! 8or situations like this- you can assi&n pointers the special value E@66! ,ny pointer can #e assi&ned the value E@66- no matter what the type of that pointer is! 8or e)ample=
int2 m%(nt<tr = /U11; double2 m%9ouble<tr = /U11;
7ecause E@66 indicates that a pointer does not actually point at anythin&- dereferencin& a E@66 pointer leads to undefined #ehavior! 'n most cases- it crashes the pro&ram! Thus- #efore you dereference a pointer- make sure that you know it is non1E@66! 8or e)ample- here's code to print out the value a pointer points to if the pointer is non1E@66- and an error otherwise=
if(myPtr ,, /U11) "" 4he*+ if m%<tr $oints to E@66 *err .<ointer is E@660. endl; else *out .Dalue is3 . 2m%<tr endl;
1 1B; 1
Unlike $ava- in C++ pointers do not default to storin& E@66! This means that if you for&et to initialize a pointerit does not necessarily point to E@66 and in fact points to a random memory location! ,s a rule of thum#- if you declare a pointer #ut do not immediately initialize it- for safety reasons you should assi&n it the value E@66 so that you can use E@661checks later in your pro&ram! Unlike other lan&ua&es like $ava or Tisual 7asic- the value E@66 is not a C++ keyword! To use E@66- you need to include the header file *stddef)! /owever- since virtually every C++ standard li#rary header references *stddef)- in most cases you can i&nore the header file!
ne6 and delete
Up to this point- we've only used pointers to refer to other varia#les declared on the stack! /owever- there's an1 other place to store varia#les called the hea#- a re&ion of memory where varia#les can #e created at runtime! 0hile ri&ht now it mi&ht not #e apparent why you would choose to allocate memory in the heap- as you'll see later in CS1067IJ and CS106 - heap stora&e is critical in almost all nontrivial pro&rams! /eap stora&e &ives you the a#ility to create and destroy varia#les as needed durin& pro&ram e)ecution! 'f you need an inte&er to hold a value- or want to create a temporary string o#*ect- you can use the C++ ne' operator to o#tain a pointer to one! The ne' operator sets aside a small #lock of memory to hold the new varia#le- then returns a pointer to the memory! 8or e)ample- here's some code that allocates space for a double and stores it in a local pointer=
double2 d%nami*9ouble = ne6 double; 2d%nami*9ouble = H.G8IHI; "" 1rite the value H.G8IHI
"ote that #ecause ne' yields a pointer you do not need to use the address1of operator to assi&n a pointer to memory created with ne'! 'n fact- it is ille&al to do so- so code to this effect=
double2 d%nami*9ouble = ?(ne' double); "" Error3 (llegal to use ? here.
will not compile! Unlike other lan&ua&es like $ava- C++ does not have automatic &ar#a&e collection! ,s a result- if you allocate any memory with ne'- you must use the C++ delete keyword to reclaim the memory! /ere's some code that allocates some memory- uses it a #it- then deletes it=
int2 m%(nt<tr = ne6 int; 2m%(nt<tr = 8FG; *out 2m%(nt<tr endl; delete myIntPtr;
"ote that when you write delete m%(nt<tr- you are not destroyin& the m%(nt<tr varia#le! 'nstead- you're instructin& C++ to clean up the memory pointed at #y m%(nt<tr! ,fter writin& delete m%(nt<tr- you're free to reassi&n m%(nt<tr to other memory and continue usin& it! There are several important points to consider when usin& delete! 8irst- callin& delete on the same dynamic1 ally1allocated memory twice results in undefined #ehavior and commonly corrupts memory your pro&ram needs to function correctly! Thus you must #e very careful to #alance each call to ne' with one and e7actly one call to delete! This can #e tricky! 8or e)ample- consider the followin& code snippet=
int 2m%(nt<tr8 = ne' int; int 2m%(nt<trH = m%(nt<tr8; delete m%(nt<tr8; delete m%(nt<trH;
1 1BA 1
,t first you mi&ht think that this code is correct- since #oth m%(nt<tr8 and m%(nt<trH refer to dynamically1 allocated memory- #ut unfortunately this code dou#le1deletes the o#*ect! Since m%(nt<tr8 and m%(nt<trH refer to the same o#*ect- callin& delete on #oth varia#les will try to clean up the same memory twice! Second- after you call delete to clean up memory- accessin& the reclaimed memory results in undefined #eha1 vior! 'n other words- callin& delete indicates that you are done usin& the memory and do not plan on ever ac1 cessin& it a&ain! 0hile this mi&ht seem simple- it can #e 5uite complicated! 8or e)ample- consider this code snippet=
int2 m%(nt<tr8 = ne' int; int2 m%(nt<trH = m%(nt<tr8; delete m%(nt<trH; 2m%(nt<tr8 = 8FG;
Since m%(nt<tr8 and m%(nt<trH #oth refer to the same re&ion in memory- after the call to delete m%(nt, <trH- #oth m%(nt<tr8 and m%(nt<trH point to reclaimed memory! /owever- in the ne)t line we wrote 2m%, (nt<tr8 = 8FG- which tries to write a value to the memory address! This will almost certainly cause a runtime crash! 8inally- you should only delete memory that you e)plicitly allocated with ne'! That is- you should not de, lete a pointer to an o#*ect on a stack- nor should you try to delete a local varia#le! delete should only #e used to #alance out a call to ne' and nothin& more! Usin& delete to clean up memory not allocated #y ne' leads to undefined #ehavior that almost certainly will take down your entire pro&ram- so make sure you under1 stand where to use itU A /ord on Endefined ,eha7ior ''ve used the term unde"ined beha!ior several times in this discussion of pointers to indicate potential sources of runtime crashes! 7e sure that you don't e5uate undefined #ehavior and 4runtime errorR6 undefined #ehavior is far more insidious! 't would #e wonderful if all undefined #ehavior caused a crash! 'f a pro&ram had a pointer error- it would #e easy to dia&nose and fi) #y *ust waitin& for the crash to occur and then tracin& #ack to the source of the pro#lem! 7ut this *ust isn't the case! Undefined #ehavior commonly manifests nondeterministic1 ally- crashin& on some pro&ram runs and not on others! 'n fact- one of the telltale si&ns of undefined #ehavior is that the pro&ram works correctly most of the time- #ut periodically crashes une)pectedly! 8or an e)treme e)ample of undefined #ehavior- consider the ori&inal 2HS version of the classic &ame ,imCity! $oel Spolsky- a software #lo&&er- relates this story= !!! OHPne of the developers of the hit &ame SimCity!!! told me that there was a critical #u& in his ap1 plication= it used memory ri&ht after freein& it- a ma*or no1no that ha##ened to work HK on 2HS #ut would not work under 0indows where memory that is freed is likely to #e snatched up #y another runnin& application ri&ht away! The testers on the 0indows team were &oin& throu&h various popu1 lar applications- testin& them to make sure they worked HK- #ut SimCity kept crashin&! They repor1 ted this to the 0indows developers- who disassem#led SimCity- stepped throu&h it in a de#u&&erfound the #u&- and added s#ecial code that checked if SimCity was runnin&- and if it did- ran the memory allocator in a s#ecial mode in which you could still use memory a"ter "reeing it! OSpo0EP SimCity was usin& memory after freein& it- resultin& in undefined #ehavior! Hn 2HS1#ased systems where only a few applications would e)ecute at any one time- 4undefined #ehavior6 coincidentally happened to work cor1 rectly! /owever- when switchin& to a new operatin& system like 0indows <? where multiple different processes each need access to memory- 4undefined #ehavior6 crashed the pro&ram and the only way to fi) the pro#lem was to rewrite 0indows to e)plicitly avoid this pro#lem!
1 1BB 1
7e very careful that you don't step into the realm of undefined #ehavior! 2oin& so can lead to #u&s that manifest periodically yet can take down your entire pro&ram!
ne69: and delete9:
Commonly- when writin& pro&rams to manipulate data- you'll need to allocate enou&h space to store an inde1 terminate num#er of varia#les! 8or e)ample- suppose you want to write a pro&ram to play a variant on the &ame of checkers where the #oard can have any dimensions the user wishes! 0ithout usin& the Grid ,2T provided in the CS1067IJ li#raries- you would have a lot of trou#le &ettin& this pro&ram workin&- since you wouldn't know how much space the #oard would take up! To resolve this pro#lem- C++ has two special operators- ne'[] and delete[]- which allocate and deallocate #locks of memory holdin& multiple elements! 8or e)ample- you could allocate space for B00 inte&ers #y writin& ne6 int9J--:- or a se5uence of characters twelve elements lon& with ne6 char978:! The memory allocated with ne' [] stores all the elements in se5uential order- so if you know the startin& ad1 dress of the first varia#le- you can locate any varia#le in the se5uence you wish! 8or e)ample- if you have four1 teen ints startin& at address 1000- since ints are four #ytes each- you can find the second inte&er in the list at position 100B- the third at 100E- etc! Therefore- althou&h ne'[] allocates space for many varia#les- it returns only the address of the first varia#le! Thus you can store the list usin& a simple pointer- as shown #elow=
int2 m%:an%(nts = ne6 int97DE:;
Hnce you have a pointer to a dynamically1allocated array of elements- you can access individual elements usin& s5uare #rackets []! 8or e)ample- here's code to allocate ;00 inte&ers and set each one e5ual to its position in the array=
*onst int E@:BE6E:5 = H00; int2 m%:an%(nts = ne' int[E@:BE6E:5]; for(int i = 0; i E@:BE6E:5; !!i) my0anyInts9i: , i;
,s with re&ular ne'- you should clean up any memory you allocate with ne'[] #y #alancin& it with a call to delete[]! .ou should not put any num#ers inside the #rackets of delete []- since the C++ memory man1 a&er is clever enou&h to keep track of how many elements to delete! 8or e)ample- here's code to clean up a list of ints=
int 2m%(nts = ne' int[800]; delete 9: myInts;
"ote that delete and delete [] are different operators and cannot #e su#stituted for one another! That isyou must #e e)tremely careful not to clean up an array of elements usin& delete- nor a sin&le element usin& delete []! 2oin& so will have disastrous conse5uences for your pro&ram and will almost certainly cause a crash! 9ynamic Arrays 7s. STL )ector=deGue .ou now know three ways to store a linear se5uence of elements ( the ST ve*tor and deVue containers and dynamically1mana&ed arrays! Dach #ehaves sli&htly differently- so when is it appropriate to use each> The an1 swer is simple= prefer the ST ve*tor and deVue to dynamically1mana&ed arrays whenever possi#le! The ST containers are inherently safer ( they know their own size- they can &row automatically if you need to store addi1 tional data- and they are &uaranteed not to leak memory! Contrast this with dynamically1allocated arrays! 2y1 namically1allocated arrays don't know how many elements they contain- and if you want to keep track of how much memory you've allocated you must e)plicitly track it yourself! 2ynamic arrays also have a fi)ed capacity-
1 1B? 1
and once you've used up all of their stora&e space you must manually allocate additional stora&e space! Clusyou must remem#er to delete [] the memory you allocate- or you will end up with a memory leak! 0e have included dynamic arrays in our discussion of pointers for two reasons! 8irst- thou&h dynamic arrays are less safe than ST containers- many industrial C++ pro*ects nonetheless use e)posed dynamic arrays instead of the ST - either for compati#ility reasons or to avoid some of the su#tleties associated with the ST ! Second- dy1 namic arrays are a powerful tool for implementin& custom container classes! 'f you need to desi&n a custom ve*tor class or other specialized container- you may need to use dynamic array allocation to implement the container! 7ut that said- you should #e wary of dynamic array allocation and should opt to use the ST contain1 ers instead! #ore to $%&lore This introduction to pointers has #een rather #rief and does not address several important pointer topics! 0hile the ne)t chapter on C strin&s will cover some additional points- you should consider readin& into some of these additional topics for more information on pointers! ,lso- please #e sure to consult your course reader for CS1067IJ! 1! Automatic arrays= C and C++ let you allocate arrays of elements on the stack as well as in the heap! ,rrays are relatively unsafe compared to container classes like ve*tor- #ut are a #it faster and arise in le&acy code! ,rrays are stron&ly related to pointers- and you should consider lookin& into them if you plan to use C++ more seriously! ;! Smart &ointers= Cointers are difficult to work with ( you need to make sure to delete or delete [] memory once and e)actly once- and must #e on &uard not to access deallocated memory! ,s a resultC++ pro&rammers have developed o#*ects called smart #ointers that mimic standard pointers #ut handle all memory allocation and deallocation #ehind the scenes! Smart pointers are easy to use- reduce pro1 &ram comple)ity- and eliminate all sorts of errors! /owever- they do add a #it of overhead to your code! 'f you're interested in seriously usin& C++- #e sure to look into smart pointers! A! nullptr= C++ is an constantly1&rowin& lan&ua&e and a new revision of C++- called 4C++0)-6 is cur1 rently #ein& developed! 'n the new version of the lan&ua&e- the value E@66 will #e superseded #y a spe1 cial keyword null$tr- which functions identically to E@66! Hnce this new lan&ua&e revision is re1 leased- #e prepared to see null$tr in professional code! -ractice -roblems 1! 't is ille&al in C++ to have a reference to a reference! 0hy mi&ht this #e the case> <+int% is there a meaning"ul distinction between the re"erence and what it re"ers to'= ;! /owever- it is le&al C++ to have a pointer to a pointer! 0hy mi&ht this #e the case> <+int% is there a meaning"ul distinction between the #ointer and what it #oints to'= A! 0hat's wron& with this code snippet>
int m%(nteger = 8FG; int2 m%(nt<tr = m%(nteger; 2m%(nt<tr = JH;
B! 's this code snippet le&al> 'f so- what does it do> 'f not- why not>
int m%(nteger = 0; 2(?m%(nteger) = 8;
1 1B6 1
?! 0hen usin& ne'[] to allocate memory- you can access individual elements in the se5uence usin& the notation $ointer[inde7]! Fecall that $ointer[inde7] instructs C++ to start at the memory poin1 ted at #y $ointer- march forward inde7 elements- and read a value! 3iven a pointer initialized #y int2 m%(nt<tr = ne' int- what will m%(nt<tr[0] = JH do> 0hat a#out m%(nt<tr[8] = JH> M 6! 0hy is there a null pointer constant #ut no null reference> @! The C pro&rammin& lan&ua&e has pointers #ut no references! Therefore- pure C code has no concept of 4pass1#y1reference!6 'n order for a function to update the values of its parameters outside of the func1 tion- the function must accept pointers as ar&uments that point to the varia#les to chan&e! 8or e)amplethe 5'a$ function in pure C mi&ht #e written as follows=
void 5'a$(int2 one, int2 t'o) { int tem$ = 2one; 2one = 2t'o; 2t'o = tem$; #
To call this function to swap two inte&ers- you would write code as follows=
int one = 8FG, t'o = JH; 5'a$(?one, ?t'o);
,lso- e)plain one advanta&e of pass1#y1reference over pass1#y1pointer and one advanta&e of pass1#y1 pointer over pass1#y1reference! E! 't is le&al to print pointers to *outR doin& so prints out the memory address pointed at #y the pointer! 8or e)ample- the followin& code is perfectly le&al=
int m%(nt = 0; *out ?m%(nt endl; "" <rints the address of m%(nt
0rite a pro&ram that allocates several int local varia#les and prints out their addresses! 0hat do you notice a#out the memory locations they occupy> Then repeat this usin& ne' to dynamically allocate several ints! 2o the memory addresses returned #y ne' follow the same pattern>
Cha&ter 1 5 C Strings
_________________________________________________________________________________________________________
C++ was desi&ned to ma)imize #ackward compati#ility with the C pro&rammin& lan&ua&e- and conse5uently a#sor#ed C's representation of character strin&s! Unlike the C++ string type- C strin&s are very difficult to work with! Bery difficult! 'n fact- they are so difficult to work with that C++ pro&rammers invented their own string type so that they can avoid directly usin& C strin&s! 0hile C strin&s are si&nificantly more challen&in& than C++ strings and far more dan&erous- no C++ te)t would #e truly complete without a discussion of C strin&s! This chapter enters the perilous waters of C strin&s and their associated helper functions! /hat is a C String6 'n C++- string is a class that e)presses many common operations with simple operator synta)! .ou can make deep copies with the = operator- concatenate with !- and check for e5uality with ==! /owever- nearly every de1 sira#le feature of the C++ string- such as encapsulated memory mana&ement and lo&ical operator synta)- uses lan&ua&e features specific to C++! C strin&s- on the other hand- are simply *har 2 character pointers that store the startin& addresses of specially1formatted character se5uences! 'n other words- C++ strings e)emplify a#1 straction and implementation hidin&- while C strin&s amon& the lowest1level constructs you will routinely en1 counter in C++! 7ecause C strin&s operate at a low level- they present numerous pro&rammin& challen&es! 0hen workin& with C strin&s you must manually allocate- resize- and delete strin& stora&e space! ,lso- #ecause C strin&s are repres1 ented as #locks of memory- the synta) for accessin& character ran&es re5uires a sophisticated understandin& of pointer manipulation! Compoundin& the pro#lem- C strin& manipulation functions are cryptic and complicated! /owever- #ecause C strin&s are so low1level- they have several #enefits over the C++ string! Since C strin&s are conti&uous re&ions of memory- li#rary code for manipulatin& C strin&s can #e written in li&htin&1fast as1 sem#ly code that can outperform even the most ti&htly1written C or C++ loops! 'ndeed- C strin&s will almost consistently outperform C++ strings! /hy study C strings6 'n the early days of C++- the standard string type did not e)ist and C strin&s were the norm! Hver time- C strin&s have #een declinin& in usa&e! Gost modern C++ pro&rams either use the standard string type or a sim1 ilarly1desi&ned custom strin& type! So why should we dedicate any time to studyin& C strin&s> There are sever1 al reasons=
Legacy code! C++ code often needs to interoperate with pure C code or older C++ code that predates the string type! 'n these cases you are likely to #ump into C strin&s or classes layered on top of themand without an understandin& of C strin&s you are likely to &et confused or stuck! $dge cases! 't is le&al C++ code to add an inte&er to a strin& literal- #ut doin& so almost always results in a crash! Unless you understand how C strin&s and pointer arithmetic work- the root cause of this #e1 havior will remain a mystery! Library im&lementation! .ou may #e called upon to implement a strin& class that supports functional1 ity #eyond that of the standard string! Knowin& how to manipulate C strin&s can &reatly simplify this task!
0e will encounter each of these cases in the remainder of this course reader!
, C strin& is represented in memory as a consecutive se5uence of characters that ends with a 4terminatin& null-6 a special character with numeric value 0! $ust as you can use the escape se5uences ATnA for a newline and ATtA for a horizontal ta#- you can use the AT0A Kslash zeroL escape se5uence to represent a terminatin& null! 8ortu1 nately- whenever you write a strin& literal in C or C++- the compiler will automatically append a terminatin& null for you- so only rarely will you need to e)plicitly write the null character! 8or e)ample- the strin& literal 4Cirate6 is actually seven characters lon& in C++ ( si) for 4Cirate6 plus one e)tra for the terminatin& null! Gost li#rary functions automatically insert terminatin& nulls for you- #ut you should always #e sure to read the function docu1 mentation to verify this! 0ithout a terminatin& null- C and C++ won't know when to stop readin& characterseither returnin& &ar#a&e strin&s or causin& crashes!Q The strin& 4Cirate6 mi&ht look somethin& like this in memory=
P i r a t e [-
"ote that while the end of the strin& is delineated #y the terminatin& null- there is no indication here of where the strin& #e&ins! ookin& solely at the memory- it's unclear whether the strin& is 4Cirate-6 4irate-6 4rate-6 or 4ate!6 The only reason we 4know6 that the strin& is 4Cirate6 is #ecause we know that its startin& address is 1000! This has important implications for workin& with C strin&s! 3iven a startin& memory address- it is possi#le to entirely determine a strin& #y readin& characters until we reach a terminatin& null! 'n fact- provided the memory is laid out as shown a#ove- it's possi#le to reference a strin& #y means of a sin&le *har 2 varia#le that holds the startin& address of the character #lock- in this case 1000! 'f you encounter a function that accepts a parameter of type *har2- chances are that it accepts a C strin& rather than a pointer to a sin&le character! #emory Segments 7efore we #e&in workin& with C strin&s- we need to 5uickly cover memory se&ments! 0hen you run a C++ pro1 &ram- the operatin& system usually allocates memory for your pro&ram in 4se&ments-6 special re&ions dedicated to different tasks! .ou are most familiar with the stack se&ment- where local varia#les are stored and preserved #etween function calls! ,lso- there is a heap se&ment that stores memory dynamically allocated with the ne' and delete operators! There are two more se&ments- the code Kor te)tL se&ment and the data se&ment- of which we must speak #riefly! 0hen you write C or C++ code like the code shown #elow=
int main() { *har2 m%45tring = .;his is a 4 string0.; return 0; # Q Two C strin&s walk into a #ar! Hne C strin& says 4/ello- my name is $ohnSA0&BnvuAB;t@6BAt?k!!!6- so the second C strin& turns to the #artender and says 4Clease e)cuse my friend!!! he's not null1terminated!6
1 1B< 1
The te)t 4This is a C strin&U6 must #e stored somewhere in memory when your pro&ram #e&ins runnin&! Hn many systems- this te)t is stored in either the read1only code se&ment or in a read1only portion of the data se&1 ment! This ena#les the compiler to reduce the overall memory footprint of the pro&ram- since if multiple parts of the code each use the same strin& literal they can all point to the same strin&! 7ut this optimization is not without its costs! 'f you modify the contents of a read1only se&ment- you will crash your pro&ram with a segmentation "ault Ksometimes also called an access !iolation or 4se& fault6L! 7ecause your pro&ram cannot write to read1only memory se&ments- if you plan on manipulatin& the contents of a C strin&- you will need to first create a copy of that strin& somewhere where your pro&ram has write permis1 sion- usually in the heap! Thus- for the remainder of this chapter- any code that modifies strin&s will assume that the strin& resides either in the heap or on the stack Kusually the formerL! 8or&ettin& to duplicate the strin& and store its contents in a new #uffer can cause many a de#u&&in& ni&htmare- so make sure that you have writin& ac1 cess #efore you try to manipulate C strin&s! Allocating S&ace for Strings 7efore you can manipulate a C strin&- you need to first allocate memory to store it! 0hile traditionally this is done usin& older C li#rary functions K#riefly descri#ed in the 4Gore to D)plore6 sectionL- #ecause we are work1 in& in C++- we will instead use the ne'[] and delete[] operators for memory mana&ement! 0hen allocatin& space for C strin&s- you must make sure to allocate enou&h space to store the entire strin&- in1 cludin& the terminatin& null character! 'f you do not allocate enou&h space- when you try to copy the strin& from its current location to your new #uffer- you will write past the end of the #uffer into memory you do not neces1 sarily own! This is known as a bu""er o!errun and can crash your pro&ram or pose ma*or security pro#lems Ktake CS1?? if you're curious a#out why this poses a security riskL! The #est way to allocate space for a strin& is to make a new #uffer with size e5ual to the len&th of the strin& you will #e storin& in the #uffer! To &et the len&th of a C strin&- you can use the handy strlen function- declared in the header file *string)!Q strlen returns the len&th of a strin&- not includin& the terminatin& null character! 8or e)ample=
*out strlen!"String%"$ endl; "" Dalue is G *har2 m%5tr = .08HFJ.; *out strlen!myStr$ endl; "" Dalue is K
Thus- if you want to make a copy of a strin&- to allocate enou&h space you can use the followin& code=
"2 Assume *har 2te7t $oints to a 4 string. 2" *har2 m%4o$% = ne6 char9strlen!te#t$ 3 7:; "" Cemember !8 for null
,s always- remem#er to deallocate any memory you allocate with ne'[] with delete[]! ,asic String 2&erations 0hen workin& with C++ strings- you can make copies of a string usin& the K operator- as shown here=
string m%5tring = .4!! strings are eas%0.; string m%=ther5tring = m%5tring; "" 4o$% m%5tring
This is not the case for C strin&s- however! 8or e)ample- consider the followin& code snippet=
*string) is the Standard C++ header file for the C strin& li#rary! 8or pro&rams written in pure C- you'll need to instead include the header file string.h)!
1 1?0 1
*har2 m%5tring = .4 strings are hard0.; *har2 m%=ther5tring = m%5tring;
/ere- the second line is a pointer assi&nment- not a strin& copy! ,s a result- when the second line finishes e)1 ecutin&- m%5tring and m%=ther5tring #oth point to the same memory re&ion- so chan&es to one strin& will affect the other strin&! This can #e tricky to de#u&- since if you write the followin& code=
*out *out m%5tring endl; m%=ther5tring endl;
.ou will &et #ack two copies of the strin& 4 strings are hard0- which can trick you into thinkin& that you actually have made a deep copy of the strin&! To make a full copy of a C strin&- you can use the str*$% func1 tion- as shown #elow=
"2 Assume *har2 sour*e is initialiUed to a 4 string. 2" *har2 destination = ne' *har[strlen(sour*e) ! 8]; strcpy!destinationB source$;
/ere- the line str*$%(destination, sour*e) copies the data from sour*e into destination! ,s with most C strin& operations- you must manually ensure that there is enou&h space in destination to hold a copy of sour*e! Htherwise- str*$% will copy the data from sour*e past the end of the #uffer- which will wreck havoc on your pro&ram! "ote that the followin& code does not copy a C strin& from one location to another=
*har2 destination = ne' *har[strlen(sour*e) ! 8]; Rdestination , Rsource; "" 6egal but in*orre*t
This code only copies the first character from sour*e to destination- since 2sour*e is a sin&le characternot the entire C strin&! The fact that *har2s are used to point to C strin&s does not mean that C++ interprets them that way! ,s far as C++ is concerned- a *har2 is a pointer to a character and you will have to e)plicitly treat it like a C strin& usin& the li#rary functions to make C++ think otherwise! ,nother common strin& operation is concatenation! To append one C strin& onto the end of another- use the str*at function! Unlike in C++- when you concatenate two C strin&s- you must manually ensure there is enou&h allocated space to hold #oth strin&s! /ere is some code to concatenate two C strin&s! "ote the amount of space reserved #y the ne'[] call only allocates space for one terminatin& null!
"2 Assume *har2 first<art, 2 se*ond<art are initialiUed 4 strings. 2" *har2 result = ne' *har[strlen(first<art) ! strlen(se*ond<art) ! 8]; str*$%(result, first<art); "" 4o$% the first $art. strcat!resultB secondPart$; "" A$$end the se*ond $art.
7ecause of C++'s array1pointer interchan&a#ility- we can access individual characters usin& array synta)! 8or e)ample=
"2 Assume m%45tring is initialiUed to .4 5tring &un0. 2 ;his *ode might *rash if m%45tring $oints to memor% in a read,onl% 2 segment, so 'eAll assume %ou *o$ied it b% follo'ing the above ste$s. 2" *out m%45tring endl; "" =ut$ut3 4 5tring &un0 *out my;String9-: endl; "" =ut$ut3 4 my;String97-: , @a@; *out m%45tring endl; "" =ut$ut3 4 5tring &an0
1 1?1 1
0hen dealin& with C strin&s- you cannot use the #uilt1in relational operators K - ==- etc!L to check for e5uality! This will only check to see if the two pointers point to the same o#*ect- not whether the strin&s are e5ual! Thusyou must use the str*m$ function to compare two strin&s! str*m$ compares two strin&s str8 and strH- re1 turnin& a ne&ative num#er if str8 precedes strH alpha#etically- a positive num#er if str8 comes after strHand zero if str8 and strH are e5ual! Thus- you can use the followin& code to check for strin& e5uality=
if(strcmp!str7B str8$ ,, -) "2 ... strings are eVual ... 2"
That str*m$ returns zero if the two strin&s are e5ual is a common source of pro&rammin& errors! 8or e)ampleconsider the followin& code=
*har2 one = .;his is *har2 t'o = .;his is if(str*m$(one, t'o)) *out .one and else *out .one and a string0.; an entirel% different string0.; "" =atch out... this is in*orre*t0 t'o are eVual0. endl; t'o are not eVual0. endl;
/ere- we use the line if(str*m$(one, t'o)) to check if one and t'o are e5ual! /owever- this check is completely wron&! 'n C++- any nonzero value is treated as 4true6 inside an if statement and any zero value is treated as 4false!6 /owever- str*m$ returns 0 if the two strin&s are e5ual and a nonzero value otherwise- mean1 in& the statement if(str*m$(one, t'o)) will #e true if the two strin&s are di""erent and false if they're e5ui1 valent! 0hen workin& with str*m$- make sure that you don't accidentally make this mistake! -ointer Arithmetic 7ecause C strin&s are low1level constructs- strin& functions assume a familiarity with #ointer arithmetic ( the manipulation of pointers via arithmetic operators! This ne)t section is tricky- #ut is necessary to #e a#le to fully understand how to work with C strin&s! 8ortunately- if you think #ack to our discussion of ST iterators- this material should #e considera#ly less intimidatin&! 'n C and C++- pointers are implemented as inte&ral data types that store memory addresses of the values they point to! Thus- it is possi#le to chan&e where a pointer points #y addin& and su#tractin& values from it! et's #e&in with an e)ample usin& C strin&s! Suppose you have the strin& 4/elloU6 and a pointer to it laid out in memory as shown #elow=
Address 8000 8008 800H 800F 800J 800K 800M charR myString ' e l l o % [8000
1 1?; 1
Currently- #ecause m%5tring stores memory address 1000- it points to the strin& 4/elloU6 0hat happens if we write a line of code like the one shown #elow>
m%5tring = m%5tring ! 8;
'n C and C++- addin& one to a pointer returns a new pointer that points to the item one past the current pointer's location! 'n our current e)ample- this is memory address 1001- the start of the strin& 4elloU6 /ere is a drawin& of the state of memory after performin& the pointer arithmetic=
Address 8000 8008 800H 800F 800J 800K 800M charR myString ' e l l o % [8008
'n &eneral- addin& n to a pointer returns a pointer that points n items further than the ori&inal pointer! Thus- &iv1 en the a#ove state of memory- if we write m%5tring!!- we increment m%5tring to point to memory location 100;- the strin& 4lloU6 Similarly- if afterwards we were to su#tract two from m%5tring #y writin& m%5tring ,= H- m%5tring would once a&ain contain the value 1000 and would point to the strin& 4/elloU6 7e careful when incrementin& strin& pointers ( it is easy to increment them #eyond the ends of the #uffers they point to! 0hat if we were to write the code m%5tring != 8000> The strin& 4/elloU6 is less than 1000 charac1 ters lon&- and pointer m%5tring would point to a value far #eyond the end of the strin& and into random memory! Tryin& to read or write from this pointer would therefore have undefined #ehavior and would pro#a#ly result in a crash! et us consider one final type of pointer arithmetic- su#tractin& one pointer from another! Suppose we have the followin& C or C++ code=
*har2 $tr8 = .;his is m% string0.; *har2 $trH = $tr8 ! J; *out ($trH Q $tr8) endl;
0hat will the output #e> o&ically- we'd e)pect that since we set the value of $trH to #e four &reater than $tr8- the result of the su#traction would #e four! 'n &eneral- su#tractin& two pointers yields the num#er of ele1 ments #etween them! ,nother way to interpret the result of pointer su#traction is as an array inde)! ,ssumin& that $tr8 points to the #e&innin& of a C strin& and that $trH points to an element somewhere in that strin&$trH , $tr8 will return the numeric inde) of $trH in the strin&! This latter interpretation will #e important in the upcomin& section! #ore String !unctions ,rmed with a understandin& of pointer arithmetic- we can consider some more powerful strin& manipulation functions! et us first consider the strstr function- which returns a pointer to the first occurrence of a &iven su#strin& inside the specified strin&! 'f the su#strin& isn't found- strstr returns E@66 to si&nify an error!
1 1?A 1
endl;
.ou can also use the str*hr function in a similar way to determine the first instance of a &iven character in a strin&! Hne of the more useful strin& functions is the strn*$% function- which copies a specified num#er of characters from the source strin& to the destination! /owever- strn*$% is perhaps one of the most complicated li#rary functions ever introduced!Q Unlike the functions we've seen until this point- strn*$% is not &uaranteed to ap1 pend a terminatin& null to a strin&! 0hen you call strn*$%- you specify a destination strin&- a source strin&and a character count! 'f the end of the source strin& is reached #efore the specified num#er of characters have #een copied- then strn*$% will fill the remainder of the #uffer with null characters! Htherwise- you must manu1 ally append a terminatin& null! ,lthou&h strn*$% is complicated- it can #e 5uite useful! 8or e)ample- the followin& code demonstrates how to use strn*$% in con*unction with pointer arithmetic to e)tract a su#strin& from a source strin&=
*har2 Get5ubstring(*har2 str, int start, int length) { *har2 result = ne' *har[length ! 8]; "" (n*lude s$a*e for T0 strncpy!resultB str 3 startB length$; result[length] = AT0A; "" :anuall% a$$end terminating null. return result; #
The followin& ta#le summarizes some of the more useful C strin& functions! ,s usual- we have not covered the *onst keyword yet- #ut it's safe to i&nore it for now!
siUeBt strlen (*onst *har2 str) int length = strlen(.5tring0.);
Feturns the len&th of the C strin& str- e)cludin& the terminatin& null character! This function is useful for determinin& how much space is re1 5uired to hold a copy of a strin&!
*har2 str*$% (*har2 dest, *onst *har2 sr*) str*$%(m%Ouffer, .4 strings rule0.);
Copies the contents of the C strin& str into the #uffer pointed to #y dest! str*$% will not perform any #ounds checkin&- so you must make sure that the destination #uffer has enou&h space to hold the source strin&! str*$% returns dest!
*har2 str*at (*har2 dest, *onst *har2 sr*) str*at(m%5tring, . $lus more *hars..);
,ppends the C strin& specified #y sr* to the C strin& dest! ike str*$%str*at will not #ounds1check- so make sure you have enou&h room for #oth strin&s! str*at returns dest!
Q The CS department's "ick Carlante calls strn*$% the 40orst ,C' desi&n ever!6
Compares two strin&s le)ico&raphically and returns an inte&er represent1 in& how the strin&s relate to one another! 'f one precedes t'o alpha#etic1 ally- str*m$ returns a ne&ative num#er! 'f the two are e5ual- str*m$ re1 turns zero! Htherwise- it returns a positive num#er!
int strn*m$(*onst *har2 one, *onst *har2 t'o, siUeBt num4hars) if(strn*m$(m%5tr8, m%5trH, J) == 0) "" &irst J *hars eVual
'dentical to str*m$- e)cept that strn*m$ accepts a third parameter indic1 atin& the ma)imum num#er of characters to compare!
if(strstr(m%5tr, .iff%.) 0= E@66) "" found
*onst *har2 strstr(*onst *har2 sr*, *onst *har2 +e%) *har2 strstr(*har2 sr*, *onst *har2 +e%)
Searches for the su#strin& +e% in the strin& sour*e and returns a pointer to the first instance of the su#strin&! 'f +e% is not found- strstr returns E@66!
if(str*hr(m%5tr, AaA) 0= E@66) "" found
*har2 str*hr(*har2 sr*, int +e%) *onst *har2 str*hr(*onst *har2 sr*, int +e%)
Searches for the character +e% in the strin& sour*e and returns a pointer to the first instance of the character! 'f +e% is not found- str*hr returns E@66! 2espite the fact that +e% is of type int- +e% will #e treated as a *har inside of str*hr!
if(strr*hr(m%5tr, AaA) 0= E@66) "" found
*har2 strr*hr(*har2 sr*, int +e%) *onst *har2 strr*hr(*onst *har2 sr*, int +e%) *har2 strn*$% (*har2 dest, *onst *har2 sr*, siUeBt *ount)
Searches for the character +e% in the sour*e and returns a pointer to the last instance of the character! 'f +e% is not found- str*hr returns E@66!
strn*$%(m%Ouffer, .;heta., F);
Copies up to *ount characters from the strin& sr* into the #uffer pointed to #y dest! 'f the end of sr* is reached #efore *ount characters are writ1 ten- strn*$% appends null characters to dest until *ount characters have #een written! Htherwise- strn*$% does not append a terminatin& null! strn*$% returns dest!
strn*at(m%Ouffer, .;heta., F); "" A$$ends .;he. to m%Ouffer
,ppends up to *ount characters from the strin& sr* to the #uffer pointed to #y dest! Unlike strn*$%- strn*at will always append a terminatin& null to the strin&!
int first(nt = str*s$n(m%5tr, .08HFJKMGIL.);
Feturns the inde) of the first character in sour*e that matches any of the characters specified in the *hars strin&! 'f the entire strin& is made of characters not specified in *hars- str*s$n returns the len&th of the strin&! This function is similar to the findBfirstBof function of the C++ string!
*har2 str$br+(*har2 sour*e, *onst *har2 *hars) *onst *har2 str$br+(*onst *har2 sour*e, *onst *har2 *hars) siUeBt strs$n (*onst *har2 sour*e, *onst *har2 *hars); if(str$br+(m%5tr, .08HFJKMGIL.) == E@66) "" Eo ints found
Feturns a pointer to the first character in the source strin& that is con1 tained in the second strin&! 'f no matches are found- str$br+ returns E@66! This function is functionally 5uite similar to str*s$n!
int num(nts = strs$n(m%5tr, .08HFJKMGIL.);
Feturns the inde) of the first character in sour*e that is not one of the characters specified in the *hars strin&! 'f the entire strin& is made of characters specified in *hars- strs$n returns the len&th of the strin&! This function is similar to the findBfirstBnotBof function of the C++ string!
1 1?? 1
0hile this chapter has tried to demystify the #east that is the C strin&- there are several important topics we did not touch on! 'f you're interested in learnin& more a#out C strin&s- consider lookin& into the followin& topics= 1! CommandCline &arameters5 /ave you ever wondered why main returns a value> 't's #ecause it's pos1 si#le to pass parameters to the main function #y invokin& your pro&ram at the command line! To write a main function that accepts parameters- chan&e its declaration from int main() to
int main(int arg*, *har2 argv[])
/ere- arg* is the num#er of parameters passed to main Kthe num#er of C strin&s in the array argvL- and argv is an array of C strin&s containin& the parameters! This is especially useful for those of you inter1 ested in writin& command1line utilities! .ou will most certainly see this version of main if you continue writin& more serious C++ code! ;! malloc- realloc- and ree= These three functions are older C memory mana&ement functions that al1 locate- deallocate- and resize #locks of memory! These functions can #e unsafe when mi)ed with C++ code Ksee ,ppendi) 0= Govin& from C to C++L- #ut are nonetheless fre5uently used! Consider readin& up on these functions if you're interested in pro&rammin& in pure C! A! sprint and sscan = The C++ stringstream class allows you to easily read and write formatted data to and from C++ strings! The s$rintf and ss*anf functions let you perform similar functions on C strin&s! B! C memory mani&ulation routines5 The C header file *string) contains a set of functions that let you move- fill- and copy #locks of memory! ,lthou&h this is an advanced topic- some of these functions are useful in con*unction with C strin&s! 8or e)ample- the memmove function can #e used to shift char1 acters forward and #ackward in a strin& to make room for insertion of a new su#strin&! Similarly- you can use the memset function to create a strin& that's several repeated copies of a character- or to fill a #uffer with terminatin& nulls #efore writin& onto it! -ractice -roblems 1! D)plain why the code string m%5tring = .5tring. ! A0A will not work as intended! 0hat is it actually doin&> <+int% chars can im#licitly be con!erted to ints)= M ;! 0rite a function E7aggerate that accepts a C strin& and increases the value of each non1nine di&it in it #y one! 8or e)ample- &iven the input strin& 4' worked <0 hours and drove ;B miles6 the function would chan&e it to read 4' worked <1 hours and drove A? miles!6 A! D)plain why the followin& code &enerates an 4array #ounds overflow6 error durin& compilation=
*har m%5tring[M] = ./ello0.;
B! 0hen workin& with C++ strin&s- the erase function can #e used as m%5tring.erase(n) to erase all characters in the strin& startin& at position n- where n is assumed to #e within the #ounds of the strin&! 0rite a function ;run*ate5tring that accepts a *har 2 C1style strin& and an inde) in the strin&- then modifies the strin& #y removin& all characters in the strin& startin& at that position! .ou can assume that the inde) is in #ounds! <+int% o you actually need to remo!e the characters at that #osition, or can you tric* C++ into thin*ing that they(re not there'=
1 1?6 1
?! There are several ways to iterate over the contents of a C strin&! 8or e)ample- we can iterate over the strin& usin& #racket synta) usin& the followin& construct=
int length = strlen(m%5tr); "" 4om$ute on*e and store for effi*ien*%. for(int + = 0; + length; !!+) m%5tr[+] = "2 ... 2"
,nother means for iteratin& over the C strin& uses pointer arithmetic and e)plicitly checks for the ter1 minatin& null character! This is shown #elow=
for(*har2 *urr6o* = m%5tr; 2*urr6o* 0= AT0A; !!*urr6o*) 2*urr6o* = "2 ... 2"
0rite a function 4ount&reVuen*% which accepts as input a C1style strin& and a character- then returns the num#er of times that the character appears in the strin&! .ou should write this function two ways ( once usin& the first style of for loop- and once usin& the second! 6! 't is le&al to provide raw C++ pointers to ST al&orithms instead of ST iterators! Fewrite 4ount&re, Vuen*% usin& the ST *ount al&orithm! @! Compiler vendors often add their own nonstandard li#rary functions to the standard header files to entice customers! Hne common addition to the C strin& li#rary is a function str*ase*m$- which returns how two strin&s compare to one another case1insensitively! 8or e)ample- str*ase*m$(./e6l=0., .hello0.) would return zero- while str*ase*m$(./ello., .Goodb%e.) would return a ne&ative value #ecause Goodb%e alpha#etically precedes /ello!
str*ase*m$ is not availa#le with all C++ compilers- #ut it's still a useful function to have at your dis1 posal! 0hile implementin& a completely1correct version of str*ase*m$ is a #it tricky Kmainly when
decidin& how to compare letters and punctuation sym#olsL- it is not particularly difficult to write a simil1 ar function called 5tr4aseEVual which returns if two strin&s- compared case1insensitively- are e5ual to one another! 0ithout usin& str*ase*m$- implement the 5tr4aseEVual function! 't should accept as input two C1style strin&s and return whether they are e)actly identical when compared case1insensitively! The tou$$er- tolo'er- or isal$ha functions from **t%$e) mi&ht #e useful hereR consult a C++ refer1 ence for more details! To &ive you more practice directly manipulatin& C strin&s- your solution should not use any of the standard li#rary functions other than the ones e)ported #y **t%$e)! E! ,nother amusin& nonstandard C strin& manipulation function is the strfr% function- availa#le in 3"U1 compliant compilers! ,s descri#ed in O3"UP= Kstrfr%L addresses the perennial pro&rammin& 5uandary= 4/ow do ' take &ood data in strin& form and painlessly turn it into &ar#a&e>6 This is actually a fairly simple task for C pro&rammers who do not use the 3"U C li#rary strin& functions- #ut for pro&rams #ased on the 3"U C li#rary- the strfr% function is the preferred method for destroyin& strin& data!
strfr% accepts as input a C1style strin&- then randomly permutes its contents! 8or e)ample- callin& strfr% on a strin& containin& the te)t 4Unscram#le-6 strfr% mi&ht permute it to contain 4#armsc1 lUne!6 0rite an implementation of strfr%! <+int% Isn(t there an algorithm that scrambles ranges "or
you'= <! Suppose that we want to allocate a C strin& #uffer lar&e enou&h to hold a copy of an e)istin& C strin&! , common mistake is to write the followin&=
*har2 buffer = ne6 char9strlen!+SourceString 3 7$:; "" ,, Error here0
0hat does this code do> 0hat was it intended to do> 0hy doesn't it work correctly>
Hne of the most e)citin& parts of writin& a C++ pro&ram is pressin& the 4compile6 #utton and watchin& as your code transforms from static te)t into dynamic software! 7ut what e)actly &oes on #ehind the scenes that makes this transition possi#le> There are several steps involved in compilation- amon& the first of which is #re#rocessing- where a special pro&ram called the #re#rocessor reads in commands called directi!es and modifies your code #efore handin& it off to the compiler for further analysis! .ou have already seen one of the more common preprocessor directives- -in*lude- which imports additional code into your pro&ram! /owever- the prepro1 cessor has far more functionality and is capa#le of workin& a#solute wonders on your code! 7ut while the pre1 processor is powerful- it is difficult to use correctly and can lead to su#tle and comple) #u&s! This chapter intro1 duces the preprocessor- hi&hli&hts potential sources of error- and concludes with advanced preprocessor tech1 ni5ues! A /ord of /arning The preprocessor was developed in the early days of the C pro&rammin& lan&ua&e- #efore many of the more modern constructs of C and C++ had #een developed! Since then- #oth C and C++ have introduced new lan1 &ua&e features that have o#soleted or superseded much of the preprocessor's functionality and conse5uently you should attempt to minimize your use of the preprocessor! This is not to say- of course- that you should never use the preprocessor ( there are times when it's an e)cellent tool for the *o#- as you'll see later in the chapter ( #ut do consider other options #efore addin& a hastily1crafted directive!
Pinclude $%&lained
'n #oth CS1067IJ and CS106 - every pro&ram you've encountered has #e&un with several lines usin& the -in, *lude directiveR for e)ample- -in*lude iostream) or -in*lude .genlib.h.! 'ntuitively- these direct1 ives tell the preprocessor to import li#rary code into your pro&rams! iterally- -in*lude instructs the prepro1 cessor to locate the specified file and to insert its contents in place of the directive itself! Thus- when you write -in*lude .genlib.h. at the top of your CS1067IJ assi&nments- it is as if you had copied and pasted the contents of genlib.h into your source file! These header files usually contain prototypes or implementations of the functions and classes they e)port- which is why the directive is necessary to access other li#raries! .ou may have noticed that when -in*lude1in& CS1067IJ1specific li#raries- you've surrounded the name of the file in dou#le 5uotes Ke!&! .genlib.h.L- #ut when referencin& C++ standard li#rary components- you surround the header in an&le #rackets Ke!&! iostream)L! These two different forms of -in*lude instruct the prepro1 cessor where to look for the specified file! 'f a filename is surrounded in an&le #rackets- the preprocessor searches for it a compiler1specific directory containin& C++ standard li#rary files! 0hen filenames are in 5uotes- the preprocessor will look in the current directory!
-in*lude is a preprocessor directive- not a C++ statement- and is su#*ect to a different set of synta) restrictions than normal C++ code! 8or e)ample- to use -in*lude Kor any preprocessor directive- for that matterL- the dir1
ective must #e the first non1whitespace te)t on its line! 8or e)ample- the followin& is ille&al=
*out -in*lude iostream) endl; "" Error3 -in*lude must be at start of line.
Second- #ecause -in*lude is a preprocessor directive- not a C++ statement- it must not end with a semicolon! That is- -in*lude iostream); will pro#a#ly cause a compiler error or warnin&! 8inally- the entire -in, *lude directive must appear on a sin&le line- so the followin& code will not compile=
1 1?E 1
The Pde ine 9irecti7e Hne of the most commonly used Kand a#usedL preprocessor directives is -define- the e5uivalent of a 4search and replace6 operation on your C++ source files! 0hile -in*lude splices new te)t into an e)istin& C++ source file- -define replaces certain te)t strin&s in your C++ file with other values! The synta) for -define is
-define phrase replacement
,fter encounterin& a -define directive- whenever the preprocessor find phrase in your source code- it will re1 place it with replacement! 8or e)ample- consider the followin& pro&ram=
-define :RB4=E5;AE; 8FG int main() { int 7 = :RB4=E5;AE; , F; return 0; #
The first line of this pro&ram tells the preprocessor to replace all instances of :RB4=E5;AE; with the phrase 8FG! Conse5uently- when the preprocessor encounters the line
int 7 = :RB4=E5;AE; , F;
So 7 will take the value 1AB! 7ecause -define is a preprocessor directive and not a C++ statement- its synta) can #e confusin&! 8or e)1 ample- -define determines the stop of the phrase portion of the statement and the start of the replacement portion #y the position of the first whitespace character! Thus- if you write
-define ;1= 1=C95 8FG
The preprocessor will interpret this as a directive to replace the phrase ;1= with 1=C95 8FG- which is pro#a#ly not what you intended! The replacement portion of the -define directive consists of all te)t after phrase that precedes the newline character! Conse5uently- it is le&al to write statements of the form -define phrase without definin& a replacement! 'n that case- when the preprocessor encounters the specified phrase in your code- it will replace it with nothin&ness- effectively removin& it! "ote that the preprocessor treats C++ source code as se5uences of strin&s- rather than representations of hi&her1 level C++ constructs! 8or e)ample- the preprocessor treats int 7 = 8FG as the strin&s 4int-6 47-6 4=-6 and 48FG6 rather than a statement creatin& a varia#le 7 with value 8FG!Q 't may help to think of the preprocessor as a scanner that can read strin&s and reco&nize characters #ut which has no understandin& whatsoever of their mean1 in&s- much in the same way a native Dn&lish speaker mi&ht #e a#le to split Czech te)t into individual words without comprehendin& the source material!
Q Technically speakin&- the preprocessor operates on 4preprocessor tokens-6 which are sli&htly different from the whitespace1differentiated pieces of your code! 8or e)ample- the preprocessor treats strin& literals containin& whitespace as a sin&le o#*ect rather than as a collection of smaller pieces!
1 1?< 1
That the preprocessor works with te)t strin&s rather than lan&ua&e concepts is a source of potential pro#lems! 8or e)ample- consider the followin& -define statements- which define mar&ins on a pa&e=
-define 6E&;B:ACG(E 800 -define C(G/;B:ACG(E 800 -define 54A6E .K "2 ;otal margin is the sum of the left and right margins, multi$lied b% some 2 s*aling fa*tor. 2" -define ;=;A6B:ACG(E 6E&;B:ACG(E 2 54A6E ! C(G/;B:ACG(E 2 54A6E
'ntuitively- this should set 7 to twice the value of ;=;A6B:ACG(E- #ut unfortunately this is not the case! et's trace throu&h how the preprocessor will e)pand out this e)pression! 8irst- the preprocessor will e)pand ;=;A6B:ACG(E to 6E&;B:ACG(E 2 54A6E ! C(G/;B:ACG(E 2 54A6E- as shown here=
int 7 = H 2 6E&;B:ACG(E 2 54A6E ! C(G/;B:ACG(E 2 54A6E;
'nitially- this may seem correct- #ut look closely at the operator precedence! C++ interprets this statement as
int 7 = !8 R 12FT&0UCFI/ R S;U12$ ! C(G/;B:ACG(E 2 54A6E;
,nd the computation will #e incorrect! The pro#lem is that the preprocessor treats the replacement for ;=;A6B:ACG(E as a strin&- not a mathematic e)pression- and has no concept of operator precedence! This sort of error ( where a -defined constant does not interact properly with arithmetic e)pressions ( is a common mis1 take! 8ortunately- we can easily correct this error #y addin& additional parentheses to our -define! et's re1 write the -define statement as
-define ;=;A6B:ACG(E !12FT&0UCFI/ R S;U12 3 CIF'T&0UCFI/ R S;U12$
0e've surrounded the replacement phrase with parentheses- meanin& that any arithmetic operators applied to the e)pression will treat the replacement strin& as a sin&le mathematical value! "ow- if we write
int 7 = H 2 ;=;A6B:ACG(E;
0hich is the computation we want! 'n &eneral- if you -define a constant in terms of an e)pression applied to other -defined constants- make sure to surround the resultin& e)pression in parentheses! ,lthou&h this e)pression is certainly more correct than the previous one- it too has its pro#lems! 0hat if we re1 define 6E&;B:ACG(E as shown #elow>
-define 6E&;B:ACG(E H00 Q 800
0hich yields the incorrect result #ecause (H00 Q 800 2 .K ! 800 2 .K) is interpreted as
(H00 Q !7-- R .I$ ! 800 2 .K)
The pro#lem is that the -defined statement itself has an operator precedence error! ,s with last time- to fi) this- we'll add some additional parentheses to the e)pression to yield
-define ;=;A6B:ACG(E ((6E&;B:ACG(E) 2 (54A6E) ! (C(G/;B:ACG(E) 2 (54A6E))
This corrects the pro#lem #y ensurin& that each -defined su#e)pression is treated as a complete entity when used in arithmetic e)pressions! 0hen writin& a -define e)pression in terms of other -defines- make sure that you take this into account- or chances are that your constant will not have the correct value! ,nother potential source of error with -define concerns the use of semicolons! 'f you terminate a -define statement with a semicolon- the preprocessor will treat the semicolon as part of the replacement phrase- rather than as an 4end of statement6 declaration! 'n some cases- this may #e what you want- #ut most of the time it *ust leads to frustratin& de#u&&in& errors! 8or e)ample- consider the followin& code snippet=
-define :RB4=E5;AE; 8FG; "" =o$s,, un'anted semi*olon0 int 7 = :RB4=E5;AE; 2 F;
2urin& preprocessin&- the preprocessor will convert the line int 7 = :RB4=E5;AE; 2 F to read
int 7 = 8FG; 2 F;
This is not le&al C++ code and will cause a compile1time error! /owever- #ecause the pro#lem is in the prepro1 cessed code- rather than the ori&inal C++ code- it may #e difficult to track down the source of the error! ,lmost all C++ compilers will &ive you an error a#out the statement 2 F rather than a malformed -define! ,s you can tell- usin& -define to define constants can lead to su#tle and difficult1to1track #u&s! Conse5uentlyit's stron&ly preferred that you define constants usin& the *onst keyword! 8or e)ample- consider the followin& *onst declarations=
*onst *onst *onst *onst int 7 int int int int = H 6E&;B:ACG(E = H00 , 800; C(G/;B:ACG(E = 800; 54A6E = .K; ;=;A6B:ACG(E = 6E&;B:ACG(E 2 54A6E ! C(G/;B:ACG(E 2 54A6E; 2 ;=;A6B:ACG(E;
1 161 1
Dven thou&h we've used mathematical e)pressions inside the *onst declarations- this code will work as e)pec1 ted #ecause it is interpreted #y the C++ compiler rather than the preprocessor! Since the compiler understands the meaning of the sym#ols H00 Q 800- rather than *ust the characters themselves- you will not need to worry a#out stran&e operator precedence #u&s! Com&ileCtime Conditional $%&ressions Suppose we make the followin& header file- m%file.h- which defines a stru*t called :%5tru*t= -yFile)h
stru*t :%5tru*t { int 7; double %; *har U; #;
This code looks innocuous- #ut produces a compile1time error complainin& a#out a redefinition of stru*t :%, 5tru*t! The reason is simple ( when the preprocessor encounters each -in*lude statement- it copies the con1 tents of m%file.h into the pro&ram without checkin& whether or not it has already included the file! Con1 se5uently- it will copy the contents of m%file.h into the code twice- and the resultin& code looks like this=
struct 0yStruct * int #; double y; char L; 4; struct 0yStruct "" * int #; double y; char L; 4; int main() { return 0; #
The indicated line is the source of our compiler error ( we've dou#ly1defined stru*t :%5tru*t! To solve this pro#lem- you mi&ht think that we should simply have a policy of not -in*lude1in& the same file twice! 'n prin1 ciple this may seem easy- #ut in a lar&e pro*ect where several files each -in*lude each other- it may #e possi#le for a file to indirectly -in*lude the same file twice! Somehow- we need to prevent this pro#lem from happen1 in&!
1 16; 1
The pro#lem we're runnin& into stems from the fact that the preprocessor has no memory a#out what it has done in the past! Somehow- we need to &ive the preprocessor instructions of the form 4if you haven't already done so-in*lude the contents of this file!6 8or situations like these- the preprocessor supports conditional e)pressions! $ust as a C++ pro&ram can use if !!! else if !!! else to chan&e pro&ram flow #ased on varia#les- the prepro1 cessor can use a set of preprocessor directives to conditionally include a section of code #ased on -defined val1 ues! There are several conditional structures #uilt into the preprocessor- the most versatile of which are -if- -elif-else- and -endif! ,s you mi&ht e)pect- you use these directives accordin& to the pattern
-if statement ... -elif another-statement ... -elif yet-another-statement ... -else ... -endif
There are two details we need to consider here! 8irst- what sorts of e)pressions can these preprocessor directives evaluate> 7ecause the preprocessor operates #efore the rest of the code has #een compiled- preprocessor direct1 ives can only refer to -defined constants- inte&er values- and arithmetic and lo&ical e)pressions of those val1 ues! /ere are some e)amples- supposin& that some constant :RB4=E5;AE; is defined to B;=
-if -if -if -if :RB4=E5;AE; ) 8FG "" 6egal :RB4=E5;AE; 2 JH == :RB4=E5;AE; "" 6egal sVrt(:RB4=E5;AE;) J "" (llegal, *annot *all fun*tion sVrt :RB4=E5;AE; == F.8J "" (llegal, *an onl% use integral values
'n addition to the a#ove e)pressions- you can use the defined predicate- which takes as a parameter the name of a value that may have previously #een -defined! 'f the constant has #een -defined- defined evaluates to 8R otherwise it evaluates to 0! 8or e)ample- if :RB4=E5;AE; has #een previously -defined and =;/ECB4=E, 5;AE; has not- then the followin& e)pressions are all le&al=
-if defined(:RB4=E5;AE;) "" Evaluates to true. -if defined(=;/ECB4=E5;AE;) "" Evaluates to false. -if 0defined(:RB4=E5;AE;) "" Evaluates to false.
"ow that we've seen what sorts of e)pressions we can use in preprocessor conditional e)pressions- what is the e""ect of these constructs> Unlike re&ular if statements- which chan&e control flow at e)ecution- preprocessor conditional e)pressions determine whether pieces of code are included in the resultin& source file! 8or e)ampleconsider the followin& code=
-if defined(A) *out .A is -elif defined(O) *out .O is -elif defined(4) *out .4 is -else *out .Eone -endif defined.. defined.. defined.. endl; endl; endl; endl;
of A, O, or 4 is defined..
1 16A 1
/ere- when the preprocessor encounters these directives- whichever of the conditional e)pressions evaluates to true will have its correspondin& code #lock included in the final pro&ram- and the rest will #e i&nored! 8or e)1 ample- if A is defined- this entire code #lock will reduce down to
*out .A is defined.. endl;
,nd the rest of the code will #e i&nored! Hne interestin& use of the -if ... -endif construct is to comment out #locks of code! Since C++ interprets all nonzero values as true and zero as false- surroundin& a #lock of code in a -if 0 !!! -endif #lock causes the preprocessor to eliminate that #lock! Goreover- unlike the traditional "2 !!! 2" comment type- preprocessor directives can #e nested- so removin& a #lock of code usin& -if 0 !!! -endif doesn't run into the same pro#1 lems as commentin& the code out with "2 !!! 2"! 'n addition to the a#ove conditional directives- C++ provides two shorthand directives- -ifdef and -ifndef! -ifdef Kif definedL is a directive that takes as an ar&ument a sym#ol and evaluates to true if the sym#ol has #een -defined! Thus the directive -ifdef symbol is completely e5uivalent to -if defined(symbol)! C++ also provides -ifndef Kif not definedL- which acts as the opposite of -ifdefR -ifndef symbol is e5ui1 valent to -if 0defined(symbol)! ,lthou&h these directives are strictly weaker than the more &eneric -if- it is far more common in practice to see -ifdef and -ifndef rather than -if defined and -if 0definedprimarily #ecause they are more concise! Usin& the conditional preprocessor directives- we can solve the pro#lem of dou#le1includin& header files! et's return to our e)ample with -in*lude .m%file.h. appearin& twice in one file! /ere is a sli&htly modified version of the m%file.h file that introduces some conditional directives= -yFile)h, !ersion 1
Pi nde 0yFile&included Pde ine 0yFile&included stru*t :%5tru*t { int 7; double %; *har U; #; Pendi
/ere- we've surrounded the entire file in a #lock -ifndef :%&ileBin*luded !!! -endif! The specific name :%&ileBin*luded is not particularly important- other than the fact that it is uni5ue to the m%file.h file! 0e could have *ust as easily chosen somethin& like -ifndef sdfFLKHGd+bH or another uni5ue name- #ut the cus1 tom is to choose a name determined #y the file name! 'mmediately after this -ifndef statement- we -define the constant we are considerin& inside the -ifndef! To see e)actly what effect this has on the code- let's return to our ori&inal source file- reprinted #elow=
-in*lude .m%file.h. -in*lude .m%file.h. "" -in*lude the same file t'i*e int main() { return 0; #
1 16B 1 0ith the modified version of m%file.h- this code e)pands out to
Pi nde 0yFile&included Pde ine 0yFile&included struct 0yStruct * int #; double y; char L; 4; Pendi Pi nde 0yFile&included Pde ine 0yFile&included struct 0yStruct * int #; double y; char L; 4; Pendi int main() { return 0; #
"ow- as the preprocessor #e&ins evaluatin& the -ifndef statements- the first -ifndef !!! -endif #lock from the header file will #e included since the constant :%&ileBin*luded hasn't #een defined yet! The code then -defines :%&ileBin*luded- so when the pro&ram encounters the second -ifndef #lock- the code inside the -ifndef !!! -endif #lock will not #e included! Dffectively- we've ensured that the contents of a file can only #e -in*luded once in a pro&ram! The net pro&ram thus looks like this=
struct 0yStruct * int #; double y; char L; 4; int main() { return 0; #
0hich is e)actly what we wanted! This techni5ue- known as an include guard- is used throu&hout professional C++ code- and- in fact- the #oilerplate -ifndef = -define = -endif structure is found in virtually every header file in use today! 0henever writin& header files- #e sure to surround them with the appropriate preprocessor dir1 ectives! #acros Hne of the most common and comple) uses of the preprocessor is to define macros- compile1time functions that accepts parameters and output code! 2espite the surface similarity- however- preprocessor macros and C++
1 16? 1
functions have little in common! C++ functions represent code that e)ecutes at runtime to manipulate datawhile macros e)pand out into newly1&enerated C++ code durin& preprocessin&! To create macros- you use an alternative synta) for -define that specifies a parameter list in addition to the constant name and e)pansion! The synta) looks like this=
-define macroname(parameter1, parameter2, ... , parameterN) macro-body*
"ow- when the preprocessor encounters a call to a function named macroname- it will replace it with the te)t in macro-body! 8or e)ample- consider the followin& macro definition=
-define <6@5B=EE(7) ((7) ! 8)
"ow- if we write
int 7 = <6@5B=EE(8FG);
So 7 will have the value 1AE! 'f you'll notice- unlike C++ functions- preprocessor macros do not have a return value! Gacros e)pand out into C++ code- so the 4return value6 of a macro is the result of the e)pressions it creates! 'n the case of <6@5B=EEthis is the value of the parameter plus one #ecause the replacement is interpreted as a mathematical e)pression! /owever- macros need not act like C++ functions! Consider- for e)ample- the followin& macro=
-define :AXEB&@E4;(=E(fnEame) void fnEame()
endl;
endl;
,s you can see- this is entirely different than the <6@5B=EE macro demonstrated a#ove! 'n &eneral- a macro can #e e)panded out to any te)t and that te)t will #e treated as thou&h it were part of the ori&inal C++ source file! This is a mi)ed #lessin&! 'n many cases- as you'll see later in the chapter- it can #e e)ceptionally useful! /owever- as with other uses of -define- macros can lead to incredi#ly su#tle #u&s that can #e difficult to track down! Cerhaps the most famous e)ample of macros &one wron& is this :AP macro=
-define :AP(a, b) ((a) ) (b) S (a) 3 (b))
Q "ote that when usin& -define- the openin& parenthesis that starts the ar&ument list must not #e preceded #y whitespace! Htherwise- the preprocessor will treat it as part of the replacement phrase for a -defined constant!
1 166 1
/ere- the macro takes in two parameters and uses the S3 operator to choose the lar&er of the two! 'f you're not familiar with the S3 operator- the synta) is as follows=
expression W result-if-true : result-if-false
'n our case- ((a) ) (b) S (a) 3 (b)) evaluates the e)pression (a) ) (b)! 'f the statement is true- the value of the e)pression is (a)R otherwise it is (b)! ,t first- this macro mi&ht seem innocuous and in fact will work in almost every situation! 8or e)ample=
int 7 = :AP(800, H00);
D)pands out to
int 7 = ((800) ) (H00) S (800) 3 (H00));
0hich assi&ns 7 the value H00! /owever- what happens if we write the followin&>
int 7 = :AP(:%&n8(), :%&nH());
0hile this will assi&n 7 the lar&er of :%&n8() and :%&nH()- it will not evaluate the parameters only once- as you would e)pect of a re&ular C++ function! ,s you can see from the e)pansion of the :AP macro- the functions will #e called once durin& the comparison and possi#ly twice in the second half of the statement! 'f :%&n8() or :%&nH() are slow- this is inefficient- and if either of the two have side effects Kfor e)ample- writin& to disk or chan&in& a &lo#al varia#leL- the code will #e incorrect! The a#ove e)ample with :AP illustrates an important point when workin& with the preprocessor ( in &eneralC++ functions are safer- less error1prone- and more reada#le than preprocessor macros! 'f you ever find yourself wantin& to write a macro- see if you can accomplish the task at hand with a re&ular C++ function! 'f you canuse the C++ function instead of the macro ( you'll save yourself hours of de#u&&in& ni&htmares! Inline !unctions Hne of the motivations #ehind macros in pure C was pro&ram efficiency from inlining! 8or e)ample- consider the :AP macro from earlier- which was defined as
-define :AP(a, b) ((a) ) (b) S (a) 3 (b))
'f we call this macro- then the code for selectin& the ma)imum element is directly inserted at the spot where the macro is used! 8or e)ample- the followin& code=
int m%(nt = 0UV!oneB t6o$;
D)pands out to
int m%(nt = !!one$ > !t6o$ W !one$ : !t6o$$;
0hen the compiler sees this code- it will &enerate machine code that directly performs the test! 'f we had in1 stead written :AP as a re&ular function- the compiler would pro#a#ly implement the call to :AP as follows=
Cha#ter /;% The Pre#rocessor 1! Call the function called :AP Kwhich actually performs the comparisonL ;! Store the result in the varia#le m%(nt!
1 16@ 1
This is considera#ly less efficient than the macro #ecause of the time re5uired to set up the function call! 'n com1 puter science *ar&on- the macro is inlined #ecause the compiler places the contents of the 4function6 at the call site instead of insertin& an indirect *ump to the code for the function! 'nlined functions can #e considera#ly more efficient that their non1inline counterparts- and so for many years macros were the preferred means for writin& utility routines! 7*arne Stroustrup is particularly opposed to the preprocessor #ecause of its idiosyncrasies and potential for er1 rors- and to entice pro&rammers to use safer lan&ua&e features developed the inline keyword- which can #e ap1 plied to functions to su&&est that the compiler automatically inline them! 'nline functions are not treated like macros ( they're actual functions and none of the ed&e cases of macros apply to them ( #ut the compiler will try to safely inline them if at all possi#le! 8or e)ample- the followin& :a7 function is marked inline- so a reason1 a#ly &ood compiler should perform the same optimization on the :a7 function that it would on the :AP macro=
inline int :a7(int one, int t'o) { return one ) t'o S one 3 t'o; #
The inline keyword is only a su&&estion to the compiler and may #e i&nored if the compiler deems it either too difficult or too costly to inline the function! /owever- when writin& short functions it sometimes helps to mark the function inline to improve performance! A Pde ine Cautionary Tale
-define is a powerful directive that ena#les you to completely transform C++! /owever- many CIC++ e)perts a&ree that you should not use -define unless it is a#solutely necessary! Creprocessor macros and constants o#1 fuscate code and make it harder to de#u&- and with a few cryptic -defines veteran C++ pro&rammers will #e at
a loss to understand your pro&rams! ,s an e)ample- consider the followin& code- which references an e)ternal file m%defines.h=
-in*lude .m%defines.h. =n*e u$on a time a little bo% too+ a 'al+ in a $ar+ /e (the *hild) found a small stone and thre' it (the stone) in a $ond ;he end
Surprisin&ly- and worryin&ly- it is possi#le to make this code compile and run- provided that m%defines.h con1 tains the proper -defines! 8or e)ample- here's one possi#le m%defines.h file that makes the code compile=
endl;
,fter preprocessin& Kand some whitespace formattin&L- this yields the pro&ram
-in*lude iostream) using names$a*e std; int :%&un*tion(int 7) { return 7; # int main() { *out :%&un*tion(8FG) return 0; #
endl;
0hile this e)ample is admittedly a de&enerate case- it should indicate e)actly how disastrous it can #e for your pro&rams to misuse -defined sym#ols! Cro&rammers e)pect certain structures when readin& C++ code- and #y o#scurin& those structures #ehind walls of -defines you will confuse people who have to read your code! 0orse- if you step away from your code for a short time Ksay- a week or a monthL- you may very well return to it with a#solutely no idea how your code operates! Conse5uently- when workin& with -define- always #e sure to ask yourself whether or not you are improvin& the reada#ility of your code!
1 16< 1
The previous section ended on a rather &rim note- &ivin& an e)ample of preprocessor usa&e &one awry! 7ut to entirely eschew the preprocessor in favor of other lan&ua&e features would also #e an error! 'n several circum1 stances- the preprocessor can perform tasks that other C++ lan&ua&e features cannot accomplish! The remainder of this chapter e)plores where the preprocessor can #e an invalua#le tool for solvin& pro#lems and points out several stren&ths and weaknesses of preprocessor1#ased approaches! S&ecial -re&rocessor Halues The preprocessor has access to several special values that contain information a#out the state of the file currently #ein& compiled! The values act like -defined constants in that they are replaced whenever encountered in a pro&ram! 8or e)ample- the values BB9A;EBB and BB;(:EBB contain strin& representations of the date and time that the pro&ram was compiled! Thus- you can write an automatically1&enerated 4a#out this pro&ram6 function usin& synta) similar to this=
string GetAbout(nformation() { stringstream result; result .;his $rogram 'as *om$iled on result . at time . BB;(:EBB; return result.str(); #
BB9A;EBB;
Similarly- there are two other values- BB6(EEBB and BB&(6EBB- which contain the current line num#er and the name of the file #ein& compiled! 0e'll see an e)ample of where BB6(EEBB and BB&(6EBB can #e useful later in this chapter! String #ani&ulation !unctions 0hile often dan&erous- there are times where macros can #e more powerful or more useful than re&ular C++ functions! Since macros work with source1level te)t strin&s instead of at the C++ lan&ua&e level- some pieces of information are availa#le to macros that are not accessi#le usin& other C++ techni5ues! 8or e)ample- let's return to the :AP macro we used in the previous chapter=
-define :AP(a, b) ((a) ) (b) S (a) 3 (b))
/ere- the ar&uments a and b to :AP are passed #y string ( that is- the ar&uments are passed as the strin&s that compose them! 8or e)ample- :AP(80, 8K) passes in the value 80 not as a numeric value ten- #ut as the char1 acter 8 followed #y the character 0! The preprocessor provides two different operators for manipulatin& the strin&s passed in as parameters! 8irst is the stringi:ing o#erator- represented #y the - sym#ol- which returns a 5uoted- C strin& representation of the parameter! 8or e)ample- consider the followin& macro=
-define <C(E;=@;(n) *out Pn . has value . (n) endl
/ere- we take in a sin&le parameter- n! 0e then use the strin&izin& operator to print out a strin& representation of n- followed #y the value of the e)pression n! 8or e)ample- &iven the followin& code snippet=
int 7 = 8FG; <C(E;=@;(7 2 JH);
1 1@0 1
int 7 = 8FG; *out .7 2 JH.
. has value .
(7 2 JH)
endl;
"ote that after the a#ove pro&ram has #een compiled from C++ to machine code- any notions of the ori&inal varia#le 7 or the individual e)pressions makin& up the pro&ram will have #een completely eliminated- since varia#les e)ist only at the C++ level! /owever- throu&h the strin&izin& operator- it is possi#le to preserve a strin& version of portions of the C++ source code in the final pro&ram- as demonstrated a#ove! This is useful when writin& dia&nostic functions- as you'll see later in this chapter! The second preprocessor strin& manipulation operator is the string concatenation operator- also known as the to*en-#asting operator! This operator- represented #y --- takes the strin& value of a parameter and concatenates it with another strin&! 8or e)ample- consider the followin& macro=
-define 9E46ACEB:RBDAC(t%$e) t%$e my&PPtype
The purpose of this macro is to allow the user to specify a type Kfor e)ample- intL- and to automatically &enerate a varia#le declaration of that type whose name is m%Btype- where type is the parameter type) /ere- we use the -- operator to take the name of the type and concatenate it with the strin& m%B! Thus- &iven the followin& macro call=
9E46ACEB:RBDAC(int);
"ote that when workin& with the token1pastin& operator- if the result of the concatenation does not form a sin&le C++ token Ka valid operator or nameL- the #ehavior is undefined! 8or e)ample- callin& 9E46ACEB:RBDAC(*onst int) will have undefined #ehavior- since concatenatin& the strin&s m%B and *onst int does not yield a sin&le strin& Kthe result is *onst int m%B*onst intL! -ractical A&&lications of the -re&rocessor I5 9iagnostic 9ebugging !unctions 0hen writin& a pro&ram- at times you may want to ensure that certain invariants a#out your pro&ram hold true ( for e)ample- that certain pointers cannot #e E@66- that a value is always less than some constant- etc! 0hile in many cases these conditions should #e checked usin& a lan&ua&e feature called e7ce#tion handling- in several cases it is accepta#le to check them at runtime usin& a standard li#rary macro called assert! assert- e)ported #y the header *assert)- is a macro that checks to see that some condition holds true! 'f so- the macro has no effect! Htherwise- it prints out the statement that did not evaluate to true- alon& with the file and line num#er in which it was written- then terminates the pro&ram! 8or e)ample- consider the followin& code=
void :%&un*tion(int 2m%<tr) { assert!myPtr %, /U11$; 2m%<tr = 8FG; #
'f a caller passes a null pointer into :%&un*tion- the assert statement will halt the pro&ram and print out a messa&e that mi&ht look somethin& like this=
Assertion &ailed3 Am%<tr 0= E@66A3 &ile3 main.*$$, 6ine3 JH
7ecause assert a#ruptly terminates the pro&ram without &ivin& the rest of the application a chance to respondyou should not use assert as a &eneral1purpose error1handlin& routine! 'n practical software development- as,
1 1@1 1
sert is usually used to e)press pro&rammer assumptions a#out the state of e)ecution! 8or e)ample- assumin& we have some enumerated type 4olor- suppose we want to write a function that returns whether a color is a
/ere- if the color is Ced- Green- or Olue- we return that the color is indeed a primary color! Htherwise- we re1 turn that it is not a primary color! /owever- what happens if the parameter is not a valid 4olor- perhaps if the call is (s<rimar%4olor(4olor(,8))> 'n this function- since we assume that that the parameter is indeed a color- we mi&ht want to indicate that to the pro&ram #y e)plicitly puttin& in an assert test! /ere's a modified version of the function- usin& assert and assumin& the e)istence of a function (s4olor=
bool (s<rimar%4olor(4olor *) { assert!Is;olor!c$$; "" 1e assume that this is reall% a *olor. s'it*h(*) { *ase Ced3 *ase Green3 *ase Olue3 return true; default3 "2 =ther'ise, must not be a $rimar% *olor. 2" return false; # #
"ow- if the caller passes in an invalid 4olor- the pro&ram will halt with an assertion error pointin& us to the line that caused the pro#lem! 'f we have a &ood de#u&&er- we should #e a#le to fi&ure out which caller erroneously passed in an invalid 4olor and can #etter remedy the pro#lem! 0ere we to i&nore this case entirely- we mi&ht have considera#ly more trou#le de#u&&in& the error- since we would have no indication of where the pro#lem ori&inated! .ou should not- however- use assert to check that input from Get6ine is correctly1formed- for e)ample- since it makes far more sense to reprompt the user than to terminate the pro&ram! 0hile assert can #e used to catch a &ood num#er of pro&rammer errors durin& development- it has the unfortu1 nate side1effect of slowin& a pro&ram down at runtime #ecause of the overhead of the e)tra checkin& involved! Conse5uently- most ma*or compilers disa#le the assert macro in release or optimized #uilds! This may seem dan&erous- since it eliminates checks for pro#lematic input- #ut is actually not a pro#lem #ecause- in theory- you shouldn't #e compilin& a release #uild of your pro&ram if assert statements fail durin& e)ecution!Q 7ecause assert is entirely disa#led in optimized #uilds- you should use assert only to check that specific relations
Q 'n practice- this isn't always the case! 7ut it's still a nice theoryU
1 1@; 1
hold true- never to check the return value of a function! 'f an assert contains a call to a function- when as, sert is disa#led in release #uilds- the function won't #e called- leadin& to different #ehavior in de#u& and re1 lease #uilds! This is a persistent source of de#u&&in& headaches! Usin& the tools outlined in this chapter- it's possi#le for us to write our own version of the assert macro- which we'll call 4580M6Assert- to see how to use the preprocessor to desi&n such utilities! 0e'll split the work into two parts ( a function called 9o4580M6Assert- which actually performs the testin& and error1printin&- and the macro 4580M6Assert- which will set up the parameters to this function appropriately! The 9o4580M6Assert function will look like this=
-in*lude *stdlib) "" for abort();
"2 ;hese $arameters 'ill be assumed to be *orre*t. 2" void 9o4580M6Assert(bool invariant, string statement, string file, int line) { if(0invariant) { *err .4580M6Assert &ailed0. endl; *err .E7$ression3 . statement endl; *err .&ile3 . file endl; *err .6ine3 . line endl; abort(); "" Wuits $rogram and signals error to the =5. # #
This function takes in the e)pression to evaluate- alon& with a strin& representation of that statement- the name of the file it is found in- and the line num#er of the initial e)pression! 't then checks the invariant- and- if it failssi&nals an error and 5uits the pro&ram #y callin& abort()! Since these parameters are rather #ulky- we'll hide them #ehind the scenes #y writin& the 4580M6Assert macro as follows=
-define 4580M6Assert(e7$r) Do;S7-Y1Ussert!e#prB Pe#prB &&FI12&&B &&1I/2&&$
This macro takes in a sin&le parameter- an e)pression e7$r- and passes it in to the 9o4580M6Assert function! To set up the second parameter to 9o4580M6Assert- we &et a strin& representation of the e)pression usin& the strin&izin& operator on e7$r! 8inally- to &et the file and line num#ers- we use the special preprocessor sym#ols BB&(6EBB and BB6(EEBB! "ote that since the macro is e)panded at the call site- BB&(6EBB and BB6(EEBB refer to the file and line where the macro is used- not where it was declared! To see 4580M6Assert in action- suppose we make the followin& call to 4580M6Assert in m%file.*$$ at line 8FG! 3iven this code=
4580M6Assert(m%<tr 0= E@66);
0hich is e)actly what we want! "ow- suppose that we've used 4580M6Assert throu&hout a C++ pro&ram and have successfully de#u&&ed many of its parts! 'n this case- we want to disa#le 4580M6Assert for a release #uild- so that the final pro&ram
1 1@A 1
doesn't have the overhead of all the runtime checks! To allow the user to to&&le whether 4580M6Assert has any effect- we'll let them -define a constant- E=B4580M6BA55EC;- that disa#les the assertion! 'f the user does not define E=B4580M6BA55EC;- we'll use -define to have the 4580M6Assert macro perform the runtime checks! Htherwise- we'll have the macro do nothin&! This is easily accomplished usin& -ifndef !!! -else !!! -endif to determine the #ehavior of 4580M6Assert! This smart version of 4580M6Assert is shown #elow=
Pi nde -in*lude /<&;S7-Y1&USS2CT "" Enable assertions *stdlib) "" for abort();
"2 Eote that 'e define 9o4580M6Assert inside this blo*+, sin*e if 2 the ma*ro is disabled thereAs no reason to leave this fun*tion sitting 2 around. 2" void 9o4580M6Assert(bool invariant, string statement, string file, int line) { if(0invariant) { *err .4580M6Assert &ailed0. endl; *err .E7$ression3 . statement endl; *err .&ile3 . file endl; *err .6ine3 . line endl; abort(); "" Wuits $rogram and signals error to the =5. # # -define 4580M6Assert(e7$r) 9o4580M6Assert(e7$r, -e7$r, BB&(6EBB, BB6(EEBB) Pelse "" 9isable assertions "2 9efine 4580M6Assert as a ma*ro that e7$ands to nothing. 2 4580M6Assert in our *ode, it has absolutel% no effe*t. 2" Pde ine ;S7-Y1Ussert!e#pr$ TR nothing RT Pendi Eo', if 'e *all
-ractical A&&lications of the -re&rocessor II5 The D #acro Trick That macros &ive C++ pro&rams access to their own source code can #e used in other ways as well! Hne uncom1 mon pro&rammin& techni5ue that uses the preprocessor is known as the H -acro tric*- a way to specify data in one format #ut have it availa#le in several formats! 7efore e)plorin& the J Gacro trick- we need to cover how to redefine a macro after it has #een declared! $ust as you can define a macro #y usin& -define- you can also undefine a macro usin& -undef! The -undef prepro1 cessor directive takes in a sym#ol that has #een previously -defined and causes the preprocessor to i&nore the earlier definition! 'f the sym#ol was not already defined- the -undef directive has no effect #ut is not an error! 8or e)ample- consider the followin& code snippet=
-define :RB(E; 8FG int 7 = :RB(E;; "" :RB(E; is re$la*ed Punde 0N&I/T; int :RB(E; = JH; "" :RB(E; not re$la*ed
1 1@B 1
int 7 = 8FG; int :RB(E; = JH;
,lthou&h :RB(E; was once a -defined constant- after encounterin& the -undef statement- the preprocessor stopped treatin& it as such! Thus- when encounterin& int :RB(E; = JH- the preprocessor made no replace1 ments and the code compiled as written! To introduce the J Gacro trick- let's consider a common pro&rammin& pro#lem and see how we should &o a#out solvin& it! Suppose that we want to write a function that- &iven as an ar&ument an enumerated type- returns the strin& representation of the enumerated value! 8or e)ample- &iven the enum
enum 4olor {Ced, Green, Olue, 4%an, :agenta, Rello'#;
0e want to write a function called 4olor;o5tring that returns a strin& representation of the color! 8or e)1 ample- passin& in the constant Ced should hand #ack the strin& .Ced.- Olue should yield .Olue.- etc! Since the names of enumerated types are lost durin& compilation- we would normally implement this function usin& code similar to the followin&=
string 4olor;o5tring(4olor *) { s'it*h(*) { *ase Ced3 return .Ced.; *ase Olue3 return .Olue.; *ase Green3 return .Green.; *ase 4%an3 return .4%an.; *ase :agenta3 return .:agenta.; *ase Rello'3 return .Rello'.; default3 return . un+no'n).; # #
"ow- suppose that we want to write a function that- &iven a color- returns the opposite color!Q 0e'd need another function- like this one=
4olor Get=$$osite4olor(4olor *) { s'it*h(*) { *ase Ced3 return 4%an; *ase Olue3 return Rello'; *ase Green3 return :agenta; *ase 4%an3 return Ced; *ase :agenta3 return Green; *ase Rello'3 return Olue; default3 return *; "" @n+no'n *olor, undefined result # #
These two functions will work correctly- and there's nothin& functionally wron& with them as written! The pro#1 lem- thou&h- is that these functions are not scalable! 'f we want to introduce new colors- say- 1hite and Ola*+we'd need to chan&e #oth 4olor;o5tring and Get=$$osite4olor to incorporate these new colors! 'f we ac1 cidentally for&et to chan&e one of the functions- the compiler will &ive no warnin& that somethin& is missin& and we will only notice pro#lems durin& de#u&&in&! The pro#lem is that a color encapsulates more information than
Q 8or the purposes of this e)ample- we'll work with additive colors! Thus red is the opposite of cyan- yellow is the oppos1 ite of #lue- etc!
1 1@? 1
can #e e)pressed in an enumerated type! Colors also have names and opposites- #ut the C++ enum 4olor knows only a uni5ue '2 for each color and relies on correct implementations of 4olor;o5tring and Get=$, $osite4olor for the other two! Somehow- we'd like to #e a#le to &roup all of this information into one place! 0hile we mi&ht #e a#le to accomplish this usin& a set of C++ stru*t constants Ke!&! definin& a color stru*t and makin& *onst instances of these stru*ts for each colorL- this approach can #e #ulky and tedious! 'nsteadwe'll choose a different approach #y usin& J Gacros! The idea #ehind J Gacros is that we can store all of the information needed a#ove inside of calls to preprocessor macros! 'n the case of a color- we need to store a color's name and opposite! Thus- let's suppose that we have some macro called 9E&(EEB4=6=C that takes in two parameters correspondin& to the name and opposite color! 0e ne)t create a new file- which we'll call *olor.h- and fill it with calls to this 9E&(EEB4=6=C macro that e)1 press all of the colors we know Klet's i&nore the fact that we haven't actually defined 9E&(EEB4=6=C yetR we'll &et there in a momentL! This file looks like this= File% color.h
9E&(EEB4=6=C(Ced, 4%an) 9E&(EEB4=6=C(4%an, Ced) 9E&(EEB4=6=C(Green, :agenta) 9E&(EEB4=6=C(:agenta, Green) 9E&(EEB4=6=C(Olue, Rello') 9E&(EEB4=6=C(Rello', Olue)
Two thin&s a#out this file should *ump out at you! 8irst- we haven't surrounded the file in the traditional -ifndef !!! -endif #oilerplate- so clients can -in*lude this file multiple times! Second- we haven't provided an implementation for 9E&(EEB4=6=C- so if a caller does include this file- it will cause a compile1time error! 8or now- don't worry a#out these pro#lems ( you'll see why we've structured the file this way in a moment! et's see how we can use the J Gacro trick to rewrite Get=$$osite4olor- which for convenience is reprinted #elow=
4olor Get=$$osite4olor(4olor *) { s'it*h(*) { *ase Ced3 return 4%an; *ase Olue3 return Rello'; *ase Green3 return :agenta; *ase 4%an3 return Ced; *ase :agenta3 return Green; *ase Rello'3 return Olue; default3 return *; "" @n+no'n *olor, undefined result # #
/ere- each one of the *ase la#els in this switch statement is written as somethin& of the form
*ase color3 return opposite;
ookin& #ack at our *olor.h file- notice that each 9E&(EEB4=6=C macro has the form 9E&(EEB4=6=C(color, opposite)! This su&&ests that we could somehow convert each of these 9E&(EEB4=6=C statements into *ase la#els #y craftin& the proper -define! 'n our case- we'd want the -define to make the first parameter the ar&ument of the *ase la#el and the second parameter the return value! 0e can thus write this -define as
-define 9E&(EEB4=6=C(*olor, o$$osite) case color: return opposite;
4olor Get=$$osite4olor(4olor *) { s'it*h(*) { Pde ine D2FI/2&;<1<C!colorB opposite$ case color: return opposite; Pinclude "color.h" Punde D2FI/2&;<1<C default3 return *; "" @n+no'n *olor, undefined result. # #
This is pretty cryptic- so let's walk throu&h it one step at a time! 8irst- let's simulate the preprocessor #y repla1 cin& the line -in*lude .*olor.h. with the full contents of *olor.h=
4olor Get=$$osite4olor(4olor *) { s'it*h(*) { -define 9E&(EEB4=6=C(*olor, o$$osite) *ase *olor3 return o$$osite; D2FI/2&;<1<C!CedB ;yan$ D2FI/2&;<1<C!;yanB Ced$ D2FI/2&;<1<C!FreenB 0agenta$ D2FI/2&;<1<C!0agentaB Freen$ D2FI/2&;<1<C!(lueB Nello6$ D2FI/2&;<1<C!Nello6B (lue$ -undef 9E&(EEB4=6=C default3 return *; "" @n+no'n *olor, undefined result. # #
"ow- we replace each 9E&(EEB4=6=C #y instantiatin& the macro- which yields the followin&=
4olor Get=$$osite4olor(4olor *) { s'it*h(*) { case Ced: return ;yan; case (lue: return Nello6; case Freen: return 0agenta; case ;yan: return Ced; case 0agenta: return Freen; case Nello6: return (lue; -undef 9E&(EEB4=6=C default3 return *; "" @n+no'n *olor, undefined result. # #
8inally- we -undef the 9E&(EEB4=6=C macro- so that the ne)t time we need to provide a definition for 9E&(EEB4=6=C- we don't have to worry a#out conflicts with the e)istin& declaration! Thus- the final code for Get=$$osite4olor- after e)pandin& out the macros- yields
1 1@@ 1
0hich is e)actly what we wanted! The fundamental idea underlyin& the J Gacros trick is that all of the information we can possi#ly need a#out a color is contained inside of the file *olor.h! To make that information availa#le to the outside world- we em1 #ed all of this information into calls to some macro whose name and parameters are known! 0e do nothowever- provide an implementation of this macro inside of *olor.h #ecause we cannot anticipate every pos1 si#le use of the information contained in this file! 'nstead- we e)pect that if another part of the code wants to use the information- it will provide its own implementation of the 9E&(EEB4=6=C macro that e)tracts and formats the information! The #asic idiom for accessin& the information from these macros looks like this=
-define macroname(arguments) "2 some use for the arguments 2" -in*lude .filename. -undef macroname
/ere- the first line defines the mechanism we will use to e)tract the data from the macros! The second includes the file containin& the macros- which supplies the macro the data it needs to operate! The final step clears the macro so that the information is availa#le to other callers! 'f you'll notice- the a#ove techni5ue for implementin& Get=$$osite4olor follows this pattern precisely! 0e can also use the a#ove pattern to rewrite the 4olor;o5tring function! "ote that inside of 4olor;o, 5tring- while we can i&nore the second parameter to 9E&(EEB4=6=C- the macro we define to e)tract the in1 formation still needs to have two parameters! To see how to implement 4olor;o5tring- let's first revisit our ori&inal implementation=
string 4olor;o5tring(4olor *) { s'it*h(*) { *ase Ced3 return .Ced.; *ase Olue3 return .Olue.; *ase Green3 return .Green.; *ase 4%an3 return .4%an.; *ase :agenta3 return .:agenta.; *ase Rello'3 return .Rello'.; default3 return . un+no'n).; # #
string 4olor;o5tring(4olor *) { s'it*h(*) { "2 4onvert something of the form 9E&(EEB4=6=C(*olor, o$$osite) 2 into something of the form A*ase *olor3 return .*olor.A; 2" -define 9E&(EEB4=6=C(*olor, o$$osite) case color: return Pcolor; -in*lude .*olor.h. -undef 9E&(EEB4=6=C # # default3 return . un+no'n).;
'n this particular implementation of 9E&(EEB4=6=C- we use the strin&izin& operator to convert the *olor para1 meter into a strin& for the return value! 0e've used the preprocessor to &enerate #oth Get=$$osite4olor and 4olor;o5tringU There is one final step we need to take- and that's to rewrite the initial enum 4olor usin& the J Gacro trick! Htherwise- if we make any chan&es to *olor.h- perhaps renamin& a color or introducin& new colors- the enum will not reflect these chan&es and mi&ht result in compile1time errors! et's revisit enum 4olor- which is re1 printed #elow=
enum 4olor {Ced, Green, Olue, 4%an, :agenta, Rello'#;
0hile in the previous e)amples of 4olor;o5tring and Get=$$osite4olor there was a reasona#ly o#vious mappin& #etween 9E&(EEB4=6=C macros and *ase statements- it is less o#vious how to &enerate this enum us1 in& the J Gacro trick! /owever- if we rewrite this enum as follows=
enum 4olor { Ced, Green, Olue, 4%an, :agenta, Rello' #;
't should #e sli&htly easier to see how to write this enum in terms of J Gacros! 8or each 9E&(EEB4=6=C macro we provide- we'll simply e)tract the first parameter Kthe color nameL and append a comma! 'n code- this looks like
enum 4olor { -define 9E&(EEB4=6=C(*olor, o$$osite) colorB "" Eame follo'ed b% *omma -in*lude .*olor.h. -undef 9E&(EEB4=6=C #;
1 1@< 1
0hich is e)actly what we want! .ou may have noticed that there is a trailin& comma at after the final color KRello'L- #ut this is not a pro#lem ( it turns out that it's totally le&al C++ code! Analysis of the D #acro Trick The J Gacro1&enerated functions have several advanta&es over the hand1written versions! 8irst- the J macro trick makes the code considera#ly shorter! 7y relyin& on the preprocessor to perform the necessary e)pansionswe can e)press all of the necessary information for an o#*ect inside of an J Gacro file and only need to write the synta) necessary to perform some task once! Second- and more importantly- this approach means that addin& or removin& 4olor values is simple! 0e simply need to add another 9E&(EEB4=6=C definition to *olor.h and the chan&es will automatically appear in all of the relevant functions! 8inally- if we need to incorporate more in1 formation into the 4olor o#*ect- we can store that information in one location and let any callers that need it ac1 cess it without accidentally leavin& one out! That said- J Gacros are not a perfect techni5ue! The synta) is considera#ly trickier and denser than in the ori1 &inal implementation- and it's less clear to an outside reader how the code works! Femem#er that reada#le code is *ust as important as correct code- and make sure that you've considered all of your options #efore settlin& on J Gacros! 'f you're ever workin& in a &roup and plan on usin& the J Gacro trick- #e sure that your other &roup mem#ers are up to speed on the techni5ue and &et their approval #efore usin& it!Q
Q The J Gacro trick is a special case of a more &eneral techni5ue known as #re#rocessor meta#rogramming) 'f you're in1 terested in learnin& more a#out preprocessor metapro&rammin&- consider lookin& into the 7oost Getapro&rammin& i#1 rary KGC L- a professional C++ packa&e that simplifies common metapro&rammin& tasks!
''ve com#ined the 4Gore to D)plore6 and 4Cractice Cro#lems6 sections #ecause many of the topics we didn't cover in &reat detail in this chapter are #est understood #y playin& around with the material! /ere's a samplin& of different preprocessor tricks and techni5ues- mi)ed in with some pro&rammin& puzzles= 1! ist three ma*or differences #etween -define and the *onst keyword for definin& named constants! ;! 3ive an e)ample- #esides preventin& pro#lems from -in*lude1in& the same file twice- where -ifdef and -ifndef mi&ht #e useful! <+int% &hat i" you(re wor*ing on a #roject that must run on &indows, -ac 5, H, and Linu7 and want to use #lat"orm-s#eci"ic "eatures o" each'= A! 0rite a re&ular C++ function called :a7 that returns the lar&er of two int values! D)plain why it does not have the same pro#lems as the macro :AP covered earlier in this chapter! B! 3ive one advanta&e of the macro :AP over the function :a7 you wrote in the previous pro#lem! <+int% &hat is the !alue o" ax(!."#, !.$%)' &hat is the !alue o" &'(!."#, !.$%)'= ?! The followin& C++ code is ille&al #ecause the -if directive cannot call functions=
bool (s<ositive(int 7) { return 7 0; # Pi IsPositi)e!0N&;</STU/T$ TT .-- 2rror occurs here -define result true -else -define result false -endif
3iven your knowled&e of how the preprocessor works- e)plain why this restriction e)ists! M 6! Compilers rarely inline recursive functions- even if they are e)plicitly marked inline! 0hy do you think this is> @! Gost of the ST al&orithms are inlined! Considerin& the comple)ity of the implementation of a**umu, late from the chapter on ST al&orithms- e)plain why this is! E! , common #ut nonstandard variant of the assert macro is the verif% macro which- like assertchecks a condition at runtime and prints and error and terminates if the condition is false! /owever- in optimized #uilds- verif% is not disa#led- #ut simply does not a#ort at runtime if the condition is false! This allows you to use verif% to check the return value of functions directly K2o you see why>L! Cre1 ate a function called 4580M6Derif% that- unless the sym#ol E=B4580M6BDEC(&R is defined- checks the parameter and a#orts the pro&ram if it is false! Htherwise- if E=B4580M6BDEC(&R is defined- check the condition- #ut do not terminate the pro&ram if it is false! <! ,nother common de#u&&in& macro is a 4not reached6 macro which automatically terminates the pro1 &ram if e)ecuted! 4"ot reached6 macros are useful inside constructs such as s'it*h statements where the default la#el should never #e encountered! 0rite a macro- 4580M6EotCea*hed- that takes in a strin& parameter and- if e)ecuted- prints out the strin&- file name- and line num#er- then calls abort to end the pro&ram! ,s with 4580M6Assert and 4580M6Derif%- if the user -defines the sym#ol E=B4580M6BE=;CEA4/E9- chan&e the #ehavior of 4580M6EotCea*hed so that it has no effect! M 10! 'f you've done the two previous pro#lems- you'll notice that we now have three constantsE=B4580M6BA55EC;- E=B4580M6BDEC(&R- and E=B4580M6BE=;CEA4/E9- that all must #e -defined to disa#le them at runtime! This can #e a hassle and could potentially #e incorrect if we acci1 dentally omit one of these sym#ols! 0rite a code snippet that checks to see if a sym#ol named 9(5, AO6EBA66B4580M6B9EO@G is defined and- if so- disa#les all of the three aforementioned de#u&&in& tools! /owever- still &ive the user the option to selectively disa#le the individual functions!
1 1E1 1
11! Godify the earlier definition of enum 4olor such that after all of the colors defined in *olor.h- there is a special value- E=;BAB4=6=C- that specifies a none)istent color! <+int% o you actually need to change color.h to do this'= M 1;! Usin& J Gacros- write a function 5tring;o4olor which takes as a parameter a string and returns the 4olor o#*ect whose name e7actly matches the input strin&! 'f there are no colors with that name- re1 turn E=;BAB4=6=C as a sentinel! 8or e)ample- callin& 5tring;o4olor(.Green.) would return the value Green- #ut callin& 5tring;o4olor(.green.) or 5tring;o4olor(.=live.) should #oth re1 turn E=;BAB4=6=C! 1A! Suppose that you want to make sure that the enumerated values you've made for 4olor do not conflict with other enumerated types that mi&ht #e introduced into your pro&ram! Godify the earlier definition of 9E&(EEB4=6=C used to define enum 4olor so that all of the colors are prefaced with the identifier e4olorB! 8or e)ample- the old value Ced should chan&e to e4olorBCed- the old Olue would #e e4o, lorBOlue- etc! 2o not chan&e the contents of *olor.h! <+int% 2se one o" the #re#rocessor string-mani#ulation o#erators= M 1B! The -error directive causes a compile1time error if the preprocessor encounters it! This may sound stran&e at first- #ut is an e)cellent way for detectin& pro#lems durin& preprocessin& that mi&ht snow#all into lar&er pro#lems later in the code! 8or e)ample- if code uses compiler1specific features Ksuch as the HpenGC li#raryL- it mi&ht add a check to see that a compiler1specific -define is in place- usin& -er, ror to report an error if it isn't! The synta) for -error is -error message- where message is a mes1 sa&e to the user e)plainin& the pro#lem! Godify *olor.h so that if a caller -in*ludes the file without first -define1in& the 9E&(EEB4=6=C macro- the preprocessor reports an error containin& a messa&e a#out how to use the file! 1?! Suppose that you are desi&nin& a control system for an autonomous vehicle in the spirit of the 2,FC, 3rand Challen&e! ,s part of its initialization process- the pro&ram needs to call a function named (nit4riti*al(nfrastru*ture() to set up the car's sensor arrays! 'n order for the rest of the pro1 &ram to respond in the event that the startup fails- (nit4riti*al(nfrastru*ture() returns a bool indicatin& whether or not it succeeded! To ensure that the function call succeeds- you check the return value of (nit4riti*al(nfrastru*ture() as follows=
assert((nit4riti*al(nfrastru*ture());
2urin& testin&- your software #ehaves admira#ly and you mana&e to secure fundin&- fame- and presti&e! .ou then compile your pro&ram in release mode- install it in a production car- and to your horror find that the car immediately drives off a cliff! ater analysis determines that the cause of the pro#lem was that (nit4riti*al(nfrastru*ture had not #een called and that conse5uently the sensor array had failed to initialize! 0hy did the release version of the pro&ram not call (nit4riti*al(nfrastru*ture> /ow would you rewrite the code that checks for an error so that this pro#lem doesn't occur>
1 1E; 1
16! 'f you're up for a challen&e- consider the followin& pro#lem! 7elow is a ta#le summarizin& various units of len&th= Enit <ame Geter Centimeter Kilometer 8oot 'nch Gile ,stronomical Unit i&ht .ear Cu#itQ Lmeters = unit 1!0 0!01 1000!0 0!A0BE 0!0;?B 160<!ABB 1!B<6 ) 10 0!??
11 1?
Suffi% m cm km ft in mi ,U ly cu#it
System Getric Getric Getric Dn&lish Dn&lish Dn&lish ,stronomical ,stronomical ,rchaic
<!B61 e 10
aL Create a file called units.h that uses the J macro trick to encode the a#ove ta#le as calls to a macro 9E&(EEB@E(;! 8or e)ample- one entry mi&ht #e 9E&(EEB@E(;(:eter, 8.0, m, :et, ri*)! #L Create an enumerated type- 6ength@nit- which uses the suffi) of the unit- preceded #y e6ength@nitB- as the name for the unit! 8or e)ample- a cu#it is e6ength@nitB*ubit- while a mile would #e e6ength@nitBmi! ,lso define an enumerated value e6ength@nitBECC=C that serves as a sentinel indicatin& that the value is invalid! cL 0rite a function called 5uffi75tring;o6ength@nit that accepts a string representation of a suffi) and returns the 6ength@nit correspondin& to that strin&! 'f the string does not match the suffi)- return e6ength@nitBECC=C! dL Create a stru*t- 6ength- that stores a double and a 6ength@nit! 0rite a function Cead6ength that prompts the user for a double and a string representin& an amount and a unit suffi) and stores data in a 6ength! 'f the strin& does not correspond to a suffi)- reprompt the user! .ou can modify the code for Get(nteger from the chapter on streams to make an implementation of GetCeal! eL Create a function- Get@nit;%$e- that takes in a 6ength and returns the unit system in which it oc1 curs Kas a stringL fL Create a function- <rint6ength- that prints out a 6ength in the format amount suffix (amount unitnames)! 8or e)ample- if a 6ength stores 10B!; miles- it would print out
80J.Hmi (80J.H :iles)
&L Create a function- 4onvert;o:eters- which takes in a 6ength and converts it to an e5uivalent len&th in meters! Surprisin&ly- this pro#lem is not particularly lon& ( the main challen&e is the user input- not the unit mana&ementU
Q There is no a&reed1upon standard for this unit- so this is an appro)imate avera&e of the various len&ths!
0eneric #rogramming is about abstracting and classi"ying algorithms and data structures) It gets its ins#iration "rom Inuth and not "rom ty#e theory) Its goal is the incremental construction o" systematic catalogs o" use"ul, e""icient and abstract algorithms and data structures) ( ,le) Stepanov- inventor of the ST ! OSte0@P Hne of the most important lessons an upstart computer scientist or software en&ineer can learn is decom#osition or "actoring ( #reakin& pro#lems down into smaller and smaller pieces and solvin& each su#pro#lem separately! ,t the heart of decomposition is the concept of generality ( code should avoid overspecializin& on a sin&le pro#1 lem and should #e ro#ust enou&h to adapt to other situations! Take as an e)ample the ST ! Father than special1 izin& the ST container classes on a sin&le type- the authors decided to parameterize the containers over the types they store! This means that the code written for the ve*tor class can #e used to store almost any typeand the ma$ can use ar#itrary types as keyIvalue pairs! Similarly- the ST al&orithms were desi&ned to operate on all types of iterators rather than on specific container classes- makin& them fle)i#le and adapta#le! The ST is an e)cellent e)ample of how versatile- fle)i#le- and powerful C++ templates can #e! 'n C++ a tem#late is *ust that ( a code pattern that can #e instantiated to produce a type or function that works on an ar#itrary type! Up to this point you've primarily #een a client of template code- and now it's time to &ear up to write your own templates! 'n this chapter we'll cover the #asics of templates and &ive a 5uick tour of how template classes and functions operate under the hood! 0e will make e)tensive use of templates later in this course and espe1 cially in the e)tended e)amples- and hopefully #y the time you've finished this course reader you'll have an ap1 preciation for *ust how versatile templates can #e! 9efining a Tem&late Class Hnce you've decided that the class you're writin& is #est parameterized over some ar#itrary type- you can indic1 ate to C++ that you're definin& a template class #y usin& the tem$late keyword and specifyin& what types the template should #e parameterized over! 8or e)ample- suppose that we want to define our own version of the $air struct used #y the ST ! 'f we want to call this struct :%<air and have it #e parameterized over two typeswe can write the followin&=
template .typename FirstTypeB typename SecondType> stru*t :%<air { FirstType first; SecondType se*ond; #;
/ere- the synta) tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) indicates to C++ that what follows is a template that is parameterized over two types- one called &irst;%$e and one called 5e*ond;%$e! The actual names of the parameters are unimportant as far as clients are concerned- much in the same way that the actual names of parameters to functions are unimportant! 8or e)ample- the a#ove definition is functionally e5uivalent to this one #elow=
template .typename <neB typename T6o> stru*t :%<air { <ne first; T6o se*ond; #;
1 1EB 1
0ithin the #ody of the template struct- we can use the names =ne and ;'o Kor &irst;%$e and 5e*ond;%$eL to refer to the types that the client specifies when she instantiates :%<air! "otice that when specifyin& the template ar&uments we used the t%$ename keyword to indicate that the para1 meter should #e a type- such as int or ve*tor double)! 'n some template code you will see the keyword *lass su#stituted for t%$ename- as in this e)ample=
tem$late class &irst;%$e, class 5e*ond;%$e) stru*t :%<air { &irst;%$e first; 5e*ond;%$e se*ond; #;
'n this instance- t%$ename and *lass are completely e5uivalent to one another! /owever- ' find the use of *lass misleadin& #ecause it incorrectly implies that the parameter must #e a class type! This is not the case ( you can still instantiate templates that are parameterized usin& *lass with primitive types like int or double! Thus for the remainder of this course reader we will use t%$ename instead of the *lass!Q To create an instance of :%<air specialized over any particular types- we use the Khopefully familiarL synta) #e1 low=
0yPair.intB string> one; "" A $air of an int and a string. one.first = 8FG; one.se*ond = .;em$lates are *ool0.;
Classes and structs are closely related to one another- so unsurprisin&ly the synta) for declarin& a template class is similar to that for a template struct! et's suppose that we want to convert our :%<air struct into a class with full encapsulation Ki!e! with accessor methods and constructors instead of e)posed data mem#ersL! Then we would #e&in #y declarin& :%<air as
template .typename FirstTypeB typename SecondType> class 0yPair { $ubli*3 "2 ... 2" $rivate3 &irst;%$e first; 5e*ond;%$e se*ond; #;
"ow- what sorts of functions should we define for our :%<air class> 'deally- we'd like to have some way of ac1 cessin& the elements stored in the pair- so we'll define a pair of functions get&irst and set&irst alon& with an e5uivalent get5e*ond and set5e*ond! This is shown here=
*lass
for
t%$ename
in this instance ( it's ille&al to declare a re&ular C++ class usin& the
t%$e,
1 1E? 1
"otice that we're usin& the template ar&uments &irst;%$e and 5e*ond;%$e to stand for whatever types the client parameterizes :%<air over! 0e don't need to indicate that &irst;%$e and 5e*ond;%$e are at all differ1 ent from other types like int or string- since the C++ compiler already knows that from the tem$late declar1 ation! 'n fact- with a few minor restrictions- once you've defined a template ar&ument- you can use it anywhere that an actual type could #e used and C++ will understand what you mean! "ow that we've declared these functions- we should &o a#out implementin& them in the intuitive way! 'f :%<air were not a template class- we could write the followin&=
"2 Eote3 ;his is not legal *ode0 2" &irst;%$e :%<air33get&irst() { return first; #
The pro#lem with this a#ove code is that :%<air is a class tem#late- not an actual class! 'f we don't tell C++ that we're tryin& to implement a mem#er function for a template class- the compiler won't understand what we mean! Thus the proper way to implement this mem#er function is
template .typename FirstTypeB typename SecondType> &irst;%$e :%<air.FirstTypeB SecondType>33get&irst() { return first; #
/ere- we've e)plicitly prefaced the implementation of get&irst with a template declaration and we've marked that the mem#er function we're implementin& is for :%<air &irst;%$e, 5e*ond;%$e)! The template de1 claration is necessary for C++ to fi&ure out what &irst;%$e and 5e*ond;%$e mean here- since without this in1 formation the compiler would think that &irst;%$e and 5e*ond;%$e were actual types instead of placeholders for types! That we've mentioned this function is availa#le inside :%<air &irst;%$e, 5e*ond;%$e) instead of *ust :%<air is also mandatory since there is no real :%<air class ( after all- :%<air is a class tem#late- not an actual class! The other mem#er functions can #e implemented similarly! 8or e)ample- here's an implementation of set, 5e*ond=
template .typename FirstTypeB typename SecondType> void 0yPair.FirstTypeB SecondType>33set5e*ond(5e*ond;%$e ne'Dalue) { se*ond = ne'Dalue; #
1 1E6 1
0hen implementin& mem#er functions for template classes- you do not need to repeat the template definition if you define the function inside the #ody of the template class! Thus the followin& code is perfectly le&al=
tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) *lass :%<air { $ubli*3 FirstType getFirst!$ { return first; # )oid setFirst!FirstType ne65alue$ { first = ne'Dalue; # SecondType getSecond!$ { return se*ond; # )oid setSecond!SecondType ne65alue$ { se*ond = ne'Dalue; # $rivate3 &irst;%$e first; 5e*ond;%$e se*ond; #;
"ow- let's suppose that we want to define a mem#er function called s'a$ which accepts as input a reference to another :%<air class- then swaps the elements in that :%<air with the elements in the receiver o#*ect! Then we can prototype the function like this=
tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) *lass :%<air { $ubli*3 &irst;%$e get&irst() { return first; # void set&irst(&irst;%$e ne'Dalue) { first = ne'Dalue; # 5e*ond;%$e get5e*ond() { return se*ond; # void set5e*ond(5e*ond;%$e ne'Dalue) { se*ond = ne'Dalue; # )oid s6ap!0yPairS other$; $rivate3 &irst;%$e first; 5e*ond;%$e se*ond; #;
1 1E@ 1
Dven thou&h :%<air is a template class parameterized over two ar&uments- inside the #ody of the :%<air tem1 plate class definition we can use the name :%<air without mentionin& that it's a :%<air &irst;%$e, 5e*ond;%$e)! This is perfectly le&al C++ and will come up more when we #e&in discussin& copyin& #ehavior in a few chapters! The actual implementation of s'a$ is left as an e)ercise! .h and .c&& files for tem&late classes 0hen writin& a C++ class- you normally partition the class into two files= a !h file containin& the declaration and a !cpp file containin& the implementation! The C++ compiler can then compile the code contained in the !cpp file and then link it into the rest of the pro&ram when needed! 0hen writin& a template class- however- #reakin& up the definition like this will cause linker errors! The reason is that C++ templates are *ust that ( they're tem#lates for C++ code! 0henever you write code that instantiates a template class- C++ &enerates code for the par1 ticular instance of the class #y replacin& all references to the template parameters with the ar&uments to the tem1 plate! 8or e)ample- with the :%<air class defined a#ove- if we create a :%<air int, string)- the compiler will &enerate code internally that looks like this=
*lass :%<air int, string) { $ubli*3 int get&irst(); void set&irst(int ne'Dalue); string get5e*ond(); void set5e*ond(string ne'Dalue); $rivate3 int first; string se*ond; # int :%<air int, string)33get&irst() { return first; # void :%<air int, string)33set&irst(int ne'Dalue) { first = ne'Dalue; # string :%<air int, string)33get5e*ond() { return se*ond; # void :%<air int, string)33set5e*ond(string ne'Dalue) { se*ond = ne'Dalue; #
,t this point- compilation continues as usual! 7ut what would happen if the compiler didn't have access to the implementation of the :%<air class> That islet's suppose that we've created a header file- m%,$air.h- that contains only the class declaration for :%<air- as shown here=
-ifndef :%<airB(n*luded "" (n*lude guard $revents multi$le in*lusions -define :%<airB(n*luded tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) *lass :%<air { $ubli*3 &irst;%$e get&irst(); void set&irst(&irst;%$e ne'Dalue); 5e*ond;%$e get5e*ond(); void set5e*ond(5e*ond;%$e ne'Dalue); $rivate3 &irst;%$e first; 5e*ond;%$e se*ond; #; -endif
Suppose that we have a file that -in*ludes the m%,$air.h file and then tries to use the :%<air class! Since all that the compiler has seen of :%<air is the a#ove class definition- the compiler will only &enerate the follow1 in& code for :%<air=
*lass :%<air int, string) { $ubli*3 int get&irst(); void set&irst(int ne'Dalue); string get5e*ond(); void set5e*ond(string ne'Dalue); $rivate3 int first; string se*ond; #
"otice that while all the mem#er functions of :%<air int, string) have #een #rototy#ed- they haven't #een im#lemented #ecause the compiler didn't have access to the implementations of each of these mem#er functions! 'n other words- if a template class is instantiated and the compiler hasn't seen implementations of its mem#er functions- the resultin& template class will have no code for its mem#er functions! This means that the pro&ram won't link- and our template class is now useless! 0hen writin& a template class for use in multiple files- the entire class definition- includin& implementations of mem#er functions- must #e visi#le in the header file! Hne way of doin& this is to create a !h file for the template class that contains #oth the class definition and implementation without creatin& a matchin& !cpp file! This is the approach adopted #y the C++ standard li#raryR if you open up any of the headers for the ST - you'll find the complete Kand crypticL implementations of all of the functions and classes e)ported #y those headers! To &ive a concrete e)ample of this approach- here's what the m%,$air.h header file mi&ht look like if it con1 tained #oth the class and its implementation=
1 1E< 1
"2 ;his method of $a*+aging the .h".*$$ $air $uts the entire *lass definition and 2 im$lementation into the .h file. ;here is no .*$$ file for this header. 2" -ifndef :%<airB(n*luded -define :%<airB(n*luded tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) *lass :%<air { $ubli*3 &irst;%$e get&irst(); void set&irst(&irst;%$e ne'Dalue); 5e*ond;%$e get5e*ond(); void set5e*ond(5e*ond;%$e ne'Dalue); $rivate3 &irst;%$e first; 5e*ond;%$e se*ond; #; tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) &irst;%$e :%<air &irst;%$e, 5e*ond;%$e)33get&irst() { return first; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set&irst(&irst;%$e ne'Dalue) { first = ne'Dalue; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) 5e*ond;%$e :%<air &irst;%$e, 5e*ond;%$e)33get5e*ond() { return se*ond; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set5e*ond(5e*ond;%$e ne'Dalue) { se*ond = ne'Dalue; # -endif
Cuttin& the class and its definition inside the header file is a valid way to prevent linker errors- #ut it seems to vi1 olate the principle of separation of interface and implementation! ,fter all- the reason we have #oth !h and !cpp files is to hide a class implementation in a file that clients never have to look at! Cuttin& the entire implementa1 tion into the !h file thus seems mis&uided! 's there some way to maintain the traditional !hI!cpp split with tem1 plate classes> .es- thou&h you mi&ht want to #uckle up- since this ne)t section is rather technical! The trick to splittin& a template class into a !hI!cpp pair is to write the two files- then to -in*lude the !cpp file at the end of the !h file! Since -in*lude1in& the !cpp file at the end of the !h file splices the contents of the !cpp file into the !h file- this approach is functionally e5uivalent to writin& one lar&e !h file! /owever- if you do split the file up into two parts- you should make sure to not include the )c## "ile in the list o" "iles to com#ile ! The
1 1<0 1
reason is that -in*lude1in& the !cpp file inside the !h file leads to a circular dependency! To see how this hap1 pens- suppose that we partition the code into a !h file and a !cpp file as shown here= File% my-pair.h
"2 ;his method of $a*+aging the .h".*$$ $air $uts the *lass definition into the .h 2 file and im$lementations into the .*$$ file, then -in*ludes the .*$$ file into 2 the .h file. 2" -ifndef :%<airB(n*luded -define :%<airB(n*luded tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) *lass :%<air { $ubli*3 &irst;%$e get&irst(); void set&irst(&irst;%$e ne'Dalue); 5e*ond;%$e get5e*ond(); void set5e*ond(5e*ond;%$e ne'Dalue); $rivate3 &irst;%$e first; 5e*ond;%$e se*ond; #; Pinclude "my-pair.cpp" -endif "" (m$ort the im$lementation into the header
File% my-pair.cpp
Pinclude "my-pair.h" "" ;his is $roblemati*, see belo'. tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) &irst;%$e :%<air &irst;%$e, 5e*ond;%$e)33get&irst() { return first; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set&irst(&irst;%$e ne'Dalue) { first = ne'Dalue; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) 5e*ond;%$e :%<air &irst;%$e, 5e*ond;%$e)33get5e*ond() { return se*ond; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set5e*ond(5e*ond;%$e ne'Dalue) { se*ond = ne'Dalue; #
1 1<1 1
et's suppose that the compiler now tries to compile m%,$air.*$$! The first thin& it does is try to -in*lude m%,$air.h into the file- resultin& in the followin& code Kwith the comments removedL
Pi nde 0yPair&Included Pde ine 0yPair&Included template .typename FirstTypeB typename SecondType> class 0yPair * public: FirstType getFirst!$; )oid setFirst!FirstType ne65alue$; SecondType getSecond!$; )oid setSecond!SecondType ne65alue$; pri)ate: FirstType irst; SecondType second; 4; Pinclude "my-pair.cpp" Pendi tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) &irst;%$e :%<air &irst;%$e, 5e*ond;%$e)33get&irst() { return first; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set&irst(&irst;%$e ne'Dalue) { first = ne'Dalue; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) 5e*ond;%$e :%<air &irst;%$e, 5e*ond;%$e)33get5e*ond() { return se*ond; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set5e*ond(5e*ond;%$e ne'Dalue) { se*ond = ne'Dalue; #
"ow- the compiler will try to -in*lude m%,$air.*$$- which splices the ori&inal contents of the file bac* into itsel"! This results in the followin& code=
1 1<; 1
-ifndef :%<airB(n*luded -define :%<airB(n*luded tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) *lass :%<air { $ubli*3 &irst;%$e get&irst(); void set&irst(&irst;%$e ne'Dalue); 5e*ond;%$e get5e*ond(); void set5e*ond(5e*ond;%$e ne'Dalue); $rivate3 &irst;%$e first; 5e*ond;%$e se*ond; #; Pinclude "my-pair.h" template .typename FirstTypeB typename SecondType> FirstType 0yPair.FirstTypeB SecondType>::getFirst!$ * return irst; 4 template .typename FirstTypeB typename SecondType> )oid 0yPair.FirstTypeB SecondType>::setFirst!FirstType ne65alue$ * irst , ne65alue; 4 template .typename FirstTypeB typename SecondType> SecondType 0yPair.FirstTypeB SecondType>::getSecond!$ * return second; 4 template .typename FirstTypeB typename SecondType> )oid 0yPair.FirstTypeB SecondType>::setSecond! SecondType ne65alue$ * second , ne65alue; 4 -endif tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) &irst;%$e :%<air &irst;%$e, 5e*ond;%$e)33get&irst() { return first; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set&irst(&irst;%$e ne'Dalue) { first = ne'Dalue; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) 5e*ond;%$e :%<air &irst;%$e, 5e*ond;%$e)33get5e*ond() { return se*ond; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set5e*ond( 5e*ond;%$e ne'Dalue) { se*ond = ne'Dalue; #
1 1<A 1
"e)t- the preprocessor will -in*lude "my-pair.h" a&ain! 8ortunately- we've surrounded m%,$air.h in an include &uard- so nothin& happens! ,fter applyin& the effects of the -ifndef and -define directives- we end up with the followin& code &ettin& handed off to the rest of the compiler=
tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) *lass :%<air { $ubli*3 &irst;%$e get&irst(); void set&irst(&irst;%$e ne'Dalue); 5e*ond;%$e get5e*ond(); void set5e*ond(5e*ond;%$e ne'Dalue); $rivate3 &irst;%$e first; 5e*ond;%$e se*ond; #; tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) &irst;%$e :%<air &irst;%$e, 5e*ond;%$e)33get&irst() { return first; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set&irst(&irst;%$e ne'Dalue) { first = ne'Dalue; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) 5e*ond;%$e :%<air &irst;%$e, 5e*ond;%$e)33get5e*ond() { return se*ond; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set5e*ond( 5e*ond;%$e ne'Dalue) { se*ond = ne'Dalue; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) &irst;%$e :%<air &irst;%$e, 5e*ond;%$e)33get&irst() TT 2rror: rede inition { return first; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set&irst(&irst;%$e ne'Dalue) TT 2rror { first = ne'Dalue; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) 5e*ond;%$e :%<air &irst;%$e, 5e*ond;%$e)33get5e*ond() TT 2rror { return se*ond; # tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) void :%<air &irst;%$e, 5e*ond;%$e)33set5e*ond( 5e*ond;%$e ne'Dalue) TT 2rror { se*ond = ne'Dalue; #
1 1<B 1
'f you'll notice- the contents of our !cpp file appear here twice ( once from the ori&inal !cpp file and once #ecause the header file -in*luded the source file #ack into itself! 0hen the compiler looks at this code- it will &enerate a compile1time error #ecause we've provided two definitions for each mem#er function and the compiler can't tell which one to use! That was 5uite an comple) chain of events culminatin& in a compile1time error- and everythin& seemed to stem from the fact that the !h and !cpp files each -in*lude each other! 0e know that the !h file has to -in*lude the !cpp file- #ut does the !cpp file have to -in*lude the !h> The answer is no- unless the )c## "ile ends u# getting com#iled! 'f that happens and the !h file isn't -in*luded- then the compiler will trip up over the definitions of mem#er functions for a template class it hasn't seen yet! 'n short- if we split the class into a !hI!cpp pair- we have to #e very careful that the !cpp file isn't compiled alon& with the rest of our source code! Htherwise- we'll &et a nasty set of compiler errors! 8ortunately- there's a cute preprocessor trick we can use to let the !cpp file #e compiled alon& with other files in the pro*ect! et's review our current situation! 0e want the contents of the !cpp file to #e availa#le to the !h file- since clients of the template class need to have the full template definition availa#le- #ut we don(t want those contents to #e visi#le if we *ust try compilin& the !cpp file #y itself! The trick is to surround the !cpp file in a -ifdef ... -endif #lock checkin& whether a sym#ol particular to the !h file is defined! 'f we try compilin& the !cpp file directly- this sym#ol won't #e defined and the code won't #e com1 piled! 'nside the !h file- however- we can surround the directive to -in*lude the source file with a -define and -undef pair to define this particular sym#ol- include the !cpp file- then undefine the sym#ol! This makes the sym#ol availa#le to the !cpp file- and it will #e spliced into the header correctly! This version of the code looks as follows= File% my-pair.h
"2 ;his method of $a*+aging the .h".*$$ $air $uts the *lass definition into the .h 2 file and im$lementations into the .*$$ file, then -in*ludes the .*$$ file into 2 the .h file. 2" -ifndef :%<airB(n*luded -define :%<airB(n*luded tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) *lass :%<air { "2 ... omitted for brevit% ... 2" #; Pde ine Use0yPair;pp TT 0a+e symbol a)ailable here... Pinclude "my-pair.cpp" Punde Use0yPair;pp TT ... but no6here else -endif
File% my-pair.cpp
Pi de Use0yPair;pp TT <nly compile this ile i it@s being used by the .h ile tem$late t%$ename &irst;%$e, t%$ename 5e*ond;%$e) &irst;%$e :%<air &irst;%$e, 5e*ond;%$e)33get&irst() { return first; # "2 ... et*. ... 2" Pendi
1 1<? 1
0e now have two ways of packa&in& a template class in a header file= put the entire template definition inside the header file- or partition it into a !hI!cpp pair with a #it of preprocessor machinery! 3iven the choice #etween these two options- ' personally find the monolithic !h file approach much more appealin&- thou&h you're free to use whichever you prefer! Tem&late !unctions ,s you saw with the ST - templates come in two forms ( class templates- as descri#ed a#ove- and function tem1 plates! The function templates you're most familiar with ri&ht now are the ST al&orithms- which can accept any form of iterator you provide as an ar&ument- whether raw C++ pointers- ve*tor int)33iterators- or os, treamBiterators! 0hile function templates are in many ways similar to class templates- they differ si&nific1 antly in a few aspects and thus we'll devote some time to their nuances! et's suppose that we want to write a template function called :a+e(ntoDe*tor which accepts as input an ele1 ment of any type and a len&th- then returns a ve*tor consistin& of the specified num#er of copies of that ele1 ment! This is admittedly a simple function- #ut it helps illustrate some of the important points of writin& a tem1 plate function! Since our :a+e(ntoDe*tor function can accept an o#*ect of any type- we'll make :a+e(ntoDe*tor a tem1 plate function parameterized over the type of the parameter! Hne possi#le implementation is as follows=
template .typename 2lemType> ve*tor 2lemType) :a+e(ntoDe*tor(2lemType e, int num4o$ies) { ve*tor 2lemType) result(num4o$ies, e); return result; #
ike a template class- the template function #e&ins with a tem$late declaration indicatin& what the ar&uments to the template are! 0e then write the function under the assumption that Elem;%$e is a valid type- and end up with a very concise function! /ad we wanted to parameterize the function over more types- we could have ad1 ded them to the template ar&ument list! "ow that we've written :a+e(ntoDe*tor- we can call the function as follows=
*out .Enter string3 .; string line = Get6ine(); ve*tor string) v = 0a+eInto5ector!lineB 7DE$;
Hr to #e more concise=
*out .Enter string3 .; ve*tor string) v = 0a+eInto5ector!Fet1ine!$B 7DE$;
"otice that althou&h :a+e(ntoDe*tor is a template function- we didn't need to specify that we were callin& :a+e(ntoDe*tor string)! 'nstead- the C++ compiler was a#le to infer that- since the first ar&ument to the function has type string- the template parameter must #e a string! This is known as ty#e in"erence and is one of the more powerful features of the C++ template system! 0hy doesn't C++ have support for automatically inferrin& the type of a class template> That is- why can't we *ust write
)ector v = :a+e(ntoDe*tor(Get6ine(), 8FG);
1 1<6 1 'nstead of
)ector.string> v = :a+e(ntoDe*tor(Get6ine(), 8FG);
The answer has to do with what information C++ has availa#le to it at the time that it needs to instantiate the template! 0hen workin& with template functions- C++ has full knowled&e of the types of its ar&uments and thus can try to deduce precisely what the template ar&uments must #e! 0hen workin& with template classeshowever- it is not always possi#le to determine the template ar&uments from conte)t! 8or e)ample- suppose we were to write
)ector ); "" /%$otheti*al onl%; not legal 4!! *ode
0hat type should we parameterize v over> There are no clues from conte)t- so we cannot determine its type here! 7ecause function templates have type inference while class templates do not- it is common to find template classes paired with template functions that produce instances of the template class! Hne e)ample you've already seen is the ma+eB$air template function which accepts two ar&uments and produces a $air of the proper type! , possi#le implementation of ma+eB$air is shown #elow=
tem$late t%$ename &irst, t%$ename 5e*ond) pair.FirstB Second> ma+eB$air(*onst &irst? one, *onst 5e*ond? t'o) { pair.FirstB Second> result(one, t'o); return result; #
'f we call this ma+eB$air function- C++ can deduce what types &irst and 5e*ond must #e and can construct a $air with those ar&uments! 'f we only need the $air as a parameter to a function Kfor e)ample- the ST ma$'s insert mem#er functionL- usin& ma+eB$air can save us some typin& #y automatically computin& the type of the ar&uments! 0e'll see this techni5ue used later when we talk a#out the ST fun*tional) li#rary! 'n some cases when you're writin& template functions C++ won't #e a#le to fi&ure out what types the template is parameterized over and you'll have to specify them manually! ,s an e)ample- suppose that we want to write a template function that- &iven a strin&- converts the contents of that strin& to a particular typeR that is- this function is a &eneralization of the 5tring;o(nteger and 5tring;oCeal functions from strutils.h! 0e'll call this function le7i*alB*ast in the spirit of C++'s castin& operators!Q Hne possi#le prototype is as follows=
tem$late typename TargetType) ;arget;%$e le7i*alB*ast (string to4onvert) { "2 ... see $ra*ti*e $roblems ... 2" #
"otice that this function is parameterized over a type called ;arget;%$e that appears nowhere in the function's ar&ument list! Template ar&ument type inference only applies to function ar&uments- and C++ will not infer the types of template parameters that appear only in return values! 8or e)ample- this code will not compile=
int m%(nteger = le7i*alB*ast(.8FG.); "" Error Q 'hat is ;arget;%$eS
0hy is there a distinction #etween ar&uments and return values> ,fter all- can't the compiler look at this code and realize that the return value should #e an int> Unfortunately- the answer is no! Consider this related code=
Q C++ has four castin& operators ( stati*B*ast- *onstB*ast- reinter$retB*ast- and d%nami*B*ast! These will #e covered later! The inspiration for the name le7i*alB*ast comes from the 7oost li#rary's template function of the same name!
1 1<@ 1
/ere- we're usin& le7i*alB*ast to try to convert a strin& representation of ;!@1E;E into an inte&er! 'f C++ uses le7i*alB*ast specialized over double- this code will work correctly Kal#eit with truncationL and set m%, (nteger to ;! 7ut if C++ chooses to use le7i*alB*ast specialized over int- the code mi&ht cause an error as le7i*alVcast realizes that ;!@1E;E isn't an int! The pro#lem is that there are many possi#le types for the return value- and anythin& that can #e implicitly converted to an int is a candidate! Father than &uessin&- in situations like these the compiler re5uires you to e)plicitly indicate the template ar&uments to the function as you would in a template class! Thus the correct version of the a#ove code is
int m%(nteger = le7i*alB*ast.int>(.8FG.);
Im&licit Interfaces Suppose that we're interested in writin& a function which returns the ma)imum of two int values! Then we could write a function like this=Q
int :a7(nt(int one, int t'o) { return one t'o S t'o 3 one; #
Similarly- we could write a function which returns the ma)imum of two doubles as
double :a79ouble(double one, double t'o) { return one t'o S t'o 3 one; #
0e could also write a function that returns which of two strings alpha#etically follows the other as
string :a75tring(string one, string t'o) { return one t'o S t'o 3 one; #
"otice that every sin&le one of these functions has the followin& form=
Type :a7(Type one, Type t'o) { return one t'o S t'o 3 one; #
This su&&ests that there is some way to unify all of these functions toðer into a sin&le template function! 'n1 deed there is- and one possi#le implementation is as follows=
template .typename T> T :a7(T one, T t'o) { return one t'o S t'o 3 one; #
"ow- to &et the ma)imum of two doubles we could call :a7(8.0, F.0)- and to &et the ma)imum of two ints we could call :a7(8FG, HG8I)!
Q 'n case you're not familiar with the S3 operator- see the chapter on the preprocessor for more info!
1 1<E 1
,n interestin& o#servation is that the meanin& of the one t'o depends on what type the user passes into the :a7 function! The mi&ht #e an inte&er comparison- a floatin&1point comparison- or even a le)ico&raphic com1 parison in the case of strings! Crovided that it's meanin&ful to have two o#*ects of the specified type compared #y the operator- we can instantiate :a7 over any ar&uments! 7ut what if we try to invoke :a7 on somethin& that cannot #e compared with the less1than operator- such as this struct>
stru*t :%5tru*t { int 7; double %; #;
'n this case- C++ will not somehow 4fi&ure out6 how to compare two o#*ects of the :%5tru*t type- nor will it compile the pro&ram into somethin& that crashes or results in undefined #ehavior! 'nstead- the pro&ram will &en1 erate a compile1time error #ecause C++ reco&nizes that there is no way to compare two :%5tru*ts usin& the less1than operator! Unfortunately- this mi&ht not #e the prettiest of error messa&esR when compilin& this e)act code on Tisual Studio ;00? ' received this error messa&e=
*3T...Tmain.*$$(F) 3 error 4HMGM3 binar% A A 3 A:%5tru*tA does not define this o$erator or a *onversion to a t%$e a**e$table to the $redefined o$erator *3T...Tmain.*$$(8K) 3 see referen*e to fun*tion tem$late instantiation A; :a7 :%5tru*t)(;,;)A being *om$iled 'ith [ ;=:%5tru*t ]
The compiler correctly reports that the error is that two :%5tru*ts cannot #e compared usin& the operator#ut does so in a rounda#out- convoluted way! "otice the key phrase 4function template instantiation #ein& com1 piled6 here ( any time you encounter an error like this- #e sure to look at what the template ar&uments are and where the actual template is! This is where you'll encounter the error! 'f you'll notice- we've written a template function that at first &lance can accept o#*ects of any type #ut which can only #e successfully instantiated over types that have a well1defined operator! This is perfectly accepta#le C++ pro&rammin& and in fact templates are often used to define functions that operate over any type meetin& some set of restrictions! These restrictions are sometimes called an im#licit inter"ace Kas opposed to e7#licit inter"aces defined #y inheritanceR see the chapter on inheritance for more informationL! ,lthou&h our :a7 function is &uaranteed to #e safe Ki!e! it will never compile when the elements can't #e com1 pared usin& L- there is nothin& in the function si&nature to e)plicitly indicate what restrictions apply to the tem1 plate ar&uments! To communicate these re5uirements to other coders- C++ pro&rammers commonly choose names for the template ar&ument that make these re5uirements more clear! 8or e)ample- since the template ar1 &ument to :a7 must #e compara#le usin& less1than- we mi&ht rewrite the function like this=
tem$late t%$ename 1essThan;omparable) 1essThan;omparable :a7(1essThan;omparable one, 1essThan;omparable t'o) { return one t'o S t'o 3 one; #
"amin& the template ar&ument 6ess;han4om$arable mi&ht help communicate these restrictions to other pro1 &rammers- #ut otherwise has no effect on the C++ pro&ram! 'n fact- it's *ust as effective as namin& a parameter to a function mustOeEonnegative ( it mi&ht help pro&rammers fi&ure out what your intentions are- #ut the compiler won't enforce it!
1 1<< 1
'n addition to template classes and template functions- you can define template member functions inside a class! The class itself need not #e a template class to contain a template mem#er function- thou&h it is often the case! 8or e)ample- suppose that we're writin& a class that acts as a wrapper around the streams li#rary to simplify file readin& and writin&! 0e're interested in e)portin& a function called ma$All which iterates over the characters in the file and calls a user1specified call#ack on each of them! 8or e)ample- &iven the followin& function=
void A$$end;o5tring(*har *h, string? out) { out != *h; #
'f the class we're desi&nin& is called &ile1ra$$er- we'd hope to #e a#le to do the followin&=
&ile1ra$$er f'(.m%,file.t7t.); string te7t; 6.mapUll!UppendToStringB te#t$;
/ere- the ma$All function accepts two parameters- the function to call and some user1supplied data to pass into the function- then calls the function once per character read passin& in the user1supplied data!Q Since the user could theoretically supply au)iliary data they chose- we should make ma$All a template function parameterized over the type of the user1supplied data! This is shown here=
*lass &ile1ra$$er { $ubli*3 "2 ... mis*ellaneous fun*tions ... 2" template .typename Uu#Type> )oid mapUll!)oid unction!charB Uu#TypeS$B Uu#TypeS au#Data$
#;
,s with mem#er functions of template classes- when implementin& template mem#er functions you must e)pli1 citly mark that the function is a template and should #e sure to put it in its proper !h file instead of in the !cpp file! 8or e)ample- here's how we'd #e&in implementin& ma$All=
template .typename Uu#Type> void &ile1ra$$er33ma$All(void fun*tion(*har, Uu#TypeS), Uu#TypeS au79ata) { "2 ... 2" #
"ow- suppose that we want to implement a similar function- e)cept inside of the CS1067IJ De*tor! Since the De*tor itself is a template class- the synta) for implementin& the function is sli&htly different! The function de1 claration itself is similar to the declaration in the &ile1ra$$er class=
tem$late t%$ename Elem;%$e) *lass De*tor { $ubli*3 "2 ... 2" template .typename Uu#Data> )oid mapUll!)oid unction!2lemTypeB Uu#DataS$B Uu#DataS au#Data$; #; Q 0hen we talk a#out functors you'll learn a much #etter way to do this! 8or now- thou&h- this approach is perfectly fine!
1 ;00 1
0hen implementin& the ma$All function- however- we have to use two template declarations ( one to indicate that the De*tor class is a template and one to indicate that ma$All is a template! 8or e)ample=
template .typename 2lemType> template .typename Uu#Data> void De*tor Elem;%$e)33void ma$All(void fun*tion(Elem;%$e, Au79ata?), Au79ata? au79ata) { "2 ... 2" #
That's 5uite a mouthful- and admittedly the synta) is a #it clunky- #ut we're now set up to implement the tem1 plate mem#er function of a template class! The Two #eanings of typename There is an unfortunate 5uirk in the C++ lan&ua&e that shows up when tryin& to access types defined inside of template classes! Suppose that we want to write a function accepts as input an ar#itrary ST container class con1 tainin& ints Kve*tor int)- set int)- etc!L and returns the num#er of elements in the container that are even! Since the function needs to accept any valid ST container- we write the function as a template- as shown here=
"2 1at*h out0 ;his *ode doesnAt *om$ile0 2" tem$late t%$ename 4ontainer;%$e) int 4ountEvenElements(4ontainer;%$e? *) { int result = 0; "2 (terate over the *ontainer *ounting evens. 2" for(4ontainer;%$e33iterator itr = *.begin(); itr 0= *.end(); !!itr) if(2itr Z H == 0) !!result; # return result;
'nitially this function looks pretty strai&htforward ( we simple use an iterator loop to walk over the elements of the container- incrementin& a counter as we &o- and finally return the counter! Unfortunately- this isn't le&al C++! 2ue to a rather o#scure technical reason- when inside the #ody of the template- C++ can't necessarily tell whether 4ontainer;%$e33iterator is a type called iterator nested inside the type 4ontainer;%$e or a class constant of 4ontainer;%$e called iterator! 0e therefore need to tell the C++ compiler that 4on, tainer;%$e33iterator is indeed the name of a nested type #y usin& the t%$ename keyword! The revised code is as follows=
tem$late t%$ename 4ontainer;%$e) int 4ountEvenElements(4ontainer;%$e? *) { int result = 0; "2 (terate over the *ontainer *ounting evens. 2" for(typename 4ontainer;%$e33iterator itr = *.begin(); itr 0= *.end(); !!itr) if(2itr Z H == 0) !!result; return result; #
"otice that we've put t%$ename in front of 4ontainer;%$e33iterator to tell C++ that iterator is a nes1 ted type! This is another use of the t%$ename keyword that crops up surprisin&ly often when writin& template code! 'f you ever need to access the name of a type nested inside a template class- make sure to preface the type with t%$ename or your code will not compile!
1 ;01 1
7e aware that you only have to preface the name of a type with t%$ename if you are writin& a template and need to access an inner class of a type that depends on a template ar&ument! Thus you don't need to Kand can't le&allyL put t%$ename in front of ve*tor int)33iterator since ve*tor int)33iterator is a complete type! .ou would- however- have to put t%$ename in front of ve*tor ;)33iterator if you were implement1 in& a template parameterized over ;! This is an arcane rule that complicates template code- #ut fortunately com1 pilers these days are very &ood at dia&nosin& missin& t%$enames! 'n fact- the inu) compiler &++ will actually &ive an error messa&e that e)plicitly tells you to insert the missin& t%$ename! #ore to $%&lore 0e've covered a &reat deal of template synta) and usa&e in this chapter- #ut we've #arely scratched the surface when it comes to templates! Godern C++ pro&rammin& relies e)tensively on clever and su#tle uses of tem1 plates- and there are many advanced techni5ues involvin& templates that are far #eyond the scope of this course reader! 0hile we will periodically revisit templates- there simply isn't enou&h room to cover them in their en1 tirety! 'f you're interested in learnin& more a#out templates- consider lookin& into the followin&= 1! <onCty&e Tem&late Arguments! ,ll of the template code you've seen so far is parameterized over vari1 a#le types- such as a ve*tor int) or a ma$ string, string)! /owever- it's possi#le to parameter1 ize C++ templates over other thin&s as well- such as ints- function pointers- or even other templates! 0hile non1type template ar&uments are uncommon- they are at times 5uite useful and there are many e)1 amples where they are an e)cellent solution to a difficult pro#lem! Consult a reference for more inform1 ation! 0e will see one e)ample of non1type template ar&uments in a later chapter! ;! Traits Classes! 0hen writin& template functions or classes- sometimes you will need additional inform1 ation a#out the structure of a particular template ar&ument! 8or e)ample- when writin& an ST al1 &orithm that works on an ar#itrary iterator ran&e- you mi&ht need temporary stora&e space to hold ele1 ments of the type #ein& iterated over! 3iven the type of an iterator- how can you determine what type of value it dereferences to> Usin& a techni5ue called traits classes- it's possi#le to inspect template ar&u1 ments to &ain additional information a#out them! 'n the case of ST iterators- the C++ standard li#rary provides a template class called iteratorBtraits that- when instantiated over a particular type- e)1 ports information a#out that type- such as its iterator cate&ory- the type it iterates over- etc! Traits classes are #ecomin& increasin&ly more relevant in &eneric pro&rammin&- and if you plan on usin& templates in an industrial settin& you will almost certainly encounter them! A! Tem&late S&eciali3ation! 't is possi#le to define a specific instance of a template class to use instead of the default version in certain circumstances! 8or e)ample- when implementin& a class akin to the ST set- you mi&ht #e a#le to improve memory usa&e when storin& a set *har) #y takin& advanta&e of the fact that there are only ;?6 possi#le values for a *har! Template specialization can #e used to op1 timize code for a particular application- to improve the accuracy of traits classes- or for far more creative purposes Ksee the ne)t item on this listL! 'f you plan on pursuin& template pro&rammin& more seriously#e sure to look into template specialization! B! Tem&late #eta&rogramming! Usin& a com#ination of the techni5ues listed a#ove- it is possi#le to write C++ code containin& tem#late meta#rograms! , template metapro&ram is a collection of tem1 plates and template specializations such that when one of the templates is instantiated- it fires off chains of further instantiations whose end result produces new C++ code! 8or e)ample- it's possi#le to write a template metapro&ram to compute at com#ile time whether a num#er is prime- or even to automatically &enerate a sortin& routine optimized for data of a particular size! 't has #een proven that template meta1 pro&rammin& is a Turin&1complete proper su#set of C++ that e)ecutes at compile1time- meanin& that template metapro&rams have the same computational power as the rest of the C++ lan&ua&e e)cept that they e)ecute inside the compiler! Code usin& template metapro&rammin& is notoriously difficult to read#ut the sheer firepower and performance &ains offered #y template metapro&rammin& cannot #e under1 stated! 'f you are up for a true challen&e- investi&ate template metapro&rammin& in more detail! .ou will not #e disappointed!
1! The ST al&orithms are implemented as template functions parameterized over the types of all of their ar&uments! This allows them to operate over any types of ar&uments- provided of course that the ar&u1 ments make sense! 8or e)ample- consider the transform al&orithm- which accepts four parameters ( two iterators definin& an input ran&e- and iterator definin& the start of an output ran&e- and a transforma1 tion function ( then applies the transformation function to all elements in the source ran&e and stores them in the output ran&e! Hne possi#le implementation of transform is as follows=
tem$late t%$ename (n$ut(terator, t%$ename =ut$ut(terator, t%$ename &un*tion) inline =ut$ut(terator transform((n$ut(terator start, (n$ut(terator end, =ut$ut(terator 'here, &un*tion fn) { 'hile(start 0= end) { 2'here = fn(2start); !!start; !!'here; # return 'here; "" Ceturn iterator one $ast the end of the 'ritten range #
/ere- the #ody of the function simply walks over the ran&e from start to end- at each point applyin& the transformation function and storin& the result in the output ran&e the starts with 'here! 7y conven1 tion- the al&orithm returns an iterator one past the end of the ran&e written! Usin& transform as a reference- implement an ST al&orithm1like function *o$%Bif which accepts as input three iterators ( two delineatin& an input ran&e and one delineatin& an output ran&e ( alon& with a predicate function- then copies the elements in the input ran&e for which the predicate is true to the out1 put! 8or e)ample- if v contains 0- 1- ;- A- B- ? and if (sEven returns whether an inte&er is even- then
*o$%Bif(v.begin(), v.end(), ostreamBiterator int)(*out, . .), (sEven);
should print out 0 H J! 'f you've templatized the function correctly- the fact that one of the parameters is an ostreamBiterator int) shouldn't #e a pro#lem! M ;! Hne particularly useful ST al&orithm is eVual- which is defined as follows=
tem$late t%$ename (n$ut(terator8, t%$ename (n$ut(teratorH) inline bool eVual((n$ut(terator8 start8, (n$ut(terator8 end8, (n$ut(teratorH startH) { 'hile(start8 0= end8) { if(2start8 0= 2startH) return false; !!start8; !!startH; # return true; #
The al&orithm walks over the ran&e defined #y Ostart8- end8L and Ostart8- start8 + Kend8 ( start8LL and returns whether the elements in the ran&e are e5ual! "otice that the al&orithm is templatized over two different types of input iterators! 0hy is this>
1 ;0A 1
A! Hne hassle with the eVual al&orithm is that it assumes that the input ran&es are the same size! 7ut what if we have two iterator ran&es containin& unknown num#ers of elements that we'd like to compare> 'n that case- we'd have to manually compute the size of the ran&es first- then call eVual if they a&reed! 8or random1access iterators this can #e implemented 5uickly- #ut for simple input iterators can #e inefficient! Godify the implementation of eVual &iven a#ove to create an al&orithm called rangesBareBeVual that accepts four inputs Ktwo pairs of iteratorsL and returns true if the ran&es are the same size and con1 tain the same elements! ,s an incentive- if you've written rangesBareBeVual correctly- you can check whether two files have the same contents in three lines of code=
ifstream in$ut8(.file8.t7t.); ifstream in$utH(.fileH.t7t.); return rangesBareBeVual(istreambu istreambu istreambu istreambu
istreambufBiterator is an iterator adapter that- like istreamBiterator- iterates over a stream readin& values! Unlike istreamBiterator- however- istreambufBiterator always reads raw val1 ues from the stream with get instead of usin& stream e)traction- so it doesn't skip whitespace! The iter1 ator is specialized over *har since we're readin& from an ifstream- which encodes characters as *hars! B! 'n the chapter on the preprocessor- we defined a :AP macro as follows= -define :AP(a, b) ((a) (b) S (b) 3 (a))
Compare this macro to the :a7 template function we wrote in this chapter and e)plain why the template function is safer! .ou mi&ht want to review the chapter on the preprocessor #efore answerin& this 5ues1 tion! ?! Hne of the motivations #ehind re5uirin& C++ pro&rammers to put the t%$ename keyword #efore access1 in& a type nested inside a templatized type was to prevent am#i&uity in templates! 8or e)ample- con1 sider the followin& code- which assumes the e)istence of a template type called :%;em$late=
tem$late t%$ename ;) void :%&un*tion(*onst ;? elem) { int itr; 'hile(true) { t%$ename :%;em$late ;)33(nner 2 itr; # #
Consider the line t%$ename :%;em$late ;)33(nner 2 itr! Since we have the t%$ename keyword here- the compiler understands that we're declarin& a pointer of type :%;em$late ;)33(n, ner2 called itr! 'f we don't put the t%$ename keyword in here- C++ will think that :%;em$late ;)33(nner is a class constant rather than a type! 0hat will the statement :%;em$late ;)33(nner 2 itr mean in this case> 2oes the difference #etween these two cases help e)plain why the t%$ename keyword is necessary>
1 ;0B 1
6! Usin& the code for Get(nteger we wrote in the streams chapter as a #ase- write a template function GetDalue which safely reads a value of any type from the console! The si&nature for this function should #e
tem$late t%$ename Dalue;%$e) Dalue;%$e GetDalue(string t%$e);
/ere- the t%$e ar&ument is a strin& containin& a human1reada#le description of the type that is dis1 played in any error messa&es that arise from invalid input! 8or e)ample- to read in an int- you would call GetDalue int)(.an integer.); M @! Godify the code for GetDalue to implement the le7i*alB*ast function! 'f an error occurs #ecause the strin& is not formatted properly- you can assume the e)istence of an Error function in the style of CS1067IJ! ater- when we cover e)ceptions- we'll see a #etter way to report an error! E! 0rite a template function 4lam$ that accepts three values ( a value #y reference to 4clamp-6 a lower #ound- and an upper #ound ( then updates the reference parameter so that it is #etween the lower and upper #ounds! 8or e)ample- if 7 has value B;- callin& 4lam$(7, 800, H00) would set 7 to 100 while callin& 4lam$(7, 0, 8FG) would leave 7 unchan&ed! 0hat is the implicit interface on the type of the reference parameter>
,t its core- C++ is a lan&ua&e #ased on modifyin& pro&ram state! ints &et incremented in for loopsR ve*tors have innumera#le calls to *lear- resiUe- and $ushBba*+R and console 'IH overwrites varia#les with values read directly from the user! 0hen desi&nin& or maintainin& a lar&e software system- it is important to track e)actly where side effects can occur! , mem#er function named get1idth- for e)ample- pro#a#ly should not chan&e the width of the receiver o#*ect since class clients would not e)pect the function to do so! Similarly- when passin& a varia#le #y reference to a function- it is critical that you are a#le to check whether the function will destructively modify the varia#le or whether it plans on leavin& it untouched! To to track and monitor side effects- C++ uses the *onst keyword! .ou have already seen *onst in the conte)t of &lo#al constants- #ut the *onst keyword has many other uses! This chapter introduces the mechanics of *onst Kfor e)ample- where *onst can #e used and what it means in these conte)tsL and how to properly use it in C++ code! A /ord on $rror #essages The error messa&es you'll &et if you accidentally #reak *onstness can #e intimidatin& and confusin&! Com1 monly you'll &et unusual errors a#out conversions losin& 5ualifiers or lvalues specifyin& *onst o#*ects! These are the telltale si&ns that you've accidentally tried to modify a *onst varia#le! ,lso- the errors for *onst can #e completely incomprehensi#le when workin& with template classes and the ST ! ,s with all ST errors- under1 standin& these error messa&es will only come with practice!
const Hariables
So far- you've only seen *onst in the conte)t of &lo#al constants! 8or e)ample- &iven the followin& &lo#al de1 claration=
*onst int :%4onstant = 8FG;
0henever you refer to the value :%4onstant in code- the compiler knows that you're talkin& a#out the value 1A@! 'f later in your pro&ram you were to write :%4onstant = JH- you'd &et a compile1time error since you would #e modifyin& a *onst varia#le! /owever- *onst is not limited to &lo#al constants! .ou can also declare local varia#les *onst to indicate that their values should never chan&e! Consider the followin& code snippet=
int length = m%De*tor.siUe(); for(int i = 0; i length; !!i) "2 ...9o something that doesnAt modif% the length of the ve*tor... 2"
/ere- we compute length only once since we know at compile1time that our operation isn't &oin& to modify the contents of m%De*tor! Since we don't have the overhead of a call to m%De*tor.siUe() every iteration- on some compilers this loop can #e a#out ten percent faster than if we had simply written the conventional for loop! 7ecause the len&th of the ve*tor is a constant- we know at compile1time that the varia#le length should never chan&e! 0e can therefore mark it *onst to have the compiler enforce that it must never chan&e- as shown here=
1 ;06 1
const int length = m%De*tor.siUe(); for(int i = 0; i length; !!i) "2 ...9o something that doesnAt modif% the length of the ve*tor... 2"
This code works identically to the ori&inal- e)cept that we've e)plicitly announced that we will not chan&e length! 'f we accidentally try to modify length- we'll &et a compile1time error directin& us to the offendin& line- rather than a runtime #u&! 'n essence- the compiler will help us de#u& our code #y convertin& a potential runtime #u& into an easily fi)a#le compile1time error! 8or e)ample- suppose we want to perform some special handlin& if the ve*tor is e)actly ten elements lon&! 'f we write if(length = 80) instead of if(length )= 80)- if length isn't declared *onst- our code will always e)ecute e)actly ten times and we'll &et stran&e #ehavior where an if statement e)ecutes more fre5uently than e)pected! Since assi&nments1in1if1 statements are perfectly le&al C++ code- the C++ compiler won't warn us of this fact and we will have to de#u& the pro&ram #y hand! /owever- if we mark length *onst- the compiler will &enerate an error when compilin& the statement if(length = 80) #ecause it contains an assi&nment to a *onst varia#le! "o lon&er will we have to track down errant runtime #ehavior ( for once it appears that the C++ compiler is helpin& us write &ood codeU
const and -ointers
The *onst keyword is useful #ut has its share of 5uirks! Cerhaps the most persistent source of confusion when workin& with *onst arises in the conte)t of pointers! 8or e)ample- suppose that you want to declare a C strin& as a &lo#al constant! Since to declare a &lo#al C++ string constant you use the synta)
*onst string +Global4$$5tring = .;his is a string0.;
.ou mi&ht assume that to make a &lo#al C strin& constant- the synta) would #e=
*onst charR +Global5tring = .;his is a string0.;
This synta) is partially correct! 'f you were ever to write k Global5tring[0] = APA- rather than &ettin& se&1 mentation faults at runtime Ksee the C strin&s chapter for more infoL- you'd &et a compiler error that would direct you to the line where you tried to modify the &lo#al constant! 7ut unfortunately this varia#le declaration isn't 5uite ri&ht! Suppose- for e)ample- that you write the followin& code=
+Global5tring = .Ceassigned0.;
/ere- you're reassi&nin& +Global5tring to point to the strin& literal 4Feassi&nedU6 "ote that you aren't modi1 fyin& the contents of the character se5uence +Global5tring points to ( instead you're chan&in& what character seAuence ()lobal*tring #oints to! 'n other words- you're modifyin& the #ointer- not the #ointee- so the a#ove line will compile correctly and other code that references +Global5tring will suddenly #e&in usin& the strin& 4Feassi&nedU6 instead of 4This is a strin&U6 as you would hope! 0hen workin& with *onst- C++ distin&uishes #etween two similar1soundin& entities= a #ointer-to-const and a const #ointer! , pointer1to1*onst is a pointer like +Global5tring that points to data that cannot #e modi1 fied! 0hile you're free to reassi&n pointers1to1*onst- you cannot chan&e the value of the elements they point to! To declare a pointer1to1*onst- use the synta) const TypeR myPointer- with the *onst on the left of the star! ,lternatively- you can declare pointers1to1*onst #y writin& Type constR myPointer! , const #ointer- on the other hand- is a pointer that cannot #e assi&ned to point to a different value! Thus with a *onst pointer- you can modify the #ointee #ut not the #ointer! To declare a *onst pointer- you use the synta) TypeR const my;onstPointer- with the *onst on the ri&ht side of the star! /ere- m%4onst<ointer can't #e reassi&ned- #ut you are free to modify the value it points to!
1 ;0@ 1
"ote that the synta) for a pointer1to1*onst is *onst ;%$e 2 $tr while the synta) for a *onst pointer is ;%$e 2 *onst $tr! The only difference is where the *onst is in relation to the star! Hne trick for remem1 #erin& which is which is to read the varia#le declaration from ri&ht1to1left! 8or e)ample- readin& *onst ;%$e 2 $tr #ackwards says that 4$tr is a pointer to a ;%$e that's *onst-6 while ;%$e 2 *onst $tr read #ack1 wards is 4$tr is a *onst pointer to a ;%$e!6 Feturnin& to the C strin& e)ample- to make +Global5tring #ehave as a true C strin& constant- we'd need to make the pointer #oth a *onst pointer and a pointer1to1*onst! This is totally le&al in C++- and the result is a *onst pointer1to1*onst! The synta) looks like this=
const *har 2 const +Global5tring = .;his is a string0.;
"ote that there are two *onsts here ( one #efore the star and one after it! /ere- the first *onst indicates that you are declarin& a pointer1to1*onst- while the second means that the pointer itself is *onst! Usin& the trick of readin& the declaration #ackwards- here we have 4+Global5tring is a *onst pointer to a *har that's *onst!6 This is the correct way to make the C strin& completely *onst- althou&h it is admittedly a #it clunky! The followin& ta#le summarizes what types of pointers you can create with *onst= 9eclaration Synta%
*onst ;%$e2 m%<tr ;%$e *onst2 m%<tr ;%$e 2*onst m%<tr *onst ;%$e2 *onst m%<tr ;%$e *onst2 *onst m%<tr
<ame
Cointer1to1*onst Cointer1to1*onst
*onst pointer *onst pointer1to1*onst *onst pointer1to1*onst
Can reassign6
.es .es <o <o <o
, su#tle point with pointers1to1*onst is that it is le&al to point to non1*onst data with a pointer1to1*onst! 8or e)ample- the followin& code is perfectly le&al=
int m%(nt; *onst int2 m%<tr = ?m%(nt;
/ere- we are free to modify the m%(nt varia#le as we see fit- #ut we promise not to chan&e it indirectly throu&h m%<tr! 7y restrictin& what we are capa#le of doin& to m%(nt throu&h m%<tr- we reduce the potential for er1 rors!
const 2b?ects
So far- all of the *onst cases we've dealt with have concerned primitive types! 0hat happens when we mi) *onst with o#*ects> et us first consider a *onst string- a C++ string whose contents cannot #e modified! 0e can declare a *onst string as we would any other *onst varia#le! 8or e)ample=
const string m%5tring = .;his is a *onstant string0";
"ote that- like all *onst varia#les- we are still allowed to assi&n the string an initial value! 7ecause the string is *onst- we're not allowed to modify its contents- #ut we can still perform some #asic op1 erations on it! 8or e)ample- here's some code that prints out the contents of a *onst string=
1 ;0E 1
*onst string m%5tring = .;his is a *onstant string0.; for(int i = 0; i m%5tring.length(); !!i) *out m%5tring[i] endl;
To us humans- the a#ove code seems completely fine and indeed it is le&al C++ code! 7ut how does the com1 piler know that the length function doesn't modify the contents of the string> This 5uestion &eneralizes to a lar&er 5uestion= &iven an ar#itrary class- how can the compiler tell which mem#er functions mi&ht modify the class and which ones don't> To answer this 5uestion- let's look at the prototype for the string mem#er function length=
siUeBt%$e length() const;
"ote that there is a *onst after the mem#er function declaration! This is another use of the *onst keyword that indicates that the mem#er function does not modify any of the class's instance varia#les! That is- when callin& a *onst mem#er function- you're &uaranteed that the o#*ect's contents will not chan&e!Q 0hen workin& with *onst o#*ects- you are only allowed to call mem#er functions on that o#*ect that have #een e)plicitly marked *onst! That is- even if you have a function that doesn't modify the o#*ect- unless you tell the compiler that the mem#er function is *onst- the compiler will treat it as a non1*onst function! 8or e)ample- let's consider a <oint class that simply stores a point in two1dimensional space=
*lass <oint { $ubli*3 double getP(); double getR(); void setP(double ne'P); void setR(double ne'R); $rivate3 double 7, %; #;
Since this function doesn't modify the <oint o#*ect in any way- we should chan&e the prototype in the class definition to read
double getP() const
so we can call this function on *onst <oint o#*ects! Similarly- we need to add a *onst marker to the function definition- as shown here=
double <oint33getP() const { return 7; # Q 'n some cases it is possi#le for a *onst mem#er function to modify its receivin& o#*ect- as you'll see later in this chapter! /owever- it's perfectly reasona#le to view *onst mem#er functions this way!
1 ;0< 1 and
8or&ettin& to add this *onst can #e a source of much frustration #ecause the C++ treats getP() getP() *onst as two different functions!
'n a *onst mem#er function- all the class's instance varia#les are treated as *onst! .ou can read their values#ut must not modify them! ,dditionally- inside a *onst mem#er function- you cannot call other non1*onst mem#er functions- since they mi&ht modify the receiver! 7eyond these restrictions- *onst mem#er functions can do anythin& that re&ular mem#er functions can do! Consider- for e)ample- the followin& implementation of a distan*e;o=rigin function for the <oint class=
void <oint33distan*e;o=rigin() *onst { double d7 = getP(); "" 6egal0 double d% = %; "" 6egal0 d7 2= d7; "" 6egal0 "" d% 2= d%; "" 6egal0 return sVrt(d7 ! d%); "" 6egal0 "" # const 1eferences
getP is *onst. Ceading an instan*e variable. 1eAre modif%ing d7, 'hi*h isnAt an instan*e variable. sVrt is a free fun*tion that *anAt modif% the *urrent ob>e*t.
Throu&hout this course we've used pass1#y1reference to avoid copyin& o#*ects #etween function calls! /oweverpass1#y1reference can lead to some am#i&uity! 8or e)ample- suppose you see the followin& function prototype=
void 9o5omething(ve*tor int)? ve*);
.ou know that this function accepts a ve*tor int) #y reference- #ut it's not clear why! 2oes 9o5omething modify the contents of the ve*tor int)- or is it *ust acceptin& #y reference to avoid makin& a deep copy of the ve*tor> To remove this am#i&uity- we can use *onst references! , *onst reference is like a normal reference e)cept that the ori&inal o#*ect is treated as thou&h it were *onst! 8or e)ample- consider this modified function proto1 type=
void 9o5omething(const ve*tor int) ?ve*);
7ecause the parameter is a *onst reference- the 9o5omething function cannot modify the ve*tor! .ou are allowed to pass #oth *onst and non1*onst varia#les to functions acceptin& *onst references! 0heth1 er or not the ori&inal varia#le is *onst- inside the function call it is treated as thou&h it were! Thus it's le&al Kand encoura&edL to write code like this=
"2 5in*e 'eAre not *hanging ve*, 'e mar+ed it *onst in this fun*tion. 2" void <rintDe*tor(const ve*tor int)S ve*) { *o$%(ve*.begin(), ve*.end(), ostreamBiterator int)(*out, . .)); *out endl; # int main() { ve*tor int) m%De*tor(E@:B(E;5); <rintDe*tor(m%De*tor); "" Oe*omes *onst on*e inside <rintDe*tor m%De*tor.$ushBba*+(8FG); "" 6egal here, m%De*tor isnAt *onst. #
1 ;10 1
0hile it's le&al to pass non1*onst o#*ects to functions acceptin& *onst references- you cannot pass *onst o#1 *ects into functions acceptin& non1*onst references! .ou can think of *onst as a universal accepter and of non1*onst as the universal donor ( you can convert #oth *onst and non1*onst data to *onst data- #ut you can't convert *onst data to non1*onst data!
*onst references differ from re&ular references in one additional aspect! Consider the followin& code= void 9o5omething(int? 7); int m%(nt; double m%9ouble; 9o5omething(m%(nt); 9o5omething(8FG); "" Error0 9o5omething(m%9ouble); "" Error0 9o5omething(H.G8IHI); "" Error0
The last three calls to 9o5omething are ille&al #ecause the function accepts its parameter #y reference! 'n the first case- 9o5omething cannot accept 1A@ as a parameter #ecause 1A@ is not an lvalueR that is- it is ille&al to have 1A@ on the left1hand side of an assi&nment! 'n the second- it is ille&al to pass m%9ouble to 9o5omething #ecause 9o5omething accepts an int? and while doubles can #e converted to ints- double?s cannot #e converted to int?s! The last call to 9o5omething is ille&al #ecause ;!@1E;E is neither an inte&er nor an lvalue! /owever- if we chan&e the prototype of 9o5omething to accept its parameter #y *onst reference- all three of these calls are le&al! 0hy the difference> The reason is that a value referenced #y a *onst reference cannot #e assi&ned a value throu&h that reference! 8or e)ample- if we prototype 9o5omething to accept 7 #y reference1 to1*onst- it is ille&al to write 7 = JH in the #ody of the function! 7ecause the value is not assi&na#le- it is safe to #ind the reference to values that mi&ht not #e lvalues or even inte&ers- since C++ can initialize the reference to a temporary value! This #ehavior of *onst references is not restricted to ints and in fact any function that accepts a parameter #y reference1to1*onst can accept constants or values implicitly converti#le to the specified type! Since functions acceptin& parameters #y reference1to1*onst do not make deep copies of their ar&uments- you can think of refer1 ence1to1*onst as a smarter version of pass1#y1value! 0e will address this more ne)t chapter! const&iterator Suppose you have a function that accepts a ve*tor string) #y reference1to1*onst and you'd like to print out its contents! .ou mi&ht want to write code that looks like this=
void <rintDe*tor(*onst ve*tor string) ?m%De*tor) { for(ve*tor string)33iterator itr = m%De*tor.begin(); itr 0= m%De*tor.end(); !!itr) *out 2itr endl; #
"" ECC=C0
Unfortunately- this code will &enerate a compile1time error! The pro#lem is in the first part of the for loop where we declare an o#*ect of type ve*tor string)33iterator! 7ecause the ve*tor is *onst- somehow the compiler has to know that the iterator you're &ettin& to the ve*tor can't modify the ve*tor's contents! Hther1 wise- we mi&ht #e a#le to do somethin& like this=
ve*tor string)33iterator itr = m%De*tor.begin(); "" Assume m%De*tor is *onst 2itr = JH; "" \ust modified a *onst ob>e*t0
1 ;11 1
.our initial thou&ht mi&ht #e to declare the iterator *onst to indicate that you won't modify what the iterator's pointin& to! This won't work either- thou&h- since a *onst iterator is like a *onst pointer ( while you can't modify which element the iterator is iteratin& over- you can chan&e the value of the element referenced #y the iterator! To fi) this pro#lem- all ST containers define a type called a const_iterator that is capa#le of readin& the values of a container #ut not modifyin& them! Thus the proper version of the a#ove code is=
void <rintDe*tor(*onst ve*tor string) ?m%5et) { for(ve*tor string)33const&iterator itr = m%De*tor.begin(); "" 4orre*t0 itr 0= m%De*tor.end(); !!itr) *out 2itr endl; #
To maintain *onstness- you cannot use *onstBiterators in functions like insert or erase that modify containers! .ou can- however- define iterator ran&es usin& *onstBiterators for al&orithms like binar%Bsear*h that don't modify the ran&es they apply to! Hne interestin& point a#out the difference #etween the iterator and *onstBiterator is that all ST con1 tainers define two different begin and end functions ( non1*onst versions that return iterators and *onst versions that return *onstBiterators! 0hen two mem#er functions have the same name #ut differ in their *onstness- C++ will call the version of the function that has the same *onstness as the receiver o#*ect! That isa non1*onst ve*tor will always call the non1*onst version of begin- while a *onst ve*tor will always call the *onst version of begin! This is sometimes known as 4*onst overloadin&!6 Limitations of const ,lthou&h *onst is a useful pro&rammin& construct- it is imperfect! Hne common pro#lem arises when usin& pointers in *onst mem#er functions! Suppose you have the followin& class that encapsulates a C strin&=
*lass 45tring { $ubli*3 "2 ... other members ...2" void *onst&un*tion() *onst; $rivate3 *har2 the5tring; #;
Unfortunately- while this code modifies the value of the o#*ect pointed to #y the5tring- it is totally le&al since we didn't modify the value of the5tring M instead- we chan&ed the value of the elements it pointed at! 'n ef1 fect- #ecause the mem#er function is declared *onst- the5tring acts as a *onst pointer instead of a pointer1 to1*onst! This raises the issue of the distinction #etween 4#itwise *onstness6 versus 4semantic *onstness!6 Jitwise constness- which is the type enforced #y C++- means that *onst classes are prohi#ited from makin& any #it1
1 ;1; 1
wise chan&es to themselves! 'n the a#ove e)ample- since the value of the5tring didn't chan&e K#ecause we didn't reassi&n itL- C++ considers it *onst1correct! /owever- from the viewpoint of semantic constness*onst classes should #e prohi#ited from modifyin& anythin& that would make the o#*ect appear somehow dif1 ferent! 0ith re&ards to the a#ove scenario with the5tring- the class isn't semantically *onst #ecause the o#1 *ect- while *onst- was a#le to modify its data! 0hen workin& with *onst it's important to remem#er that while C++ will enforce #itwise *onstness- you must take care to ensure that your pro&ram is semantically *onst! 8rom your perspective as a pro&rammer- if you call a function that's marked *onst- you would e)pect that it cannot modify whatever class it was workin& on! 'f the function isn't semantically *onst- however- you'll run into pro#lems where code that shouldn't #e modify1 in& an o#*ect somehow leaves the o#*ect in a different state! To demonstrate the difference #etween #itwise and semantically *onst code- let's consider another mem#er function of the 45tring class that simply returns the internally stored strin&=
*har2 45tring33get5tring() *onst { return the5tring; #
'nitially- this code looks correct! Since returnin& the strin& doesn't modify the strin&'s contents- we've marked the function *onst- and- indeed- the function is #itwise *onst! /owever- our code has a ma*or flaw! Consider the followin& code=
*onst 45tring m%5tr = .;his is a 4 string0.; str*$%(myStr.getString!$, .=h no0.);
/ere- we're usin& the pointer &et o#tained from get5tring as a parameter to str*$%! ,fter the str*$% com1 pletes- m%5tr's internal strin& will contain 4Hh noU6 instead of 4This is a C strin&U6 0e've modified a *onst o#*ect usin& only *onst mem#er functions- somethin& that defeats the purpose of *onst! Somehow we need to chan&e the code to prevent this from happenin&! The pro#lem is that the pointer returned #y get5tring is not itself *onst- so it's le&al to use it with functions like str*$%! To resolve this pro#lemwe can simply chan&e the return value of get5tring from *har 2 to *onst *har 2! This approach solves the pro#lem #ecause it's now ille&al to modify the strin& pointed at #y the return value! 'n &eneral- when return1 in& pointers or references to an o#*ect's internal data- you should make sure to mark them *onst when appropri1 ate! The a#ove e)ample illustrates that makin& semantically1*onst code can #e difficult! /owever- the #enefits of semantically1*onst code are noticea#le ( your code will #e more reada#le and less error1prone! mutable 7ecause C++ enforces #itwise *onstness rather than semantic *onstness- you mi&ht find yourself in a situation where a mem#er function chan&es an o#*ect's #itwise representation without modifyin& its semantic value! ,t first this mi&ht seem unusual ( how could we possi#ly leave the o#*ect in the same lo&ical state if we chan&e its #inary representation> ( #ut such situations can arise in practice! 8or e)ample- suppose that we want to write a class that represents a &rocery list! The class definition is provided here=
1 ;1A 1
The Gro*er%6ist constructor takes in a filename representin& a &rocery list Kwith one element per lineL- then allows us to look up items in the list usin& the mem#er function get(temAt! 'nitially- we mi&ht want to imple1 ment this class as follows=
Gro*er%6ist33Gro*er%6ist(*onst string? filename) { "2 Cead in the entire *ontents of the file and store in the ve*tor. 2" ifstream in$ut(filename.*Bstr()); data.insert(data.begin(), istreamBiterator string)(in$ut), istreamBiterator string)()); "" 5ee note2 # "2 Ceturns the element at the $osition s$e*ified b% inde7. 2" string Gro*er%6ist33get(temAt(int inde7) *onst { return data[inde7]; #
/ere- the Gro*er%6ist constructor takes in the name of a file and reads the contents of that file into a ve*, tor string) called data! The get(temAt mem#er function then accepts an inde) and returns the corres1 pondin& element from the ve*tor! 0hile this implementation works correctly- in many cases it is needlessly inefficient! Consider the case where our &rocery list is several million lines lon& Kmay#e if we're literally tryin& to find enou&h food to feed an armyL- #ut where we only need to look at the first few elements of the list! 0ith the current implementation of Gro*er%6ist- the Gro*er%6ist constructor will read in the entire &rocery list file- an operation which undou#tedly will take a lon& time to finish and dwarfs the small time necessary to re1 trieve the stored elements! /ow can we resolve this pro#lem> There are several strate&ies we could use to eliminate this inefficiency! Cerhaps the easiest approach is to have the constructor open the file- and then to only read in data when it's e)plicitly re5uested in the get(temAt func1 tion! That way- we don't read any data unless it's a#solutely necessary! /ere is one possi#le implementation=
Q 'f you're still a #it shaky on iterator adapters- the final two ar&uments to the insert function define a ran&e spannin& the entire source file in$ut! See the chapter on ST iterators for more information!
1 ;1B 1
*lass Gro*er%6ist { $ubli*3 Gro*er%6ist(*onst string? filename); "2 ... other member fun*tions ... 2" string get(temAt(int inde7); "" E=;E3 not *onst $rivate3 ve*tor string) data; i stream sourceStream; #; Gro*er%6ist33Gro*er%6ist(*onst string? filename) { sourceStream.open! ilename.c&str!$$; "" =$en the file. #
string Gro*er%6ist33get(temAt(int inde7) { "2 Cead in enough data to satisf% the reVuest. (f 'eAve alread% read it 2 in, this loo$ 'ill not e7e*ute and 'e 'onAt read an% data. 2" 6hile!inde# . data.length!$$ * string line; getline!sourceStreamB line$; "2 ... some sort of error,*he*+ing ... 2" data.push&bac+!line$; 4 return data9inde#:; #
Unlike our previous implementation- the new Gro*er%6ist constructor opens the file without readin& any data! The new get(temAt function is sli&htly more complicated! 7ecause we no lon&er read all the data in the con1 structor- when asked for an element- one of two cases will #e true! 8irst- we mi&ht have already read in the data for that line- in which case we simply hand #ack the value stored in the data o#*ect! Second- we may need to read more data from the file! 'n this case- we loop readin& data until there are enou&h elements in the data ve*tor to satisfy the re5uest- then return the appropriate strin&! ,lthou&h this new implementation is more efficient-Q the get(temAt function can no lon&er #e marked *onst #ecause it modifies #oth the data and sour*e5tream data mem#ers! 'f you'll notice- thou&h- despite the fact that the get(temAt function is not #itwise *onst- it is semantically *onst! Gro*er%6ist is supposed to en1 capsulate an immuta#le &rocery list- and #y shiftin& the file readin& from the constructor to get(temAt we have only chan&ed the implementation- not the &uarantee that get(temAt will not modify the list! 8or situations such as these- where a function is semantically *onst #ut not #itwise *onst- C++ provides the mutable keyword! mutable is an attri#ute that can #e applied to data mem#ers that indicates that those data mem#ers can #e modified inside mem#er functions that are marked *onst! 0e can thus rewrite the Gro*er%6ist class definition to look like this=
Q The &eneral techni5ue of deferrin& computations until they are a#solutely re5uired is called la:y e!aluation and is an e)1 cellent way to improve pro&ram efficiency! Consider takin& CS;B; or CS;?E if you're interested in learnin& more a#out lazy evaluation!
1 ;1? 1
7ecause data and sour*e5tream are #oth mutable- the new implementation of get(temAt can now #e marked *onst- as shown a#ove!
mutable is a special1purpose keyword that should #e used sparin&ly and with caution! Guta#le data mem#ers are e)empt from the type1checkin& rules normally applied to *onst and conse5uently are prone to the same er1 rors as non1*onst varia#les! ,lso- once data mem#ers have #een marked mutable- any mem#er functions can modify them- so #e sure to dou#le1check your code for correctness! Gost importantly- thou&h- do not use mut, able to silence compiler warnin&s and errors unless you're a#solutely certain that it's the ri&ht thin& to do! 'f you do- you run the risk of havin& functions marked *onst that are neither #itwise nor semantically *onst- en1 tirely defeatin& the purpose of the *onst keyword! constCCorrectness
I still sometimes come across #rogrammers who thin* const isn(t worth the trouble) EAw, const is a #ain to write e!erywhere,F I(!e heard some com#lain) EI" I use it in one #lace, I ha!e to use it all the time) And anyway, other #eo#le s*i# it, and their #rograms wor* "ine) ,ome o" the libraries that I use aren(t const-correct either) Is const worth it'F &e could imagine a similar scene, this time at a ri"le range% EAw, this gun(s sa"ety is a #ain to set all the time) And anyway, some other #eo#le don(t use it either, and some o" them ha!en(t shot their own "eet o"")))F ,a"ety-incorrect ri"lemen are not long "or this world) 6or are const-incorrect #rogrammers, car#enters who don(t ha!e time "or hard-hats, and electricians who don(t ha!e time to identi"y the li!e wire) There is no e7cuse "or ignoring the sa"ety mechanisms #ro!ided with a #roduct, and there is #articularly no e7cuse "or #rogrammers too la:y to write const-correct code) ( /er# Sutter- author of E7ce#tional C++ and all1around C++ &uru! OSut<EP "ow that you're familiar with the mechanics of *onst- we'll e)plore how to use *onst correctly in real1world C++ code! 'n the remainder of this chapter- we will e)plore const-correctness- a system for usin& *onst to in1 dicate the effects of your functions Kor lack thereofL! 8rom this point forward- all of the code in this reader will #e *onst1correct and you should make a serious effort to *onst1correct your own code! /hat is constCcorrectness6 ,t a hi&h1level- *onst1correct code is code that clearly indicates which varia#les and functions cannot modify pro&ram state!
1 ;16 1
Gore concretely- *onst1correctness re5uires that *onst #e applied consistently and pervasively! 'n particular*onst1correct code tends to use *onst as follows= 1! 5bjects are ne!er #assed by !alue! ,ny o#*ect that would #e passed #y value is instead passed #y refer1 ence1to1*onst or pointer1to1*onst! ;! -ember "unctions which do not change state are mar*ed const) Similarly- a function that is not marked *onst should mutate state somehow! A! Bariables which are set but ne!er modi"ied are mar*ed const) ,&ain- a varia#le not marked *onst should have its value chan&ed at some point! et us take some time to e)plore the ramifications of each of these items individually! 2b?ects are ne7er &assed by 7alue C++ has three parameter1passin& mechanisms ( pass1#y1value- pass1#y1reference- and pass1#y1pointer! The first of these re5uires C++ to make a full copy of the parameter #ein& passed in- while the latter two initialize the parameter #y copyin& a pointer to the o#*ect instead of the full o#*ect!Q 0hen passin& primitive types Kintdouble- *har2- etc!L as parameters to a function- the cost of a deep copy is usually ne&li&i#le- #ut passin& a heavy o#*ect like a string- ve*tor- or ma$ can at times #e as e)pensive as the #ody of the function usin& the copy! Goreover- when passin& o#*ects #y value to a function- those o#*ects also need to #e cleaned up #y their destructors once that function returns! The cost of passin& an o#*ect #y value is thus at least the cost of a call to the class's copy constructor Kdiscussed in a later chapterL and a call to the destructor- whereas passin& that same o#*ect #y reference or #y pointer simply costs a sin&le pointer copy! To avoid incurrin& the overhead of a full o#*ect deep1copy- you should avoid passin& o#*ects #y value into func1 tions and should instead opt to pass either #y reference or #y pointer! To #e *onst1correct- moreover- you should consider passin& the o#*ect #y reference1to1*onst or #y pointer1to1*onst if you don't plan on mutatin& the o#*ect inside the function! 'n fact- you can treat pass1#y1reference1to1*onst or pass1#y1pointer1to1*onst as the smarter- faster way of passin& an o#*ect #y value! 0ith #oth pass1#y1value and pass1#y1reference1to1 *onstthe caller is &uaranteed that the o#*ect will not chan&e value inside the function call! There is one difference #etween pass1#y1reference1to1*onst and pass1#y1value- thou&h- and that's when usin& pass1#y1value the function &ets a fresh o#*ect that it is free to destructively modify! 0hen usin& pass1#y1refer1 ence1to1*onst- the function cannot mutate the parameter! ,t times this mi&ht #e a #it ve)in&! 8or e)ampleconsider the 4onvert;o6o'er4ase function we wrote in the earlier chapter on ST al&orithms=
string 4onvert;o6o'er4ase(string to4onvert) { transform(to4onvert.begin(), to4onvert.end(), to4onvert.begin(), 33tolo'er); return to4onvert; #
/ere- if we simply chan&e the parameter from #ein& passed1#y1value to #ein& passed1#y1reference1to1 *onstthe code won't compile #ecause we modify the to4onvert varia#le! 'n situations like these- it is sometimes prefera#le to use pass1#y1value- #ut alternatively we can rewrite the function as follows=
string 4onvert;o6o'er4ase(const stringS to4onvert) { string result , to;on)ert; transform(result.begin(), result.end(), result.begin(), 33tolo'er); return result; # Q Feferences are commonly implemented #ehind1the1scenes in a manner similar to pointers- so passin& an o#*ect #y refer1 ence is at least as efficient as passin& an o#*ect #y pointer!
1 ;1@ 1
/ere- we simply create a new varia#le called result- initialize it to the parameter to4onvert- then proceed as in the a#ove function! #ember functions which do not change state are const 'f you'll recall from the previous chapter's discussion of *onst mem#er functions- when workin& with *onst instances of a class- C++ only allows you to invoke mem#er functions which are e)plicitly marked *onst! "o matter how innocuous a function is- if it isn't e)plicitly marked *onst- you cannot invoke it on a *onst instance of an o#*ect! This means that when desi&nin& classes- you should take &reat care to mark *onst every mem#er function that does not chan&e the state of the o#*ect! 's this a lot of work> ,#solutelyU 2oes it pay off> Hf courseU ,s an e)treme e)ample of why you should always mark nonmutatin& mem#er functions *onst- suppose you try to pass a CS1067IJ De*tor to a function #y reference1to1*onst! Since the De*tor is marked as *onst- you can only call De*tor mem#er functions that themselves are *onst! Unfortunately- none of the De*tor's mem1 #er functions are *onst- so you can't call any mem#er functions of a *onst De*tor! , *onst CS1067IJ De*tor is effectively a di&ital #rick! ,s fun as #ricks are- from a functional standpoint they're pretty much use1 less- so do make sure to *onstify your mem#er functions! 'f you take care to *onst correct all mem#er functions that don't modify state- then your code will have an addi1 tional- stron&er property= mem#er functions which are not marked *onst are almost &uaranteed to make some sort of chan&e to their internal state! 8rom an interface perspective this is wonderful ( if you want to call a par1 ticular function that isn't marked *onst- you can almost &uarantee that it's &oin& to make some form of destruct1 ive modification to the receiver o#*ect! Thus when you're &ettin& accustomed to a new code #ase- you can 5uickly determine what operations on an o#*ect modify that o#*ect and which *ust return some sort of internal state! Hariables which are set but ne7er changed are const Taria#les vary! That's why they're called varia#les! Constants- on the other hand- do not! Semantically- there is a hu&e difference #etween the sorts of operations you can perform on constants and the operations you can per1 form on varia#les- and usin& one where you meant to use the other can cause all sorts of de#u&&in& headaches! Usin& *onst- we can make e)plicit the distinction #etween constant values and true varia#les- which can make de#u&&in& and code maintenance much easier! 'f a varia#le is *onst- you cannot inadvertently pass it #y refer1 ence or #y pointer to a function which su#tly modifies it- nor can you accidentally overwrite it with K when you meant to check for e5uality with ==! Gany years after you've marked a varia#le *onst- pro&rammers tryin& to decipher your code will let out a si&h of relief as they realize that they don't need to watch out for su#tle opera1 tions which overwrite or chan&e its value! 0ithout &ettin& carried away- you should try to mark as many local varia#les *onst as possi#le! The additional compile1time safety checks and reada#ility will more than compensate for the e)tra time you spent typin& those e)tra five characters!
,s an e)ample of what *onst1correctness looks like in practice- we'll consider how to take a variant of the CS1067IJ :a$ class and modify it so that it is *onst1correct! The initial interface looks like this=
tem$late t%$ename Dalue;%$e) *lass :a$ { $ubli*3 :a$(int siUe/int = 808); ^:a$(); int siUe(); bool isEm$t%(); void $ut(string +e%, Dalue;%$e value); void remove(string +e%); bool *ontainsXe%(string +e%); "2 get *auses an Error if the +e% does not e7ist. o$erator[] (the 2 fun*tion 'hi*h is *alled 'hen %ou use the ma$[.+e%.] s%nta7) *reates 2 an element 'ith the s$e*ified +e% if the +e% does not alread% e7ist. 2" Dalue;%$e get(string +e%); Dalue;%$e? o$erator[](string +e%); void *lear(); void ma$All(void (fn)(string +e%, Dalue;%$e val)); tem$late t%$ename 4lient9ata;%$e) void ma$All(void (fn)(string +e%, Dalue;%$e val, 4lient9ata;%$e? data), 4lient9ata;%$e ?data); (terator iterator(); $rivate3 "2 ... (m$lementation s$e*ifi* ... 2" #;
The o$erator[] function shown here is what's called an o!erloaded o#erator and is the function that lets us write code to the effect of m%:a$[.Xe%.] = value and value = m%:a$[.Xe%.]! 0e will cover overloaded operators in a later chapter- #ut for now you can think of it simply as a function that is called whenever the :a$ has the element1selection #rackets applied to it! The first set of chan&es we should make to the :a$ is to mark all of the pu#lic mem#er functions which don't modify state *onst! This results in the followin& interface=
1 ;1< 1
The siUe- isEm$t%- and *ontainsXe% functions are all *onst #ecause they simply 5uery o#*ect properties without chan&in& the :a$! get is also *onst since accessin& a keyIvalue pair in the :a$ does not actually modify the underlyin& state- #ut o$erator[] should definitely not #e marked *onst #ecause it may update the container if the specified key does not e)ist! The trickier functions to *onst1correct are ma$All and iterator! Unlike the ST iterators- CS1067IJ iterat1 ors are read1only and can't modify the underlyin& container! /andin& #ack an iterator to the :a$ contents there1 fore cannot chan&e the :a$'s contents- so we have marked iterator *onst! 'n addition- since ma$All passes its ar&uments to the call#ack function #y value- there is no way for the call#ack function to modify the underly1 in& container! 't should therefore #e marked *onst! "ow that the interface has its mem#er functions *onst1ified- we should make a second pass over the :a$ and replace all instances of pass1#y1value with pass1#y1reference1to1*onst! 'n &eneral- o#*ects should never #e passed #y value and should always #e passed either #y pointer or reference with the appropriate *onstness! This eliminates unnecessary copyin& and can make pro&rams perform asymptotically #etter! The resultin& class looks like this=
1 ;;0 1
"2 Eote3 5till more *hanges to ma+e. 9o not use this *ode as a referen*e0 2" tem$late t%$ename Dalue;%$e) *lass :a$ { $ubli*3 :a$(int siUe/int = 808); ^:a$(); int siUe() *onst; bool isEm$t%() *onst; void $ut(const stringS +e%, const Dalue;%$eS value); void remove(const stringS +e%); bool *ontainsXe%(const stringS +e%) *onst; "2 get *auses an Error if the +e% does not e7ist. o$erator[] (the 2 fun*tion 'hi*h is *alled 'hen %ou use the ma$[.+e%.] s%nta7) *reates 2 an element 'ith the s$e*ified +e% if the +e% does not alread% e7ist. 2" Dalue;%$e get(const stringS +e%) *onst; Dalue;%$e? o$erator[](const stringS +e%); void *lear(); void ma$All(void (fn)(const stringS +e%, const Dalue;%$eS val)) *onst; tem$late t%$ename 4lient9ata;%$e) void ma$All(void (fn)(const stringS +e%, const Dalue;%$eS val, 4lient9ata;%$e? data), 4lient9ata;%$e ?data) *onst; (terator iterator() *onst; $rivate3 "2 ... (m$lementation s$e*ifi* ... 2" #;
The parameters to $ut- remove- *ontainsXe%- get- and o$erator[] have all #een updated to use pass1#y1 reference1to1*onst instead of pass1#y1value! The trickier functions to modify are the ma$All functions! These functions themselves accept function pointers which initially took their values #y value! 0e have updated them appropriately so that the function pointers accept their ar&uments #y reference1to1 *onst- since we assume that the class client will also #e *onst1correct! "ote that we did not mark the 4lient9ata;%$e? parameter to ma$All *onst- since the :a$ client may actually want to modify that parameter! There is one last chan&e to make- and it concerns the get function- which currently returns a copy of the value associated with a &iven key! ,t a hi&h1level- there is nothin& intuitively wron& with returnin& a copy of the stored value- #ut from an efficiency standpoint we may end up payin& a steep runtime cost #y returnin& the o#1 *ect #y value! ,fter all- this re5uires a full o#*ect deep copy- plus a call to the o#*ect's destructor once the re1 turned o#*ect &oes out of scope! 'nstead- we'll modify the interface such that this function returns the o#*ect #y reference1to1*onst! This allows the :a$ client to look at the value and- if they choose- copy it- #ut prevents cli1 ents from intrusively modifyin& the :a$ internals throu&h a *onst function! The final- correct interface for :a$ looks like this=
1 ;;1 1
,s an interestin& intellectual e)ercise- compare this code to the ori&inal version of the :a$! The interface de1 claration is considera#ly lon&er than #efore #ecause of the additional *onsts- #ut ultimately is more pleasin&! Someone unfamiliar with the interface can understand- for e)ample- that the :a$'s (terator type cannot modi1 fy the underlyin& container Ksince otherwise the iterator() function wouldn't #e *onstL- and can also note that ma$All allows only a read1only map operation over the :a$! This makes the code more self1documentin&a &reat #oon to pro&rammers responsi#le for maintainin& this code #ase in the lon& run! /hy be constCcorrect6 ,s you can see from the e)ample with the CS1067IJ :a$- makin& code *onst1correct can #e tricky and time1 consumin&! 'ndeed- typin& out all the re5uisite *onsts and ?s can #ecome tedious after a while! So why should you want to #e *onst1correct in the first place> There are multiple reasons why code is #etter off *onst1correct than non1*onst1correct! /ere are a few=
Code correctness! 'f nothin& else- markin& code *onst whenever possi#le reduces the possi#ility for lurkin& #u&s in your code! 7ecause the compiler can check which re&ions of the code are and are not muta#le- your code is less likely to contain lo&ic errors stemmin& either from a misuse of an interface or from a #u&&y implementation of a mem#er function!
1 ;;; 1
Cha#ter /3% const Code documentation! *onst1correct code is self1documentin& and clearly indicates to other pro&ram1 mers what it is and is not capa#le of doin&! 'f you are presented an interface for an entirely forei&n class- you may still #e a#le to fi&ure out which methods are safe to call with important data #y notin& which mem#er functions are *onst or accept parameters #y reference1to1*onst! Library integration! The C++ standard li#raries and most third1party li#raries are fully *onst1correct and e)pect that any classes or functions that interface with them to #e *onst1correct as well! 0ritin& code that is not *onst1correct can prevent you from fully harnessin& the full power of some of these li#1 raries!
-ractice -roblems /ere are some open1ended pro#lems that you can use to play around with *onst and *onst1correctness! ' stron&ly encoura&e you to try them out as a way of &ettin& a feel for what *onst feels like in the real world! 1! The followin& line of code declares a mem#er function inside a class=
*onst *har 2 *onst :%&un*tion(*onst string? in$ut) *onst;
D)plain what each *onst in this statement means! ;! 0rite a class with a mem#er function is4onst that returns whether he receiver o#*ect is *onst! <+int% &rite two di""erent "unctions named is+onst= A! The ST ma$'s #racket operator accepts a key and returns a reference to the value associated with that key! 'f the key is not found- the ma$ will insert a new keyIvalue pair so that the returned reference is valid! 's this function #itwise *onst> Semantically *onst> B! 0hen workin& with pointers to pointers- *onst can #ecome considera#ly trickier to read! 8or e)amplea *onst int 2 *onst 2 *onst is a *onst pointer to a *onst pointer to a *onst int- so neither the pointer- its pointee- or its pointee's pointee can #e chan&ed! 0hat is an int 2 *onst 2> /ow a#out an int 22 *onst 22> ?! 'n C++- it is le&al to convert a pointer of type ; 2 to a pointer of type *onst ; 2- since the *onst ; 2's restrictions on what can #e done with the pointee are a su#set of the #ehaviors allowed #y a vari1 a#le of type ; 2! 't is not le&al- however- to convert a pointer of type ; 22 to a pointer of type *onst ; 22- #ecause doin& so would open a hole in the type system that would allow you to modify a varia#le of type *onst ;! Show why this is the case! M
Cha#ter /3% const 6! The CS1067IJ De*tor has the followin& interface=
tem$late t%$ename Elem;%$e) *lass De*tor { $ubli*3 De*tor(int siUe/int = 0); int siUe(); bool isEm$t%(); Elem;%$e getAt(int inde7); void setAt(int inde7, Elem;%$e value); Elem;%$e? o$erator[](int inde7); void add(Elem;%$e elem); void insertAt(int inde7, Elem;%$e elem); void removeAt(int inde7); void *lear(); void ma$All(void (2fn)(Elem;%$e elem)); tem$late t%$ename 4lient9ata;%$e) void ma$All(void (2fn)(Elem;%$e elem, 4lient9ata;%$e ? data), 4lient9ata;%$e ? data); #; (terator iterator();
1 ;;A 1
Godify this interface so that it is *onst1correct! <+int% 4ou may need to const-o!erload some o" these "unctions= M @! Godify the Snake simulation code from the earlier e)tended e)ample so that it is *onst1correct!
0e've covered a &ood many lan&ua&e features in the past few chapters- and to see how all of them work toðer we'll implement a specialized ,2T called a union-"ind data structure Ksometimes also called a disjoint-set data structureL! Union1find structures are critical for several important al&orithms- most nota#ly Irus*al(s algorithm for computin& minimal spannin& trees! ,s we desi&n a union1find structure- we'll see what thou&ht processes underlie the desi&n of a C++ class and how the lan&ua&e features we've recently e)plored make for an e)press1 ive and fle)i#le class that is surprisin&ly simple to use! Guch of the theoretical material descri#ed here a#out the union1find data structure is #ased on the discussion of dis*oint1set forests in Chapter ;1 of Introduction to Algorithms #y Cormen- eiserson- Fivest- and Stein! This is a most e)cellent al&orithms te)t and if you're interested in e)pandin& your computer science knowled&e ' hi&hly recommend pickin& up a copy! Tracking 9is?oint Sets Simply put- a set is an unordered collection of elements! 8or e)ample- the set a1- ;- Ab contains three elements and is e5ual to the set a;- 1- Ab since orderin& does not matter! /owever- it is not e5ual to the set a1- ;b or the set a1- ;- A- Bb #ecause the former does not contain the element three and the latter contains an e)tra element KBL! The union of two sets Kdenoted , 7L is the set formed #y addin& all of the elements of the first set to the second! 8or e)ample- a1- ;- Ab aA- B- ?b is a1- ;- A- B- ?b! "ote that althou&h A appears in #oth the first and second set- it only appears once in the union #ecause sets do not allow for duplicate elements! Two sets are said to #e disjoint if they contain no elements in common! 8or e)ample- a1- ;- Ab and aB- ?b are dis*oint- #ut a1- ;- Ab and aA- B- ?b are not #ecause #oth sets contain A! Suppose that we have some master set of elements S! Then a #artition of S is a collection of sets such that all of the sets are dis*oint and the union of the sets is the set S! 'n other words- every element of S #elon&s to one and e)actly one set in the partition! ,s an e)ample- &iven the set a1- ;- A- B- ?b- one partition is the sets a1- ;b- aAband aB- ?b and another is a1- ;- A- Bb- a?b! 'f we represent a set visually as a collection of points- then a partition is a way of &roupin& toðer those points such that no points are contained in two different &roups! 8or e)1 ample- here is a fi&ure demonstratin& a set S and a partition of S=
1 ;;6 1
3iven a set S that is partitioned into smaller sets S1- S;- !!!- Sn- we say that an element si is a re#resentati!e of Si if si is an element of Si! 8or e)ample- in the a#ove e)ample with the partition a1- ;b- aAb- aB- ?b of the first five nonne&ative inte&ers- 1 is a representative of a1- ;b- A is a representative of aAb- and ? is a representative of aB- ?b! "ote that representatives are not uni5ue ( ; is also a representative of a1- ;b and B is a representative of aB- ?b! 'f we have a partition of a set S- we can choose from every set in the partition a sin&le- ar#itrary element to form a re#resentati!e set for S! Gathematically- this set can #e useful if we have additional information a#out the un1 derlyin& structure of the partition! 8rom a computer science perspective- however- a representative set is useful if we want to test whether two elements are contained in the same partition! To check whether two elements are in the same partition- we first choose a representative set called the designated re#resentati!e set and associate each set with its desi&nated representative! To check whether a sin&le set contains two elements- we can simply look up the desi&nated representative of the set containin& the first element and of the set containin& the second element- then check whether the representatives are the same! 'f so- the two elements must #elon& to the same partition since each partition has a uni5ue desi&nated representative! 'f not- the elements must lie in different partitions! , union-"ind data structure is a data structure that mana&es a partition of a set S and a set of representatives for the sets in the partition! ,s its name implies- a union1find data structure supports two operations ( union- which mer&es two sets- and "ind- which returns the representative of the set containin& a &iven element! 8or e)amplesuppose that S Y a1- ;- A- B- ?b as a#ove! 'nitially- a union1find data structure stores the partition of S where every element is in its own set! 'n our case- this is the partition a1b- a;b- aAb- aBb- a?b which has representatives 1- ;- A- B- ?! 'f we were to union toðer the sets containin& 1 and ;- the new partition would #e a1- ;b- aAbaBb- a?b and we would need to pick a new representative for a1- ;bR let's ar#itrarily pick 1! "ow our set of rep1 resentatives is 1- A- B- ?! 'f at this point we wanted to take the union of the sets containin& ; and ?- the new parti1 tion would #e a1- ;- ?b- aAb- aBb and we would need to pick another representative for a1- ;- ?bR a&ain we ar#it1 rarily pick 1! This leaves us with a set of representatives 1- A- B! There are many ways in which we can implement a union1find data structure- of which the fastest is called a disjoint-set "orest! 'n a dis*oint1set forest- every element in the master set S is represented as a node storin& the value of the element and a link to the representative of its partition! 8or e)ample- &iven the a#ove partition and set of representatives Ki!e! a1- ;- ?b- aAb- aBb with representatives 1- A- BL- one possi#ility for the dis*oint1set forest looks like this=
"otice that the elements A and B point to themselves- since they are the representatives of their own e5uivalence class! ,lso- note that althou&h ? has 1 as the representative of its e5uivalence class- its link points to the element ; which in turn points to 1! This is perfectly accepta#le- and in fact the method we will use for computin& the representative of a set is simply to follow the link pointers up to the first element that refers to itself! 3iven this structure- it is simple to implement the union operation! 3iven two elements ) and y whose sets we want to mer&e- we look up the representatives of their two sets- then ar#itrarily chan&e one of the two to refer to the other as a representative! 8or e)ample- here is the effect of mer&in& the sets a1- ;- ?b and aAb=
1 ;;@ 1
Im&lementing the UnionFind Class 'n the rest of this chapter- we will implement a class called @nion&ind which encapsulates a dis*oint1set forest! 'n summary- the @nion&ind class should #ehave as follows= 1! 0hen constructed- the @nion&ind class stores a set partitioned such that every element is in its own partition! ;! @nion&ind should allow us to 5uery for the representative of the e5uivalence class of any element in the set! <"ind= A! @nion&ind should allow us to take the union of any two sets in the partition <union= "otice that in this discussion of @nion&ind we never e)plicitly mentioned what types of elements we plan on storin& inside this @nion&ind class! Hur a#ove discussion considered sets of inte&ers- #ut we could have *ust as easily considered a set of strings or of doubles or ve*tor set int) )s! Conse5uently- we will define @nion&ind as a template class parameterized over the type of o#*ect we plan on storin&! This leads to the fol1 lowin& pseudo1definition of @nion&ind=
template .typename 2lemType> class UnionFind * TR ... RT 4;
To implement the dis*oint1set forest- we need to keep track of a collection of elements paired with a link to their parent! 0hile there are several ways of implementin& this- one simple solution is to store everythin& in a ma$ Elem;%$e, Elem;%$e) mappin& every element to its parent! To save time- we'll use a t%$edef state1 ment and define this type as ma$;! This yields
tem$late t%$ename Elem;%$e) *lass @nion&ind { "2 ... 2" $rivate3 typede map.2lemTypeB 2lemType> mapT; TT Shorthand mapT orest; TT The actual dis\oint-set orest. #;
or simplicity.
,s mentioned in the a#ove description of @nion&ind- the @nion&ind constructor should accept as input a set of elements and initialize the partition such that each element is in its own set! 7ut how should we accept the set of elements> ,s an ST set> , ve*tor> , raw C++ array> 'ndeed- any of these choices seems reasona#le! Father than sin&lin& any of these forms out- instead we'll cater to the lowest common denominator #y havin& the constructor accept as input a ran&e of iterators delineatin& the input to use! Since the iterators can come from
1 ;;E 1
any possi#le source- however- the constructor itself must #e a template function parameterized over the type of the iterators! This makes @nion&ind look as follows=
tem$late t%$ename Elem;%$e) *lass @nion&ind { $ubli*3 "2 A**e$ts a range of elements and initialiUes the $artition. 2" template .typename IteratorType> UnionFind!IteratorType beginB IteratorType end$; "2 ... 2" $rivate3 t%$edef ma$ Elem;%$e, Elem;%$e) ma$;; "" 5horthand for sim$li*it%. ma$; forest; "" ;he a*tual dis>oint,set forest. #;
"ote that in this implementation we have two template headers ( one indicatin& that the function is a mem#er function of a class parameterized over Elem;%$e and one indicatin& that the function itself is parameterized over (terator;%$e! The #ody of the function is not particularly tricky ( we simply iterate over the ran&e and set each element to refer to itself! ,t this point we're ready to implement the find function- which simply walks up the links in the forest startin& at the specified position until it reaches an element which has itself as a representative! 'nitially- we mi&ht want to consider prototypin& find as follows=
Elem;%$e find(*onst Elem;%$e? to&ind) *onst;
This is functionally correct- #ut it returns a deep copy of the stored o#*ect! 'f we are storin& ints this isn't a pro#lem- #ut if we are usin& a @nion&ind string) or a @nion&ind ve*tor int) ) this operation could #e costly! To circumvent this- we'll have find return a *onst reference to the element! This way- clients can still view the representative- #ut don't have to pay for a copy if they doesn't want to! This results in the followin& interface for @nion&ind=
1 ;;< 1
The final step in desi&nin& @nion&ind is to implement the union operation! Unfortunately- union is a reserved keyword in C++-Q so rather than namin& this function union we will name it lin+! The implementation of lin+ is surprisin&ly strai&htforward ( we simply look up the representatives for the two sets to link- then ar#it1 rarily set one to use the other as a representative! This results in the followin& interface and implementation=
Q , union is similar to a stru*t or *lass- e)cept that the data mem#ers all share a memory location! Consult a reference for more details!
1 ;A0 1
tem$late t%$ename Elem;%$e) *lass @nion&ind { $ubli*3 "2 A**e$ts a range of elements and initialiUes the $artition. 2" tem$late t%$ename (terator;%$e) @nion&ind((terator;%$e begin, (terator;%$e end); "2 Ceturns the re$resentative of the eVuivalen*e *lass *ontaining the 2 s$e*ified element. 2" *onst Elem;%$e? find(*onst Elem;%$e? to&ind) *onst; "2 @nions the t'o sets *ontaining the s$e*ified elements. 2" )oid lin+!const 2lemTypeS oneB const 2lemTypeS t6o$; $rivate3 t%$edef ma$ Elem;%$e, Elem;%$e) ma$;; "" 5horthand for sim$li*it%. ma$; forest; "" ;he a*tual dis>oint,set forest. #; tem$late t%$ename Elem;%$e) void @nion&ind Elem;%$e)33lin+(*onst Elem;%$e? one, *onst Elem;%$e? t'o) { forest[find(one)] = forest[find(t'o)]; #
Im&lementing -ath Com&ression The implementation of a dis*oint1set forest used in @nion&ind performs all of the re5uired operations of a uni1 on1find data structure- #ut can #e made considera#ly more efficient via several small transformations! Hne particular chan&e that is easy to implement is #ath com#ression! Dvery time we look up the representative of a set- we may have to traverse a very lon& chain of nodes until we hit the representative! ,fter we've com1 puted the representative once- however- we can simply chan&e the representative link so that it directly refers to the representative! This reduces the num#er of recursive calls necessary to look up the representative for a &iven class! Thus- after each call to find- we will update all of the link pointers for elements we accessed durin& the search! ,n important point to note here is that we marked find *onst in the initial version of @nion&ind- since look1 in& up the representative for a set doesn't chan&e the structure of the sets themselves! /owever- path compres1 sion potentially re5uires us to modify the forest in the @nion&ind data structure on each call to find- mean1 in& that find is no lon&er #itwise *onst! /owever- it is semantically *onst- so we will mark the forest field mutable so we can modify it in the *onst mem#er function find! The updated @nion&ind interface and implementation of find are &iven #elow=
1 ;A1 1
Im&lementing EnionCbyC1ank Fecall the dia&ram we had from earlier demonstratin& one possi#le dis*oint1set forest for the partition a1- ;- A- ?b- aBb=
,fter applyin& path compression- we would end up with this dis*oint1set forest=
1 ;A; 1
"ote how the element ? now points directly to the element 1! Suppose that at this point we want to mer&e the set a1- ;- A- ?b and aBb! 0e would do this #y chan&in& one of the representatives to point to the other representative! "ow- we can do this either #y havin& 1 refer to B as its parent- or #y havin& B refer to 1 as its parent! Clearly it is #etter to have B point to 1- since 1 has multiple chil1 dren that would need to have their parents recomputed on future calls to find! 'f- on the other hand- we chan&e B to point to 1- we would only need to update a sin&le link! This o#servation motivates a final optimization we can implement on the @nion&ind class call union-by-ran*! The idea is that when tryin& to mer&e two representatives- we will try to make the element with fewer represent1 atives point to the element with more representatives! Hne possi#le way to implement this is to ta& each entry in the set with a ran* representin& the relative num#er of elements that treat that element as a representative! 'f we think of an element and all of the elements pointin& to it as a tree- the rank is rou&hly the hei&ht of that tree #e1 fore applyin& path compression! Hur particular implementation of union1#y1rank will use the followin& system= 1! Dach node #e&ins with rank 0- since no other nodes depend on it! ;! 0hen mer&in& two sets- if one representative has lower rank than the other- then that node is chan&ed to point to the hi&her1rank node! This means that smaller trees Kfewer dependenciesL are always mer&ed into lar&er trees Kmore dependenciesL! The rank of the hi&h1rank tree doesn't chan&e #ecause its hei&ht has not increased! A! 0hen mer&in& two sets- if #oth nodes have e5ual rank- one of the two nodes ar#itrarily is chosen to point to the other node- and the other node has its rank increased #y one! This means that e5ual1hei&ht trees are *oined toðer to form a tree lar&er than #oth of the ori&inal trees! 0e will not prove it here- #ut addin& this chan&e causes any m operations on the @nion&ind data structure to complete in appro)imately HKm l& mLQ time! To implement this set of chan&es- we simply need to chan&e from usin& Elem;%$es as the values in our ma$ to usin& $air Elem;%$e, int)s as keys! The resultin& code chan&es are shown here=
Q Technically the asymptotic comple)ity is HKm Kl& mL fKmLL- where fKmL is an e7tremely slowly1&rowin& function Kfor in1 puts smaller than the num#er of atoms in the known universe- it is less than fiveL! 'f we were to switch from an ST 1 style tree map to a hash map as the main data structure #ackin& the forest- this drops to HKmfKmLL!
1 ;AA 1
1 ;AB 1
tem$late t%$ename Elem;%$e) void @nion&ind Elem;%$e)33lin+(*onst Elem;%$e? one, *onst Elem;%$e? t'o) { "2 6oo+ u$ the re$resentatives and their ran+s. 2" pair.2lemTypeB int>S oneCep , loo+upTable9 ind!one$:; pair.2lemTypeB int>S t6oCep , loo+upTable9 ind!t6o$:; "2 (f the t'o are eVual, 'eAre done. 2" i !oneCep. irst ,, t6oCep. irst$ return; "2 =ther'ise, union the t'o a**ordingl%. 2" i !oneCep.second . t6oCep.second$ oneCep. irst , t6oCep. irst; else i !oneCep.second > t6oCep.second$ t6oCep. irst , oneCep. irst; "2 =n a tie, bum$ the ran+. 2" else * oneCep. irst , t6oCep. irst; 33t6oCep.second; 4
#ore to $%&lore The Union18ind data structure we've introduced here can #e used to implement all sorts of nifty al&orithms that arise in nontrivial software systems! Consider lookin& into the followin& al&orithms and seein& how you can use the @nion&ind structure we've *ust created to implement them=
@ruskalNs Algorithm= , gra#h is a collection of nodes *oined toðer #y arcs that represents some sort of relationship! 8or e)ample- a &raph of the telephone system mi&ht have individual phones as nodes and telephone wires as arcs- while a &raph of a social network mi&ht have people as nodes and arcs #etween individuals who know one another! Irus*al(s -inimum-,#anning Tree Algorithm is a simple al&orithm to find the minimum set of arcs such that every node is reacha#le from every other node! 'f we were to apply Kruskal's al&orithm to the telephone system- for e)ample- it would return the smallest num#er of wires we'd need to lay down to connect all of the world's telephones! Kruskal's al&orithm hin&es on the a#ility to track partitions of the set of all nodes- and is an e)cellent test#ed for the @nion, &ind data structure! CS161 is also a &reat way to learn more a#out this and other &raph al&orithms! 1elational Enification= Hne of the more powerful automated theorem provin& techni5ues relies on the uni"ication algorithm- which takes multiple predicates e)pressin& true and false statements and com1 #ines them to try to derive a contradiction! The unification al&orithm is sound and complete- meanin& that &iven a set of input propositions that are consistent the al&orithm can construct a proof of this con1 sistency! Usin& the @nion&ind data structure- it is relatively strai&htforward to perform the unification step- so if you're interested in automated theorem provin& look into this al&orithm Kor CS1?@ for a more in1depth treatmentL! 0indleyC#ilner Ty&e Inference= C++ is a statically1typed lan&ua&e- meanin& that each varia#le is as1 si&ned a uni5ue- immuta#le type at compile1time! /owever- you as a C++ pro&rammer have to e)pli1 citly indicate the type of every varia#le and function- even if it's clear from conte)t what types every varia#le must #e! Hther pro&rammin& lan&ua&es like G and /askell are also statically1typed- #ut use a ty#e in"erence al&orithm to automatically compute the types of each of the varia#les and e)pressions in the pro&ram! The particular al&orithm used Kthe +indley--ilner Ty#e In"erence AlgorithmL is #ased on unification of type varia#les and is a &reat way to show off your @nion&ind skills! Take CS;B; if you're interested in learnin& more a#out type inference!
1 ;A? 1
1 ;A6 1
tem$late t%$ename Elem;%$e) void @nion&ind Elem;%$e)33lin+(*onst Elem;%$e? one, *onst Elem;%$e? t'o) { "2 6oo+ u$ the re$resentatives and their ran+s. 2" $air Elem;%$e, int)? oneCe$ = loo+u$;able[find(one)]; $air Elem;%$e, int)? t'oCe$ = loo+u$;able[find(t'o)]; "2 (f the t'o are eVual, 'eAre done. 2" if(oneCe$.first == t'oCe$.first) return; "2 =ther'ise, union the t'o a**ordingl%. 2" if(oneCe$.se*ond t'oCe$.se*ond) oneCe$.first = t'oCe$.first; else if(oneCe$.se*ond ) t'oCe$.se*ond) t'oCe$.first = oneCe$.first; "2 =n a tie, bum$ the ran+. 2" else { oneCe$.first = t'oCe$.first; !!t'oCe$.se*ond; #
"ormally- when you create a class- you'll initialize all of its instance varia#les inside the constructor! /oweverin some cases you'll need to initialize instance varia#les #efore the constructor #e&ins runnin&! Cerhaps you'll have a *onst instance varia#le that you cannot assi&n a value- or may#e you have an o#*ect as an instance vari1 a#le where you do not want to use the default constructor! 8or situations like these- C++ has a construct called the member initiali:er list that you can use to fine1tune the way your data mem#ers are set up! This chapter dis1 cusses initializer list synta)- situations where initializer lists are appropriate- and some of the su#tleties of initial1 izer lists! 0ow C++ Constructs 2b?ects To fully understand why initializer lists e)ist in the first place- you'll need to understand the way that C++ cre1 ates and initializes new o#*ects! et's suppose you have the followin& class=
*lass 5im$le4lass { $ubli*3 5im$le4lass(); $rivate3 int m%(nt; string m%5tring; ve*tor int) m%De*tor; #;
0hat happens when you create a new instance of the class :%4lass> 't turns out that the simple line of code :%4lass m* actually causes a cascade of events that &oes on #ehind the scenes! et's take a look at what hap1 pens- step1#y1step! The first step in constructin& a C++ o#*ect is simply to &et enou&h space to hold all of the o#*ect's data mem#ers! The memory is not initialized to any particular value- so initially all of your o#*ect's data mem#ers hold &ar#a&e values! 'n memory- this looks somethin& like this=
1 ;AE 1
)ector.int> my5ector
,s you can see- none of the instance varia#les have #een initialized- so they all contain *unk! ,t this point- C++ calls the default constructor of each instance varia#le! 8or primitive types- this leaves the varia#les unchan&ed! ,fter this step- our o#*ect looks somethin& like this=
)ector.int> my5ector
8inally- C++ will invoke the o#*ect's constructor so you can perform any additional initialization code! Usin& the constructor defined a#ove- the final version of the new o#*ect will look like this=
)ector.int> my5ector
1 ;A< 1
/owever- there's one thin& to consider here! 7efore we reached the 5im$le4lass constructor- C++ called the default constructor on #oth m%5tring and m%De*tor! m%5tring was therefore initialized to the empty strin&and m%De*tor was constructed to hold no elements! /owever- in the 5im$le4lass constructor- we immedi1 ately assi&ned m%5tring to hold 4C++U6 and resized m%De*tor to hold ten elements! This means that we ef1 fectively initialized m%5tring and m%De*tor twice ( once with their default constructors and once in the 5im, $le4lass constructor!Q To improve efficiency and resolve certain other pro#lems which we'll e)plore later- C++ has a feature called an initiali:er list! ,n initializer list is simply a series of values that C++ will use instead of the default values to ini1 tialize instance varia#les! 8or e)ample- in the a#ove e)ample- you can use an initializer list to specify that the varia#le m%5tring should #e set to 4C++U6 #efore the constructor even #e&ins runnin&! To use an initializer list- you add a colon after the constructor and then list which values to initialize which vari1 a#les with! 8or e)ample- here's a modified version of the 5im$le4lass constructor that initializes all the in1 stance varia#les in an initializer list instead of in the constructor=
5im$le4lass335im$le4lass() : myInt!I$B myString!";33%"$B my5ector!7-$ { "" Eote3 Em$t% *onstru*tor #
/ere- we're tellin& C++ to initialize the varia#les m%(nt and m%5tring to ? and 4C++U-6 respectively- #efore the class constructor is even called! ,lso- #y writin& m%De*tor(80)- we're tellin& C++ to invoke the paramet1 rized constructor of m%De*tor passin& in the value 10- which creates a ve*tor with ten elements! This timewhen we create a new o#*ect of type m%De*tor- the creation steps will look like this= 8irst- as in the previous case- the o#*ect is allocated somewhere in memory and all varia#les have &ar#a&e val1 ues=
)ector.int> my5ector
"e)t- C++ invokes all of the constructors for the o#*ect's data mem#ers usin& the values specified in the initial1 izer list! The o#*ect now looks like this=
Q Technically speakin&- the o#*ects are only initialized once- #ut the runtime efficiency is as thou&h the o#*ects were ini1 tialized multiple times! 0e'll talk a#out the differences #etween initialization and assi&nment in a later chapter!
1 ;B0 1
)ector.int> my5ector
8inally- C++ invokes the :%4lass constructor- which does nothin&! The final version of the class thus is identical to the a#ove version! ,s you can see- the values of the instance varia#les m%(nt- m%5tring- and m%De*tor are correctly set #efore the 5im$le4lass constructor is invoked! This is considera#ly more efficient than the previous version and will run much faster! "ote that while in this e)ample we used initializer lists to initialize all of the o#*ect's instance varia#les- there is no re5uirement that you do so! /owever- in practice it's usually a &ood idea to set up all varia#les in an initial1 izer list to make clear what values you want for each of your data mem#ers! -arameters in Initiali3er Lists 'n the a#ove e)ample- the initializer list we used specified constant values for each of the data mem#ers! /owever- it's #oth le&al and useful to initialize data mem#ers with e)pressions instead of literal constants! 8or e)ample- consider the followin& class- which encapsulates a C1style strin&=
*lass 45tring { $ubli*3 45tring(*onst *har2 in$ut); ^45tring(); "2 ... 2" $rivate3 *har2 str; #;
/ere- the constructor accepts a C1style strin&- then initializes the class to hold a copy of that strin&! ,ssumin& that we have a 5tring9u$li*ate function like the one descri#ed in the chapter on C strin&s- we could write the constructor for 45tring as follows=
45tring3345tring(*onst *har2 in$ut) 3 str(StringDuplicate!input$) { "" Em$t% *onstru*tor #
"otice that we were a#le to reference the constructor parameter in$ut inside the initializer list! This allows us to use information specific to the current instance of the class to perform initialization and is u#i5uitous in C++ code!
1 ;B1 1
'n some cases you may have a constructor that accepts a parameter with the same name as a class data mem#er! 8or e)ample- consider this the CationalEumber class- which encapsulates a rational num#er=
*lass CationalEumber { $ubli*3 CationalEumber(int numerator = 0, int denominator = 8); "2 ... 2" $rivate3 int numerator, denominator; #;
The followin& is a perfectly le&al constructor that initializes the data mem#ers to the values specified as paramet1 ers to the function=
CationalEumber33CationalEumber(int numerator, int denominator) 3 numerator!numerator$B denominator!denominator$ { "" Em$t% *onstru*tor #
C++ is smart enou&h to realize that the synta) numerator(numerator) means to initialize the numerator data mem#er to the value held #y the numerator parameter- rather than causin& a compile1time error or initial1 izin& the numerator data mem#er to itself! Code of this form mi&ht indicate that you need to rename the para1 meters to the constructor- #ut is perfectly le&al! Hn an unrelated note- notice that in the CationalEumber class declaration we specified that the numerator and denominator parameters to CationalEumber were e5ual to zero and one- respectively! These are default ar&uments to the constructor and allow us to call the constructor with fewer than two parameters! 'f we don't specify the parameters- C++ will use these values instead! 8or e)ample=
CationalEumber five/alves(K, H); CationalEumber three(F); "" 4alls *onstru*tor 'ith arguments (F, 8) CationalEumber Uero; "" 4alls *onstru*tor 'ith arguments (0, 8)
.ou can use default ar&uments in any function- provided that if a sin&le parameter has a default ar&ument every parameter after it also has a default! Thus the followin& code is ille&al=
void 9o5omething(int 7 = K, int %); "" Error Q % needs a default
0hen writin& functions that take default ar&uments- you should only specify the default ar&uments in the func1 tion prototype- not the function definition! 'f you don't prototype the function- however- you should specify the defaults in the definition! C++ is very strict a#out this and even if you specify the same defaults in #oth the pro1 totype and definition the compiler will complain! /hen Initiali3er Lists are #andatory 'nitializer lists are useful from an efficiency standpoint! /owever- there are times where initializer lists are the only syntactically le&al way to set up your instance varia#les!
1 ;B; 1
Suppose we'd like to make an o#*ect called 4ounter that supports two functions- in*rement and de*rementthat ad*ust an internal counter! /owever- we'd like to add the restriction that the 4ounter can't drop #elow 0 or e)ceed a user1defined limit! Thus we'll use a parametrized constructor that accepts an int representin& the ma)1 imum value for the 4ounter and stores it as an instance varia#le! Since the value of the upper limit will never chan&e- we'll mark it *onst so that we can't accidentally modify it in our code! The class definition for 4ounter thus looks somethin& like this=
*lass 4ounter { $ubli*3 4ounter(int ma7Dalue); void in*rement(); void de*rement(); int getDalue() *onst; $rivate3 int value; const int ma#imum; #;
Unfortunately- the a#ove code isn't valid #ecause in the second line we're assi&nin& a value to a varia#le marked *onst! Dven thou&h we're in the constructor- we still cannot violate the sanctity of *onstness! To fi) this- we'll initialize the value of ma7imum in the initializer list- so that ma7imum will #e initiali:ed to the value of ma7, Dalue- rather than assigned the value ma7Dalue! This is a su#tle distinction- so make sure to think a#out it #e1 fore proceedin&! The correct version of the constructor is thus
4ounter334ounter(int ma7Dalue) : )alue!-$B ma#imum!ma#5alue$ { "" Em$t% *onstru*tor #
"ote that we initialized ma7imum #ased on the constructor parameter ma7Dalue! 'nterestin&ly- if we had for&ot1 ten to initialize ma7imum in the initializer list- the compiler would have reported an error! 'n C++- it is mandatory to initialize all *onst primitive1type instance varia#les in an initializer list! Htherwise- you'd have constants whose values were total &ar#a&e! ,nother case where initializer lists are mandatory arises when a class contains o#*ects with no le&al or meanin&1 ful default constructor! Suppose- for e)ample- that you have an o#*ect that stores a CS1067IJ 5et of a custom type *ustom; with comparison call#ack :%4allba*+! Since the 5et re5uires you to specify the call#ack func1 tion in the constructor- and since you're always &oin& to use :%4allba*+ as that parameter- you mi&ht think that the synta) looks like this=
1 ;BA 1
Unfortunately- this isn't le&al C++ synta)! /owever- you can fi) this #y rewritin& the class as
*lass 5et1ra$$er4lass { $ubli*3 5et1ra$$er4lass(); $rivate3 Set.customT> mySet; "" Eote3 no $arameters s$e*ified #;
"ow- when the o#*ect is created- m%5et will have :%4allba*+ passed to its constructor and everythin& will work out correctly! #ulti&le Constructors 'f you write a class with multiple constructors Kwhich- after we discuss of copy constructors- will #e most of your classesL- you'll need to make initializer lists for each of your constructors! That is- an initializer list for one con1 structor won't invoke if a different constructor is called! -ractice -roblems 1! D)plain each of the steps involved in o#*ect construction! 0hy do they occur in the order they do> 0hy are each of them necessary> ;! 7ased on your understandin& of how classes are constructed- how do you think they are destructed> That is- when an o#*ect &oes out of scope or is e)plicitly deleted- in what order do the followin& occur= Gemory for the o#*ect is reclaimed! 2ata mem#er destructors invoke! H#*ect destructor invokes! A! 0hy must a function with a sin&le parameter with default value must have default values specified for each parameter afterwards> B! ",S, is currently workin& on Cro*ect Constellation- which aims to resume the lunar landin&s and ulti1 mately to land astronauts on Gars! The spacecraft under development consists of two parts ( an or#ital module called Hrion and a landin& vehicle called ,ltair! 2urin& a lunar mission- the Hrion vehicle will or#it the Goon while the ,ltair vehicle descends to the surface! The Hrion vehicle is desi&ned such that it does not necessarily have to have an ,ltair landin& module and conse5uently can #e used for low Darth or#it missions in addition to lunar *ourneys! .ou have #een hired to develop the systems software for the spacecraft! 7ecause software correctness and safety are critically important- you want to desi&n the system such that the compiler will alert you to as many potential software pro#lems as possi#le!
1 ;BB 1
Cha#ter /9% -ember Initiali:er Lists Suppose that we have two classes- one called =rion:odule and one called Altair:odule! Since every ,ltair landin& vehicle is associated with a sin&le =rion:odule- you want to define the Altair, :odule class such that it stores a pointer to its =rion:odule! The Altair:odule class should #e al1 lowed to modify the =rion:odule it points to Ksince it needs to #e a#le to dockIundock and possi#ly to #orrow CCU power for critical landin& maneuversL- #ut it should under no circumstance #e allowed to chan&e which =rion:odule it's associated with! /ere is a skeleton implementation of the Altair, :odule class=
*lass Altair:odule { $ubli*3 "2 4onstru*tor a**e$ts an =rion:odule re$resenting the =rion s$a*e*raft 2 this Altair is asso*iated 'ith, then sets u$ $arent:odule to $oint to 2 that =rion:odule. 2" Altair:odule(=rion:odule2 o'ner); "2 ... 2" $rivate3 =rion:odule2 $arent:odule; #;
3iven the a#ove description a#out what the Altair:odule should #e a#le to do with its owner =rion, :odule- appropriately insert *onst into the definition of the $arent:odule mem#er varia#le! Thenimplement the constructor Altair:odule such that the $arent:odule varia#le is initialized to point to the o'ner parameter!
Suppose that we're developin& a windowed operatin& system and want to write the code that draws windows on the screen! 0e decide to create a class 1indo' that e)ports a dra'1indo' function! 'n order to display the window correctly- dra'1indo' needs access to a <alette o#*ect that performs primitive renderin& operations like drawin& lines- arcs- and filled poly&ons! ,ssume that we know that the window will always #e drawn with the same <alette! 3iven this description- we mi&ht initially desi&n 1indo' so that it has a <alette as a data mem#er- as shown here=
*lass 1indo' { $ubli*3 "2 ... *onstru*tors, destru*tors, et*. ...2" "2 All 'indo's *an dra' themselves. 2" void dra'1indo'(); $rivate3 "2 ... other data members ... 2" Palette pal; #;
"ow- every window has its own palette and can draw itself appropriately! There's nothin& fundamentally wron& with this setup- #ut it contains a small flaw! three different window o#*ects! 'n memory- those o#*ects would look like this= Fir(t In(tan&' S'&ond In(tan&' T1ird In(tan&' et's suppose that we have
Palette pal
Palette pal
Palette pal
Since $al is a data mem#er of 1indo'- every 1indo' has its own <alette! There mi&ht #e an ar#itrary num1 #er of windows on screen at any time- #ut there's only one screen and it doesn't make sense for every window to have its own palette! ,fter all- each window is likely to use a similar set of colors as those used #y every other window- and it seems more reasona#le for every window to share a sin&le palette- as shown here=
Palette pal /ow can we model this in code> Usin& the techni5ues so far- we have few options! 8irst- we could create a &lo#al <alette o#*ect- then have each 1indo' use this &lo#al <alette! This is a particularly #ad choice for two reasons=
It uses global 7ariables! 'ndependently of any other stren&ths and weaknesses of this approach- &lo#al varia#les are a #i& pro&rammin& no1no! 3lo#als can #e accessed and modified anywhere in the pro1 &ram- makin& de#u&&in& difficult should pro#lems arise! 't is also possi#le to inadvertently reference &lo#al varia#les inside of unrelated functions- leadin& to su#tle #u&s that can take down the entire pro1 &ram! It lacks enca&sulation! 7ecause the <alette is a &lo#al varia#le- other parts of the pro&ram can modi1 fy the 1indo' <alette without &oin& throu&h the 1indo' class! This leads to the same sorts of pro#1 lems possi#le with pu#lic data mem#ers= class invariants #reakin& une)pectedly- code written with one version of 1indo' #reakin& when the 1indo' is updated- etc!
Second- we could have each 1indo' o#*ect contain a #ointer to a <alette o#*ect- then pass a shared <alette as a parameter to each instance of 1indo'! 8or e)ample- we could desi&n the class like this=
*lass 1indo' { $ubli*3 =indo6!PaletteR pB TR ... RT$; "2 ... other *onstru*tors, destru*tors, et*. ...2" "2 All 'indo's *an dra' themselves. 2" void dra'1indo'(); $rivate3 "2 ... other data members ... 2" PaletteR pal; #;
This allows us to share a sin&le <alette across multiple 1indo's and looks remarka#ly like the a#ove dia1 &ram! /owever- this approach has its weaknesses=
1 ;B@ 1
It com&licates =indo6 creation! et's think a#out how we would &o a#out creatin& 1indo's with this setup! 7efore creatin& our first 1indo'- we'd need to create a <alette to associate with it- as shown here=
<alette2 $ = ne' <alette; 1indo'2 ' = ne' 1indo'(p, "2 ... 2");
'f later we want to create more 1indo's- we'd need to keep track of the ori&inal <alette we used so that we can provide it as a parameter to the 1indo' constructor! This means that any part of the pro1 &ram responsi#le for 1indo' mana&ement needs to know a#out the shared <alette! It 7iolates enca&sulation! Clients of 1indo' shouldn't have to know how 1indo's are implementedand #y re5uirin& 1indo' users to e)plicitly mana&e the shared <alette we're e)posin& too much detail a#out the 1indo' class! This approach also locks us in to a fi)ed implementation! 8or e)ample- what if we want to switch from <alette to a ;urbo<alette that draws twice as 5uickly> 0ith the current ap1 proach all 1indo' clients would need to up&rade their code to match the new implementation! It com&licates resource management! 0ho is responsi#le for cleanin& up the 1indo' <alette at the end of the pro&ram> 1indo' clients shouldn't have to- since the <alette really #elon&s to the 1indo' class! 7ut no particular 1indo' owns the <alette- since each instance of 1indo' shares a sin&le <alette! There are systems we could use to make cleanup work correctly Ksee the later e)tended e)1 ample on smart pointers for one possi#ilityL- #ut they increase pro&ram comple)ity!
7oth of these approaches have their individual stren&ths- #ut have draw#acks that outwei&h their #enefits! et's review e)actly what we're tryin& to do! 0e'd like to have a sin&le <alette that's shared across multiple differ1 ent 1indo's! Goreover- we'd like this <alette to o#ey all of the rules normally applica#le to class desi&n= it should #e encapsulated and it should #e mana&ed #y the class rather than clients! Usin& the techni5ues we've covered so far it is difficult to construct a solution with these properties! 8or a clean solution- we'll need to intro1 duce a new lan&ua&e feature= static data members! Static 9ata #embers Static data mem#ers are data mem#ers associated with a class as a whole rather than a particular instance of that class! 'n the a#ove e)ample with 1indo' and <alette- the window <alette is associated with ,indo-s in general rather than any one specific 1indo' o#*ect and is an ideal candidate for a static data mem#er! 'n many ways static data mem#ers #ehave similarly to re&ular data mem#ers! 8or e)ample- if a class has a private static data mem#er- only mem#er functions of the class can access that varia#le! /owever- static data mem#ers #ehave differently from other data mem#ers #ecause there is only one copy of each static data mem#er! Dach instance of a class containin& a static data mem#er shares the same version of that data mem#er! That is- if a sin&le class instance chan&es a static data mem#er- the chan&e affects all instances of that class! The synta) for declarin& static data mem#ers is sli&htly more complicated than for declarin& nonstatic data mem#ers! There are two steps= declaration and de"inition! 8or e)ample- if we want to create a static <alette o#*ect inside of 1indo'- we could declare the varia#le as shown here=
1 ;BE 1
*lass 1indo' { $ubli*3 "2 ... *onstru*tors, destru*tors, et*. ...2" "2 All 'indo's *an dra' themselves. 2" void dra'1indo'(); $rivate3 "2 ... other data members ... 2" static Palette sharedPal; #;
/ere- shared<al is declared as a static data mem#er usin& the stati* keyword! 7ut while we've declared shared<al as a static data mem#er- we haven't de"ined shared<al yet! Guch in the same way that functions are separated into prototypes KdeclarationsL and implementations KdefinitionsL- static data mem#ers have to #e #oth declared inside the class in which they reside and defined inside the !cpp file associated with that class! 8or the a#ove e)ample- inside the !cpp file for the 1indo' class- we would write
<alette =indo6::sharedPal;
There are two important points to note here! 8irst- when definin& the stati* varia#le- we must use the fully1 5ualified name K1indo'33shared<alL instead of *ust its local name Kshared<alL! Second- we do not repeat the stati* keyword durin& the varia#le declaration ( otherwise- the compiler will think we're doin& somethin& completely different Ksee the 4Gore to D)plore6 sectionL! .ou may have noticed that even thou&h 1indo'33shared<al is private we're still allowed to use it outside the class! This is only le&al durin& defini1 tion- and outside of this one conte)t it is ille&al to use 1indo'33shared<al outside of the 1indo' class! 'n some circumstances you may want to create a class containin& a static data mem#er where the data mem#er needs to take on an initial value! 8or e)ample- if we want to create a class containin& an int as a static data mem#er- we would pro#a#ly want to initialize the int to a particular value! 3iven the followin& class declara1 tion=
*lass :%4lass { $ubli*3 void do5omething(); $rivate3 stati* int m%5tati*9ata; #;
,s you'd e)pect- this means that m%5tati*9ata initially holds the value 1A@! ,lthou&h the synta) for creatin& a static data mem#er can #e intimidatin&- once initialized static data mem#ers look *ust like re&ular data mem#ers! 8or e)ample- consider the followin& mem#er function=
void :%4lass33do5omething() { 33myStaticData; "" :odifies m%5tati*9ata for all *lasses #
"othin& here seems all that out1of1the1ordinary and this code will work *ust fine! "ote- however- that modifica1 tions to m%5tati*9ata are visi#le to all other instances of :%4lass!
1 ;B< 1
et's consider another e)ample where static data mem#ers can #e useful! Suppose that you're de#u&&in& the windowin& code from #efore and you're pretty sure that you've for&otten to delete all instances of 1indo' that you've allocated with ne'! Since C++ won't &ive you any warnin&s a#out this- you'll need to do the instance countin& yourself! The num#er of active instances of a class is class1specific information that doesn't pertain to any specific instance of the o#*ect- and this is the perfect spot to use static data mem#ers! To handle our instance countin&- we'll modify the 1indo' definition as follows=
*lass 1indo' { $ubli*3 "2 ... *onstru*tors, destru*tors, et*. ...2" void dra'1indo'(); $rivate3 "2 ... other data members ... 2" stati* <alette shared<al; static int numInstances; #;
0e know that whenever we create a new instance of a class- the class's constructor will #e called! This means that if we increment num(nstan*es inside the 1indo' constructor- we'll correctly track the num#er of times the a 1indo' has #een created! Thus- we'll rewrite the 1indo' constructor as follows=
1indo'331indo'("2 ... 2") { "2 ... All older initialiUation *ode ... 2" 33numInstances; #
Similarly- we'll decrement num(nstan*es in the 1indo' destructor! 0e'll also have the destructor print out a messa&e if this is the last remainin& instance so we can see how many instances are left=
1indo'33^1indo'() { "2 ... All older *leanu$ *ode ... 2" --numInstances; i !numInstances ,, -$ cout .. "/o more =indo6s%R" .. endl; #
Static #ember !unctions 'nside of mem#er functions- a special varia#le called this acts as a pointer to the current o#*ect! 0henever you access a class's instance varia#les inside a mem#er function- you're really accessin& the instance varia#les of the this pointer! 8or e)ample- &iven the followin& <oint class=
1 ;?0 1
*lass <oint { $ubli*3 <oint(int 76o*, int %6o*); int getP() *onst; int getR() *onst; $rivate3 int 7, %; #;
/ow does C++ know what value this refers to> The answer is su#tle #ut important! Suppose that we have a <oint o#*ect called $t and that we write the followin& code=
int 7 = $t.getP();
The C++ compiler converts this into code alon& the lines of
int # , Point::getV!Spt$;
This is not le&al C++ code- #ut illustrates what's &oin& on #ehind the scenes whenever you call a mem#er func1 tion! The mechanism #ehind mem#er function calls should rarely #e of interest to you as a pro&rammer! /owever- the fact that an 61ar&ument mem#er function is really an K6+1L1ar&ument free function can cause pro#lems in a few places! 8or e)ample- suppose that we want to provide a comparison function for <oints that looks like this=
*lass <oint { $ubli*3 <oint(int 76o*, int %6o*); int getP() *onst; int getR() *onst; bool compareT6oPoints!const PointS oneB const PointS t6o$ const $rivate3 int 7, %; #;
1 ;?1 1
'f you have a ve*tor <oint) that you'd like to pass to the ST sort al&orithm- you'll run into trou#le if you try to use this synta)=
sort(m%De*tor.begin(), m%De*tor.end(), SPoint::compareT6oPoints); "" Error0
The pro#lem is that sort e)pects a comparison function that takes two parameters and returns a bool! /owever- <oint33*om$are;'o<oints takes three parameters= two points to compare and an invisi#le 4this6 pointer! Thus the a#ove code will &enerate an error! 'f you want to define a comparison or predicate function inside of a class- you'll want that mem#er function to not have an invisi#le this! 0hat does this mean from a practical standpoint> , mem#er function without a this pointer does not have a receiver o#*ect- and thus can only operate on its parameters and any static data mem#ers of the class it's declared in Ksince that data is particular to the class rather than any particular instanceL! 8unctions of this sort are called static member "unctions and can #e created usin& the stati* keyword! 'n the a#ove e)ample with <oint- we could create the <oint comparison function as a stati* mem#er function us1 in& the followin& synta)=
*lass <oint { $ubli*3 <oint(int 76o*, int %6o*); int getP() *onst; int getR() *onst; static bool *om$are;'o<oints(*onst <oint? one, *onst <oint? t'o) *onst; $rivate3 int 7, %; #; bool <oint33*om$are;'o<oints(*onst <oint? one, *onst <oint? t'o) *onst { if(one.7 0= t'o.7) return one.7 t'o.7; return one.% t'o.%; #
"ow- the a#ove call to sort will work since *om$are;'o<oints would no lon&er have a this pointer! Unlike static data mem#ers- when writin& static mem#er functions you do not need to separate the code out into a separate declaration and definition! .ou may want to do so anyway- thou&h! et's return to our earlier e)ample a#out trackin& the num#er of 1indo' instances currently in use! 0hile it's nice that the destructor prints out a messa&e when the last instance has #een cleaned up- we'd prefer a more ro1 #ust model where we can check how many more copies of the class e)ist! This function is not specific to a par1 ticular class instance- so we'll make this function static! 0e'll call this function getCemaining(nstan*es and implement it as shown here=
1 ;?; 1
*lass 1indo' { $ubli*3 "2 ... *onstru*tors, destru*tors, et*. ...2" void dra'1indo'(); static $rivate3 "2 ... stati* stati* #; int getCemainingInstances!$; other data members ... 2" <alette shared<al; int num(nstan*es;
,s with static data- note that when definin& static mem#er functions- you omit the stati* keyword! Hnly put stati* inside the class declaration! .ou can invoke static mem#er functions either usin& the familiar ob>e*t.method synta)- or you can use the fully 5ualified name of the function! 8or e)ample- with the a#ove e)ample- we could check how many remain1 in& instances there were of the :%4lass class #y callin& getCemaining(nstan*es as follows=
*out :%4lass33getCemaining(nstan*es() endl;
Unfortunately- the *onst and stati* keywords do not always interact intuitively! Hne of the #i&&est issues to #e aware of is that *onst mem#er functions can modify stati* data! 8or e)ample- consider the followin& class=
*lass 4onst5tati*4lass { $ubli*3 void *onst&n() *onst; $rivate3 stati* int stati*9ata; #; int 4onst5tati*4lass33stati*9ata = 0;
,lthou&h the a#ove implementation of *onst&n increments a static data mem#er- the a#ove code will compile and run without any pro#lems! The reason is that the code doesn't modify the receiver o#*ect! Static data mem1 #ers are not associated with a particular class instance- so modifications to static data mem#ers do not chan&e the state of any one instance of the class!
1 ;?A 1
,dditionally- since stati* mem#er functions don't have a this pointer- they cannot #e declared *onst! 'n the case of getEum(nstan*es- this means that althou&h the function doesn't modify any class data- we still cannot mark it *onst! Integral Class Constants There is one other topic concernin& the interaction of *onst and stati*= class constants! Suppose we want to make a constant varia#le accessi#le only in the conte)t of a class! 0hat we want is a varia#le that's *onst- so it's immuta#le- and stati*- so that all copies of the class share the data! 't's le&al to declare these varia#les like this=
*lass 4lass4onstantE7am$le { $ubli*3 "2 =mitted. 2" $rivate3 static const int 0y;onstant; #; const int 4lass4onstantE7am$le33:%4onstant = 8FG;
"ote the *onst in the definition of 4lass4onstantE7am$le33:%4onstant! /owever- since the dou#le declarationIdefinition can #e a #it tedious- C++ has a #uilt1in shorthand you can use when declarin& class constants of inte&ral types! That is- if you have a stati* *onst int or a stati* *onst *har- you can condense the definition and declaration into a sin&le statement #y writin&R
*lass 4lass4onstantE7am$le { $ubli*3 "2 =mitted. 2" $rivate3 static const int 0y;onstant , 7DE; "" 4ondense into a single line #;
This shorthand is common in professional code! 7e careful when usin& the shorthand- thou&h- #ecause some older compilers won't correctly interpret it! ,lso- #e aware that this only works with integral ty#es- so you can1 not initialize a stati* *onst double or stati* *onst float this way! #ore to $%&lore 0hile this chapter discusses stati* in relation to classes- there are three other meanin&s of stati*! The first is static lin*age s#eci"ication- which means that the specified mem#er function or &lo#al varia#le can only #e ac1 cessed #y functions defined in the current file! ,nother is static local !ariables- local varia#les whose values are preserved #etween function calls! These techni5ues are common in practice- so you should #e sure to check a reference te)t for some more info on their su#tleties! -ractice -roblems 1! D)plain why stati* mem#er functions cannot #e marked *onst! ;! 0rite a class @niVuel%(dentified such that each instance of the class has a uni5ue '2 num#er de1 termined #y takin& the '2 num#er of the previous instance and addin& one! The first instance should have '2 num#er 1! Thus the third instance of the class will have '2 A- the ninety1si)th instance <6- etc! ,lso write a *onst1correct mem#er function get@niVue(9 that returns the class's uni5ue '2! 2on't worry a#out reusin& older '2s if their o#*ects &o out of scope! M
1 ;?B 1
A! The C header file *stdlib) e)ports two functions for random num#er &eneration ( srand- which seeds the randomizer- and rand- which &enerates a pseudorandom int #etween 0 and the constant CAE9B:AP! To make the pseudorandom values of rand appear truly random- you can seed the random1 izer usin& the value returned #y the time function e)ported from *time)! The synta) is srand((un, signed int)time(E@66))! 0rite a class CandomGenerator that e)ports a function ne7t that re1 turns a random double in the ran&e O0- 1L! 0hen created- the CandomGenerator class should seed the randomizer with srand only if a previous instance of CandomGenerator hasn't already seeded it! B! Should you specify values for static data mem#ers in an initializer list> 0hy or why not> ?! Should you ever mark static data mem#ers mutable> 0hy or why not>
0hen desi&nin& classes- you mi&ht find that certain data types can lo&ically #e converted into o#*ects of the type you're creatin&! 8or e)ample- when desi&nin& the C++ string class- you mi&ht note that *har 2 C strin&s could have a defined conversion to string o#*ects! 'n these situations- it may #e useful to define im#licit con!ersions #etween the two types! To define implicit conversions- C++ uses con!ersion constructors- constructors that accept a sin&le parameter and initialize an o#*ect to #e a copy of that parameter! 0hile useful- conversion constructors have several ma*or idiosyncrasies- especially when C++ interprets normal constructors as conversion constructors! This chapter e)plores implicit type conversions- conversion construct1 ors- and how to prevent codin& errors stemmin& from inadvertent conversion constructors! Im&licit Con7ersions 'n C++- an im#licit con!ersion is a conversion from one type to another that doesn't re5uire an e)plicit typecast! Cerhaps the simplest e)ample is the followin& conversion from an int to a double=
double m%9ouble = 8FG ! H.G8IHI;
/ere- even thou&h 1A@ is an int while ;!@1E;E is a double- C++ will implicitly convert it to a double so the operation can proceed smoothly! 0hen C++ performs implicit conversions- it does not 4ma&ically6 fi&ure out how to transform one data type into another! Father- it creates a temporary o#*ect of the correct type that's initialized to the value of the implicitly converted o#*ect! Thus the a#ove code is e5uivalent to
double tem$ = (double)m%(nt; double m%9ouble = tem$ ! H.G8IHI;
't's important to remem#er that when usin& implicit conversions you are creatin& temporary o#*ects! 0ith prim1 itive types this is hardly noticea#le- #ut makes a difference when workin& with classes! 8or e)ample- consider the followin& code=
string m%5tring = .;his .; string m%=ther5tring = m%5tring ! .is a string.;
"ote that in the second line- we're addin& a C++ string to a C *har 2 strin&! Thus C++ will implicitly con1 vert 4is a strin&6 into a C++ string #y storin& it in a temporary o#*ect! The a#ove code- therefore- is e5uivalent to
string m%5tring = .;his .; string tem$5tr = .is a string.; string m%=ther5tring = m%5tring ! tem$5tr;
"otice that in #oth of the a#ove e)amples- at some point C++ needed a way to initialize a temporary o#*ect to #e e5ual to an e)istin& o#*ect of a different type! 'n the first e)ample- we made a temporary double that was e5ual to an int- and in the second- a temporary string e5ual to a *har 2!Q 0hen C++ performs these conversionsit uses a special function called a con!ersion constructor to initialize the new o#*ect! Conversion constructors
Q Technically speakin&- this isn't 5uite what happens- since there's a special form of the + operator that works on a mi) of C strin&s and C++ strin&s! /owever- for this purposes of this discussion- we can safely i&nore this!
1 ;?6 1
are simply class constructors that accept a sin&le parameter and initialize the new o#*ect to a copy of the para1 meter! 'n the double e)ample- the newly1created double had the same value as the int parameter! 0ith the C++ string- the temporary string was e5uivalent to the C strin&! C++ will invoke conversion constructors whenever an o#*ect of one type is used in an e)pression where an o#1 *ect of a different type is e)pected! Thus- if you pass a *har 2 to a function acceptin& a C++ string- the string will #e initialized to the *har 2 in its conversion constructor! Similarly- if you have a function like this one=
string :%&un*tion() { return .;his is a string0.; #
The temporary o#*ect created for the return value will #e initialized to the C strin& 4This is a strin&U6 usin& the conversion constructor! /riting Con7ersion Constructors To see how to write conversion constructors- we'll use the e)ample of a 45tring class that's essentially our own version of the C++ string class! 'nternally- 45tring stores the strin& as a C strin& called the5tring! Since we'd like to define an implicit conversion from *har 2 to 45tring- we'll declare a conversion constructor- as shown #elow=
*lass 45tring { $ubli*3 ;String!const charR other$; "2 =ther member fun*tions. 2" $rivate3 *har2 the5tring; #;
"ow- whenever we have a *har 2 C strin&- we can implicitly convert it to a 45tring! 'n the a#ove case- we defined an implicit conversion from *har 2 C strin&s to our special class 45tring! /owever- it's possi#le to define a second conversion from a C++ string to our new 45tring class! 'n factC++ allows you to provide conversion constructors for any num#er of different types that may or may not #e primitive types! /ere's a modified 45tring interface that provides two conversion constructors from string and *har 2=
1 ;?@ 1
A /ord on 1eadability 0hen desi&nin& classes with conversion constructors- it's easy to &et carried away #y addin& too many implicit conversions! 8or e)ample- suppose that for the 45tring class we want to define a conversion constructor that converts ints to their strin& representations! This is completely le&al- #ut can result in confusin& or unreada#le code! 8or e)ample- if there's an implicit conversion from ints to 45trings- then we can write code like this=
45tring m%5tr = m%(nt ! 8FG;
The resultin& 45tring would then hold a strin& version of the value of m%(nt ! 8FG- not the strin& composed of the concatenation of the value of m%(nt and the strin& 41A@!6 This can #e a #it confusin& and can lead to counterintuitive code! 0orse- since C++ does not normally define implicit conversions #etween numeric and strin& types- people unfamiliar with the 45tring implementation mi&ht &et confused #y lines assi&nin& ints to 45trings! 'n &eneral- when workin& with conversion constructors- make sure that the conversion is intuitive and consistent with ma*or C++ conventions! 'f not- consider usin& non1constructor mem#er functions! 8or e)ample- if we would like to #e a#le to convert int values into their strin& representations- we mi&ht want to make a &lo#al function int;o5tring that performs the conversion! This way- someone readin& the code could e)plicitly see that we're convertin& an int to a 45tring! -roblems with Con7ersion Constructors 0hile conversion constructors are 5uite useful in a wide num#er of circumstances- the fact that C++ automatic1 ally treats all sin&le1parameter constructors as conversion constructors can lead to convoluted or nonsensical code! Hne of my favorite e)amples of 4conversion1constructors1&one1wron&6 comes from an older version of the CS1067IJ ,2T class li#raries! Hri&inally- the CS1067IJ De*tor was defined as
tem$late t%$ename Elem;%$e) *lass De*tor { $ubli*3 De*tor(int siUe/int = 80); "" /int about the siUe of the De*tor "2 ... 2" #;
"othin& seems all that out1of1the1ordinary here ( we have a De*tor template class that lets you &ive the class a hint a#out the num#er of elements you will #e storin& in it! /owever- #ecause the constructor accepts a sin&le parameter- C++ will interpret it as a conversion constructor and thus will let us implicitly convert from ints to De*tors! This can lead to some very stran&e #ehavior! 8or e)ample- &iven the a#ove class definition- consider the followin& code=
De*tor int) m%De*tor = 8FG;
1 ;?E 1
This code- while nonsensical- is le&al and e5uivalent to De*tor int) m%De*tor(8FG)! 8ortunately- this pro#a#ly won't cause any pro#lems at runtime ( it *ust doesn't make sense in code! /owever- suppose we have the followin& code=
void 9o5omething(De*tor int)? m%De*tor) { my5ector , /U11; #
This code is totally le&al even thou&h it makes no lo&ical sense! Since E@66 is -defined to #e 0- The a#ove code will create a new De*tor int) initialized with the parameter 0 and then assi&n it to m%De*tor! 'n other words- the a#ove code is e5uivalent to
void 9o5omething(De*tor int)? m%De*tor) { De*tor int) tem$De*tor(0); m%De*tor = tem$De*tor; # tem$De*tor is empty when it's created- so when we assi&n tem$De*tor to m%De*tor- we'll set m%De*tor to the empty vector! Thus the nonsensical line m%De*tor = 0 is effectively an o#fuscated call to m%De*tor.*lear()!
This is a 5uintessential e)ample of why conversion constructors can #e dan&erous! 0hen writin& sin&le1ar&u1 ment constructors- you run the risk of lettin& C++ interpret your constructor as a conversion constructor!
e#plicit
To prevent pro#lems like the one descri#ed a#ove- C++ provides the e7$li*it keyword to indicate that a con1 structor must not #e interpreted as a conversion constructor! 'f a constructor is marked e7$li*it- it indicates that the constructor should not #e considered for the purposes of implicit conversions! 8or e)ample- let's look at the current version of the CS1067IJ De*tor- which has its constructor marked e7$li*it=
tem$late t%$ename Elem;%$e) *lass De*tor { $ubli*3 e#plicit De*tor(int siUe/int = 80); "" /int the siUe of the De*tor "2 ... 2" #;
0e'll &et a compile1time error since there's no implicit conversion from int to De*tor int)! /owever- we can still write
De*tor int) m%De*tor(80);
0hich is what we were tryin& to accomplish in the first place! Similarly- we eliminate the m%De*tor = 0 errorand a whole host of other nasty pro#lems!
1 ;?< 1
0hen desi&nin& classes- if you have a sin&le1ar&ument constructor that is not intended as a conversion functionyou must mark it e7$li*it to avoid runnin& into the 4implicit conversion6 trap! 0hile indeed this is more work for you as an implementer- it will make your code safer and more sta#le! -ractice -roblems These practice pro#lems concern a CationalEumber class that encapsulates a rational num#er Kthat is- a num1 #er e)pressi#le as the 5uotient of two inte&ersL! CationalEumber is declared as follows=
*lass CationalEumber { $ubli*3 CationalEumber(int num = 0, int denom = 8) 3 numerator(num), denominator(denom) {# double getDalue() *onst { return stati*B*ast double)(numerator) " denominator; # void setEumerator(int value) { numerator = value; # void set9enominator(int value) { denominator = value; # $rivate3 int numerator, denominator; #;
The constructor to CationalEumber accepts two parameters that have default values! This means that if you omit one or more of the parameters to CationalEumber- they'll #e filled in usin& the defaults! Thus all three of the followin& lines of code are le&al=
CationalEumber Uero; "" Dalue is 0 " 8 = 0 CationalEumber five(K); "" Dalue is K " 8 = K CationalEumber $iA$$ro7(FKK, 88F); "" Dalue is FKK"88F = F.8J8KLHLH0F...
1! D)plain why the CationalEumber constructor is a conversion constructor! ;! 0rite a CealEumber class that encapsulates a real num#er Kany num#er on the num#er lineL! 't should have a conversion constructor that accepts a double and a default constructor that sets the value to zero! <6ote% 4ou only need to write one constructor) 2se .ational/umber as an e7am#le= A! 0rite a conversion constructor that converts CationalEumbers into CealEumbers! 3eneral 5uestions= B! 'f a constructor has two or more ar&uments and no default values- can it #e a conversion constructor> ?! C++ will apply at most one implicit type conversion at a time! That is- if you define three types A- O- and 4 such that A is implicitly converti#le to O and O is implicitly converti#le to 4- C++ will not automatic1 ally convert o#*ects of type A to o#*ects of type 4! 3ive an reason for why this mi&ht #e! <+int% Add another im#licit con!ersion between these ty#es= M 6! 'mplement the conversion constructor convertin& a C++ string to our special 45tring class!
Consider the ST ve*tor! 'nternally- ve*tor is #acked #y a dynamically1allocated array whose size &rows when additional space is needed for more elements! 8or e)ample- a ten1element ve*tor mi&ht store those ele1 ments in an array of size si)teen- increasin& the array size if we call $ushBba*+ seven more times! 3iven this description- consider the followin& code=
ve*tor int) one(E@:B(E;5); for(int + = 0; + one.siUe(); !!+) one.$ushBba*+(+); ve*tor int) t'o = one;
'n the first three lines- we fill one with the first E@:B(E;5 inte&ers- and in the last line we create a new ve*tor called t'o that's a copy of one! /ow does C++ know how to correctly copy the data from one into t'o> 't can't simply copy the pointer to the dynamically1allocated array from one into t'o- since that would cause one and t'o to share the same data and chan&es to one ve*tor would show up in the other! Somehow C++ is aware that to copy a ve*tor it needs to dynamically allocate a new array of elements- then copy the elements from the source to the destination! This is not done #y ma&ic- #ut #y two special functions called the co#y constructor and the assignment o#erator- which control how to copy instances of a particular class! Used correctly- copy constructors and assi&nment operators let you fine1tune the way that C++ copies instances of your custom types! ,s you'll see in a later e)tended e)ample- these functions can also #e used to define spe1 cial copy #ehavior to ele&antly and efficiently mana&e resources! /owever- copy constructors and assi&nment operators are amon& the most difficult features of the C++ lan&ua&e and unless you understand their su#tleties and comple)ities you are likely to end up with code containin& dan&erous #u&s! This chapter introduces copy semantics- discusses the copy constructor and assi&nment operator- shows how to implement them- and hi&h1 li&hts potential sources of error! Two Styles of Co&ying5 Assignment and Initiali3ation 7efore discussin& the particulars of the copy constructor and assi&nment operator- we first need to dissect e)1 actly how an o#*ect can #e copied! 'n order to copy an o#*ect- we first have to answer an important 5uestion ( where do we put the copy> 2o we store it in a new o#*ect- or do we reuse an e)istin& o#*ect> These two options are fundamentally different from one another and C++ makes an e)plicit distinction #etween them! The first op1 tion ( puttin& the copy into a new location ( creates the copy #y initiali:ing the new o#*ect to the value of the o#*ect to copy! The second ( storin& the copy in an e)istin& varia#le ( creates the copy #y assigning the e)istin& o#*ect the value of the o#*ect to copy! 0hat do these two copy mechanisms look like in C++> That is- when is an o#*ect initialized to a value- and when is it assi&ned a new value> 'n C++- initialization can occur in three different places= 1! A !ariable is created as a co#y o" an e7isting !alue) 8or e)ample- suppose we write the followin& code=
:%4lass one; 0y;lass t6o , one;
/ere- since t'o is told to hold the value of one- C++ will initiali:e t'o as a copy of one! ,lthou&h it looks like we're assi&nin& a value to t'o usin& the = operator- since it is a newly1created o#*ect- the = indicates initialization- not assi&nment! 'n fact- the a#ove code is e5uivalent to the more e)plicit initial1 ization code #elow=
1 ;6; 1
:%4lass one; 0y;lass t6o!one$;
This synta) makes more clear that t'o is #ein& created as a copy of one- indicatin& initialization rather than assi&nment! ;! An object is #assed by !alue to a "unction) Consider the followin& function=
void :%&un*tion(0y;lass arg) { "2 ... 2" #
'f we write
:%4lass m*; :%&un*tion(m*);
Then the function :%&un*tion somehow has to set up the value of arg inside the function to have the same value as m* outside the function! Since arg is a new varia#le- C++ will initiali:e it as a copy of m*! A! An object is returned "rom a "unction by !alue) Suppose we have the followin& function=
:%4lass :%&un*tion() { :%4lass m*; return mc; #
0hen the statement return m* e)ecutes- C++ needs to return the m* o#*ect from the function! /owever- m* is a local varia#le inside the :%&un*tion function- and to communicate its value to the :%&un*tion caller C++ needs to create a copy of m* #efore it is lost! This is done #y creatin& a tem1 porary :%4lass o#*ect for the return value- then initiali:ing it to #e a copy of m*! "otice that in all three cases- initialization took place #ecause some new o#*ect was created as a copy of an e)ist1 in& o#*ect! 'n the first case this was a new local varia#le- in the second a function parameter- and in the third a temporary o#*ect! ,ssi&nment in C++ is much simpler than initialization and only occurs if an e)istin& o#*ect is e)plicitly assi&ned a new value! 8or e)ample- the followin& code will assign t'o the value of one- rather than initiali:ing t'o to one=
:%4lass one, t'o; t6o , one;
't can #e tricky to differentiate #etween initialization and assi&nment #ecause in some cases the synta) is almost identical! 8or e)ample- if we rewrite the a#ove code as
:%4lass one; 0y;lass t6o , one; t'o is now initialized to one #ecause it is declared as a new varia#le! ,lways remem#er that the assi&nment
1 ;6A 1
0hy is it important to differentiate #etween assi&nment and initialization> ,fter all- they're 5uite similarR in #oth cases we end up with a new copy of an e)istin& o#*ect! /owever- assi&nment and initialization are funda1 mentally different operations! 0hen initiali:ing a new o#*ect as a copy of an e)istin& o#*ect- we simply need to copy the e)istin& o#*ect into the new o#*ect! 0hen assigning an e)istin& o#*ect a new value- the e)istin& o#1 *ect's value ceases to #e and we must make sure to clean up any resources the o#*ect may have allocated #efore settin& it to the new value! 'n other words- initialization is a strai&ht copy- while assi&nment is cleanup followed #y a copy! This distinction will #ecome manifest in the code we will write for the copy functions later in this chapter! Co&y !unctions5 Co&y Constructors and Assignment 2&erators 7ecause initialization and assi&nment are separate tasks- C++ handles them throu&h two different functions called the co#y constructor and the assignment o#erator! The copy constructor is a special constructor respons1 i#le for initializin& new class instances as copies of e)istin& instances of the class! The assi&nment operator is a special function called an o!erloaded o#erator Ksee the chapter on operator overloadin& for more detailsL re1 sponsi#le for assi&nin& the receiver o#*ect the value of some other instance of the o#*ect! Thus the code
:%4lass one; :%4lass t'o = one;
will initialize t'o to one usin& the copy constructor- while the code
:%4lass one, t'o; t'o = one;
will assi&n one to t'o usin& the assi&nment operator! Syntactically- the copy constructor is written as a one1ar&ument constructor whose parameter is another instance of the class accepted #y reference1to1*onst! 8or e)ample- &iven the followin& class=
*lass :%4lass { $ubli*3 :%4lass(); ^:%4lass(); #; "2 ... 2"
The synta) for the assi&nment operator is su#stantially more comple) than that of the copy constructor #ecause it is an o!erloaded o#erator- a special function that defines how the #uilt1in K operator applies to o#*ects of this class! 0e'll cover operator overloadin& in a later chapter- #ut for now we can use the fact that the assi&nment op1 erator is a function named o$erator =! The function name o$erator = can #e written with as much
1 ;6B 1
whitespace as you'd like #etween o$erator and =- so o$erator= and o$erator = are #oth valid! 8or reas1 ons that will #ecome clearer later in the chapter- the assi&nment operator should accept as a parameter another instance of the class #y reference1to1*onst and should return a non1*onst reference to an o#*ect of the class type! 8or a concrete e)ample- here's the assi&nment operator for :%4lass=
*lass :%4lass { $ubli*3 :%4lass(); ^:%4lass(); :%4lass(*onst :%4lass? other); "" 4o$% *onstru*tor 0y;lassS operator , !const 0y;lassS other$; "" Assignment o$erator "2 ... 2" #;
0e'll defer discussin& e)actly why this synta) is correct until later- so for now you should take it on faith! /hat C++ 9oes !or Oou Unless you specify otherwise- C++ will automatically provide any class you write with a #asic copy constructor and assi&nment operator that invoke the copy constructors and assi&nment operators of all the class's data mem1 #ers! 'n many cases- this is e)actly what you want! 8or e)ample- consider the followin& class=
*lass 9efault4lass { $ubli*3 "2 ... 2" $rivate3 int m%(nt; string m%5tring; #;
The line 9efault4lass t'o = one will invoke the copy constructor for 9efault4lass! Since we haven't e)plicitly provided our own copy constructor- C++ will initialize t'o.m%(nt to the value of one.m%(nt and t'o.m%5tring to one.m%5tring! Since int is a primitive and string has a well1defined copy constructorthis code is totally fine! /owever- in many cases this is not the #ehavior you want! et's consider the e)ample of a class 45tring that acts as a wrapper for a C strin&! Suppose we define 45tring as shown here=
*lass 45tring { $ubli*3 45tring(); ^45tring(); "2 Eote3 Eo *o$% *onstru*tor or assignment o$erator 2" $rivate3 *har2 the5tring; #;
1 ;6? 1
/ere- if we rely on C++'s default copy constructor or assi&nment operator- we'll run into trou#le! 8or e)ampleconsider the followin& code=
45tring one; 45tring t'o = one;
7ecause we haven't provided a copy constructor- C++ will initialize t'o.the5tring to one.the5tring! Since the5tring is a *har 2- instead of &ettin& a deep copy of the strin&- we'll end up with two pointers to the same C strin&! Thus chan&es to one will show up in t'o and vice1versa! This is dan&erous- especially when the destructors for #oth one and t'o try to deallocate the memory for the5tring! 'n situations like these- you'll need to override C++'s default #ehavior #y providin& your own copy constructors and assi&nment operators! There are a few circumstances where C++ does not automatically provide default copy constructors and assi&n1 ment operators! 'f your class contains a reference or *onst varia#le as a data mem#er- your class will not auto1 matically &et a copy constructor and assi&nment operator! Similarly- if your class has a data mem#er that doesn't have a copy constructor or assi&nment operator Kfor e)ample- an ifstreamL- your class won't #e copya#le! There is one other case involvin& inheritance where C++ won't automatically create the copy functions for youand in the chapter on inheritance we'll see how to e)ploit this to disa#le copyin&! The 1ule of Three There's a well1esta#lished C++ principle called the 4rule of three6 that identifies most spots where you'll need to write your own copy constructor and assi&nment operator! 'f this were a math te)t#ook- you'd pro#a#ly see the rule of three written out like this= Theorem (The Rule of Three)5 'f a class has any of the followin& three mem#er functions= 2estructor Copy Constructor ,ssi&nment Hperator Then that class should have all three of those functions! Corollary5 'f a class has a destructor- it should also have a copy constructor and assi&nment operator! The rule of three holds #ecause in almost all situations where you have any of the a#ove functions- C++'s default #ehavior won't correctly mana&e your o#*ects! 'n the a#ove e)ample with 45tring- this is the case #ecause copyin& the *har 2 pointer doesn't actually duplicate the C strin&! Similarly- if you have a class holdin& an open file handle- makin& a shallow copy of the o#*ect mi&ht cause crashes further down the line as the destructor of one class closed the file handle- corruptin& the internal state of all 4copies6 of that o#*ect! 7oth C++ li#raries and fellow C++ coders will e)pect that- #arrin& special circumstances- all o#*ects will cor1 rectly implement the three a#ove functions- either #y fallin& #ack on C++'s default versions or #y e)plicitly providin& correct implementations! Conse5uently- you must keep the rule of three in mind when desi&nin& classes or you will end up with insidious or seemin&ly untracea#le #u&s as your classes start to destructively in1 terfere with each other! /riting Co&y Constructors 8or the rest of this chapter- we'll discuss copy constructors and assi&nment operators throu&h a case study of the 9ebugDe*tor class! 9ebugDe*tor is a modified version of the CS1067IJ De*tor whose constructor and de1 structor lo& information to *out whenever an instance of 9ebugDe*tor is created or destroyed! That way- if you're writin& a pro&ram that you think mi&ht #e leakin& memory- you can check the pro&ram output to make sure that all your 9ebugDe*tors are cleaned up properly!
1 ;66 1
'nternally- 9ebugDe*tor is implemented as a dynamically1allocated array of elements! Two data mem#ers- al, lo*ated6ength and logi*al6ength- track the allocated size of the array and the num#er of elements stored in it- respectively! 9ebugDe*tor also has a class constant OA5EB5(]E that represents the default size of the al1 located array! The 9ebugDe*tor constructor is defined as
tem$late t%$ename ;) 9ebugDe*tor ;)339ebugDe*tor() 3 array!ne6 T9(US2&SI]2:$B allocated1ength!(US2&SI]2$B logical1ength!-$ { "2 6og the *reation using some fun*tions from *time) 2" timeBt *urrent;ime; time(?*urrent;ime); "" &ill in the *urrent time. *out .9ebugDe*tor *reated3 . *time(?*urrent;ime) endl; #
"ote that we initialize arra% to point to a dynamically1allocated array of OA5EB5(]E elements in the initializer list! 0e also use the time and *time functions from *time) to record the time at which the 9ebugDe*tor is createdR consult a reference for more information a#out these functions! Similarly- the 9ebugDe*tor destructor is
tem$late t%$ename ;) 9ebugDe*tor ;)33^9ebugDe*tor() { delete 9: array; array , /U11; logical1ength , allocated1ength , -; timeBt *urrent;ime; time(?*urrent;ime); *out .9estru*tor invo+ed3 . #
*time(?*urrent;ime)
endl;
"ow- let's write the copy constructor! 0e know that we need to match the prototype &iven in the class definitionso we'll write that part first=
template .typename T> Debug5ector.T>::Debug5ector!const Debug5ectorS other$ { "2 ... 2" #
1 ;6@ 1
'nside the copy constructor- we need to do two thin&s! 8irst- we must initialize the o#*ect so that we're holdin& a deep copy of the other 9ebugDe*tor! Second- we should lo& creation information since we're instantiatin& a new 9ebugDe*tor! et's first initialize the o#*ect- then handle the lo&&in& later! ,s with a re&ular constructor- with a copy constructor we should try to initialize as many instance varia#les as possi#le in the initializer list- #oth for reada#ility and speed! 'n the case of 9ebugDe*tor- while we can't com1 pletely set up the data mem#ers in the initializer list- we can still copy over the values of logi*al6ength and allo*ated6ength! 8or this chapter- however- we'll initialize these varia#les inside the #ody of the constructorsince #oth logi*al6ength and allo*ated6ength are primitives and don't &et a performance #oost from the initializer list! 0e thus have the followin& partial implementation of the copy constructor=
tem$late t%$ename ;) 9ebugDe*tor ;)339ebugDe*tor(*onst 9ebugDe*tor? other) { logical1ength , other.logical1ength; allocated1ength , other.allocated1ength; # "2 ... 2"
"ote that this implementation of the copy constructor sets logi*al6ength to other.logi*al6ength and allo*ated6ength to other.allo*ated6ength- even thou&h other.logi*al6ength and other.al, lo*ated6ength e)plicitly reference private data mem#ers of the other o#*ect! This is le&al #ecause other is an o#*ect of type 9ebugDe*tor and the copy constructor is a mem#er function of 9ebugDe*tor! , class can access #oth its private fields and private fields of other o#*ects of the same type! This is called sibling access and is true of any mem#er function- not *ust the copy constructor! 'f the copy constructor were not a mem#er of 9ebugDe*tor or if other were not a 9ebugDe*tor- this code would not #e le&al! "ow- we'll make a deep copy of the other 9ebugDe*tor's elements #y allocatin& a new array that's the same size as other's and then copyin& the elements over! The code looks somethin& like this=
tem$late t%$ename ;) 9ebugDe*tor ;)339ebugDe*tor(*onst 9ebugDe*tor? other) { logi*al6ength = other.logi*al6ength; allo*ated6ength = other.allo*ated6ength; array , ne6 T9allocated1ength:; or!int i , -; i . logical1ength; 33i$ array9i: , other.array9i:; "2 ... 2" #
'nterestin&ly- since 9ebugDe*tor is a template- it's unclear what the line arra%[i] = other.arra%[i] will actually do! 'f we're storin& primitive types- then the line will simply copy the values over- #ut if we're storin& o#*ects- the line invokes the class's assi&nment operator! "otice that in #oth cases the o#*ect will #e correctly copied over! This is one of drivin& forces #ehind definin& copy constructors and assi&nment operators- since template code can assume that e)pressions like ob>e*t8 = ob>e*tH will #e meanin&ful! ,n alternative means for copyin& data over from the other o#*ect uses the ST *o$% al&orithm! Fecall that *o$% takes three parameters ( two delineatin& an input ran&e of iterators and one denotin& the #e&innin& of an output ran&e ( then copies the specified iterator ran&e to the destination! ,lthou&h desi&ned to work on iteratorsit is possi#le to apply ST al&orithms directly to ran&es defined #y raw C++ pointers! Thus we could rewrite the copy constructor as follows=
1 ;6E 1
tem$late t%$ename ;) 9ebugDe*tor ;)339ebugDe*tor(*onst 9ebugDe*tor? other) { logi*al6ength = other.logi*al6ength; allo*ated6ength = other.allo*ated6ength; arra% = ne' ;[allo*ated6ength]; copy!other.arrayB other.array 3 other.logical1engthB array$; # "2 ... 2"
/ere- the ran&e spanned #y other.arra% and other.arra% ! other.logi*al6ength is the contents of the other 9ebugDe*tor- and arra% is the #e&innin& of the newly1allocated data we've reserved for this 9e, bugDe*tor! ' personally find this synta) prefera#le to the e)plicit for loop- since it increases reada#ility! 8inally- we need to lo& the creation of the new 9ebugDe*tor! 'f you'll notice- the code for lo&&in& the 9e, bugDe*tor's creation is already written for us in the default constructor! Unfortunately- unlike other o#*ect1ori1 ented lan&ua&es- in C++- a class with multiple constructors can't invoke one constructor from the #ody of anoth1 er! To avoid code duplication- we'll therefore move the lo&&in& code into a separate private mem#er function called log4reation that looks like this=
template .typename T> )oid Debug5ector.T>::log;reation!$ * time&t currentTime; time!ScurrentTime$; TT Fill in the current time. cout .. "Debug5ector created: " .. ctime!ScurrentTime$ .. endl; 4
0e then rewrite the default constructor for 9ebugDe*tor to call log4reation- as shown #elow=
tem$late t%$ename ;) 9ebugDe*tor ;)339ebugDe*tor() 3 arra%(ne' ;[OA5EB5(]E]), allo*ated6ength(OA5EB5(]E), logi*al6ength(0) { log;reation!$; #
,nd finally- we insert a call to log4reation into the copy constructor- yieldin& the final version=
tem$late t%$ename ;) 9ebugDe*tor ;)339ebugDe*tor(*onst 9ebugDe*tor? other) { logi*al6ength = other.logi*al6ength; allo*ated6ength = other.allo*ated6ength; arra% = ne' ;[allo*ated6ength]; *o$%(other.arra%, other.arra% ! other.logi*al6ength, arra%); # log;reation!$;
/riting Assignment 2&erators 0e've now successfully written a copy constructor for our 9ebugDe*tor class! Unfortunately- writin& an as1 si&nment operator is si&nificantly more involved than writin& a copy constructor! C++ is desi&ned to &ive you ma)imum fle)i#ility when desi&nin& an assi&nment operator- and thus won't alert you if you've written a syn1 tactically le&al assi&nment operator that is completely incorrect! 8or e)ample- consider this le&al #ut incorrect assi&nment operator for an o#*ect of type :%4lass=
1 ;6< 1
endl;
'nstead of makin& t'o a deep copy of one- instead we'll &et a messa&e printed to the screen and t'o will remain unchan&ed! This is one of the dan&ers of a poorly1written assi&nment operator ( code that looks like it does one thin& can instead do somethin& totally different! This section discusses how to correctly implement an assi&n1 ment operator #y startin& with invalid code and pro&ressin& towards a correct- final version! et's start off with a simple #ut incorrect version of the assi&nment operator for 9ebugDe*tor=
"2 :an% ma>or mista+es here. Do not use this code as a re erence% 2" tem$late t%$ename ;) void 9ebugDe*tor ;)33o$erator =(*onst 9ebugDe*tor? other) { logical1ength , other.logical1ength; allocated1ength , other.allocated1ength; array , ne6 T9allocated1ength:; copy!other.arrayB other.array 3 other.logical1engthB array$;
This code is #ased off the copy constructor- which we used to initialize the o#*ect as a copy of an e)istin& o#*ect! Unfortunately- this code contains a su#stantial num#er of mistakes that we'll need to correct #efore we end up with the final version of the function! Cerhaps the most serious error here is the line arra% = ne' ;[allo*, ated6ength]! 0hen the assi&nment operator is invoked- this 9ebugDe*tor is already holdin& on to its own array of elements! /owever- this line orphans the old array and leaks memory! To fi) this- #efore we make this o#*ect a copy of the one specified #y the parameter- we'll take care of the necessary deallocations! 'f you'll no1 tice- we've already written the necessary cleanup code in the 9ebugDe*tor destructor! Father than rewritin& this code- we'll decompose out the &eneric cleanup code into a *lear function- as shown here=
template .typename T> )oid Debug5ector.T>::clear!$ * delete 9: array; array , /U11; logical1ength , allocated1ength , -; 4
*time(?*urrent;ime)
endl;
,nd we can insert this call to *lear into our assi&nment operator as follows=
1 ;@0 1
"2 5till *ontains errors. 2" tem$late t%$ename ;) void 9ebugDe*tor ;)33o$erator =(*onst 9ebugDe*tor? other) { clear!$; logi*al6ength = other.logi*al6ength; allo*ated6ength = other.allo*ated6ength; arra% = ne' ;[allo*ated6ength]; *o$%(other.arra%, other.arra% ! other.logi*al6ength, arra%); #
,lon& the same lines- you mi&ht have noticed that all of the code after the call to *lear is e)actly the same code we wrote inside the #ody of the copy constructor! This isn't a coincidence ( in fact- in most cases you'll have a &ood deal of overlap #etween the assi&nment operator and copy constructor! Since we can't invoke our own copy constructor directly Kor any constructor- for that matterL- instead we'll decompose the copyin& code into a mem#er function called *o$%=ther as follows=
template .typename T> )oid Debug5ector.T>::copy<ther!const Debug5ectorS other$ * logical1ength , other.logical1ength; allocated1ength , other.allocated1ength; array , ne6 T9allocated1ength:; copy!other.arrayB other.array 3 other.logical1engthB array$;
This simplifies the copy constructor and assi&nment operator and hi&hli&hts the &eneral pattern of what the two functions should do! 0ith a copy constructor- you'll copy the contents of the other o#*ect- then perform any re1 mainin& initialization! 0ith an assi&nment operator- you'll clear out the receiver- then copy over the data from another o#*ect! /owever- we're still not done yet! There are two more issues we need to fi) with our current implementation of the assi&nment operator! The first one has to do with sel"-assignment! Consider- for e)ample- the followin& code=
:%4lass one; one , one;
1 ;@1 1
0hile this code mi&ht seem a #it silly- cases like this come up fre5uently when accessin& elements indirectly throu&h pointers or references! Unfortunately- with our current 9ebugDe*tor assi&nment operator- this code will lead to unusual runtime #ehavior! To see why- let's trace out the state of our o#*ect when its assi&nment op1 erator is invoked on itself! ,t the start of the assi&nment operator- we call *lear to clean out the o#*ect for the copy! 2urin& this call to *lear- we deallocate the memory associated with the o#*ect and set #oth logi*al6ength and allo*, ated6ength to zero! Thus our current o#*ect looks somethin& like this=
int logi*al6ength int allo*ated6ength ;2 arra% /U11
"ormally this isn't a pro#lem- #ut remem#er that we're self1assi&nin&- which means that the o#*ect referenced #y other is the same o#*ect referenced #y the this pointer! Conse5uently- since we erased all the contents of the current o#*ect- we also erased the contents of the other o#*ect! 0hen we &et to the code in *o$%=ther- we'll end up duplicatin& an empty o#*ect- erasin& the contents of the receiver o#*ect! The result is that our self1assi&n1 ment is effectively a &lorified call to *lear! 0hen writin& assi&nment operators- you must ensure that your code correctly handles self1assi&nment! 0hile there are many ways we can do this- perhaps the simplest is to simply check to make sure that the o#*ect to copy isn't the same o#*ect pointed at #y the this pointer! The code for this lo&ic looks like this=
"2 Eot Vuite $erfe*t %et Q thereAs one more error 2" tem$late t%$ename ;) void 9ebugDe*tor ;)33o$erator =(*onst 9ebugDe*tor? other) { i !this %, Sother$ { *lear(); *o$%=ther(other); # #
"ote that we check if(this 0= ?other)! That is- we compare the addresses of the current o#*ect and the parameter! This will determine whether or not the o#*ect we're copyin& is e)actly the same o#*ect as the one we're workin& with! "ote that we did not write if(2this 0= other)- since this would do a semantic com1 parison #etween the o#*ects- somethin& we'll cover in a later chapter when discussin& operator overloadin&! Hne last note is that code like this isn't re5uired in the copy constructor- since we can't pass an o#*ect as a parameter to its own copy constructor! There's one final #u& we need to sort out- and it has to do with how we're le&ally allowed to use the = operator! Consider- for e)ample- the followin& code=
:%4lass one, t'o, three; three , t6o , one;
1 ;@; 1
This code is e5uivalent to three = (t'o = one)! Since our current assi&nment operator does not return a value- (t'o = one) does not have a value- so the a#ove statement is meanin&less and the code will not com1 pile! 0e thus need to chan&e our assi&nment operator so that performin& an assi&nment like t'o = one yields a value that can then #e assi&ned to other values! 'n particular- we should return a non1 *onst reference to the receiver o#*ectR we'll discuss why this is in the chapter on operator overloadin&! The final version of our assi&n1 ment operator is thus
"2 ;he 4=CCE4; version. 2" tem$late t%$ename ;) Debug5ector.T>S 9ebugDe*tor ;)33o$erator =(*onst 9ebugDe*tor? other) { if(this 0= ?other) { *lear(); *o$%=ther(other); # return Rthis; #
2ne :eneral -attern ,lthou&h each class is different- in many cases the default constructor- copy constructor- assi&nment operatorand destructor will share a &eneral pattern! /ere is one possi#le skeleton you can fill in to &et your copy con1 structor and assi&nment operator workin&!
:%4lass33:%4lass() 3 "2 &ill in initialiUer list. 2" { additional(nit(); # :%4lass33:%4lass(*onst :%4lass? other) { *o$%=ther(other); additional(nit(); # :%4lass? :%4lass33o$erator =(*onst :%4lass? other) { if(this 0= ?other) { *lear(); "" Eote3 1hen 'e *over inheritan*e, thereAs one more ste$ here. *o$%=ther(other); # return 2this; # :%4lass33^:%4lass() { *lear(); additional4losedo'n(); #
Cha#ter 1$% Co#y Constructors and Assignment 5#erators Semantic $Iui7alence and copy<ther Strategies Consider the followin& code snippet=
9ebugDe*tor int) one; 9ebugDe*tor int) t'o = one;
1 ;@A 1
/ere- we know that t'o is a copy of one- so the two o#*ects should #ehave identically to one another! 8or e)1 ample- if we access an element of one- we should &et the same value as if we had accessed the correspondin& element of t'o and vice1versa! /owever- while one and t'o are indistin&uisha#le from each other in terms of functionality- their memory representations are not identical #ecause one and t'o point to two different dynam1 ically1allocated arrays! This raises the distinction #etween semantic eAui!alence and bitwise eAui!alence! Two o#*ects are said to #e #itwise e5uivalent if they have identical representations in memory! 8or e)ample- any two ints with the value 1A@ are #itwise e5uivalent- and if we define a $oint; struct as a pair of ints- any two $oint;s holdin& the same values will #e #itwise e5uivalent! Two o#*ects are semantically eAui!alent if- like one and t'o- any operations performed on the o#*ects will yield identical results! 0hen writin& a copy con1 structor and assi&nment operator- you attempt to convert an o#*ect into a semantically e5uivalent copy of another o#*ect! Conse5uently- you are free to pick any copyin& strate&y that creates a semantically e5uivalent copy of the source o#*ect! 'n the precedin& section- we outlined one possi#le implementation strate&y for a copy constructor and assi&n1 ment operator that uses a shared function called *o$%=ther! 0hile in the case of the 9ebugDe*tor it was rel1 atively easy to come up with a workin& *o$%=ther implementation- when workin& with more complicated o#1 *ects- it can #e difficult to devise a workin& *o$%=ther! 8or e)ample- consider the followin& class- which rep1 resents a mathematical set implemented as a linked list=
tem$late t%$ename ;) *lass 6ist5et { $ubli*3 6ist5et(); 6ist5et(*onst 6ist5et? other); 6ist5et? o$erator =(*onst 6ist5et? other); ^6ist5et(); )oid insert!const TS toUdd$; bool contains!const TS toFind$ const; $rivate3 stru*t *ell; { ; data; *ell;2 ne7t; #; *ell;2 list; )oid copy<ther!const 1istSetS other$; void *lear();
#;
This 6ist5et class e)ports two functions- insert and *ontains- that insert an element into the list and de1 termine whether the list contains an element- respectively! This class represents a mathematical set- an unordered collection of elements- so the underlyin& linked list need not #e in any particular order! 8or e)ample- the lists {0, 8, H, F, J# and {J, F, H, 8, 0# are semantically e5uivalent #ecause checkin& whether a num1 #er is an element of the first list yields the same result as checkin& whether the num#er is in the second! 'n factany two lists containin& the same elements are semantically e5uivalent to one another! This means that there are multiple ways in which we could implement *o$%=ther! Consider these two=
1 ;@B 1
"2 5ersion 7: 9u$li*ate the list as it e7ists in the original 6ist5et. 2" tem$late t%$ename ;) void 6ist5et ;)33*o$%=ther(*onst 6ist5et? other) { "2 Xee$ tra*+ of 'hat the *urrent lin+ed list *ell is. 2" *ell;22 *urrent = ?list; "2 (terate over the sour*e list. 2" for(*ell;2 sour*e = other.list; sour*e 0= E@66; sour*e = sour*e,)ne7t) { "2 9u$li*ate the *ell. 2" 2*urrent = ne' *ell;; (2*urrent),)data = sour*e,)data; (2*urrent),)ne7t = E@66; "2 Advan*e to ne7t element. 2" *urrent = ?((2*urrent),)ne7t); # #
"2 5ersion 8: 9u$li*ate list in reverse order of original 6ist5et 2" tem$late t%$ename ;) void 6ist5et ;)33*o$%=ther(*onst 6ist5et? other) { for(*ell;2 sour*e = other.list; sour*e 0= E@66; sour*e = sour*e,)ne7t) { *ell;2 ne'Eode = ne' *ell;; ne'Eode,)data = sour*e,)data; ne'Eode,)ne7t = list; list = ne'Eode; # #
,s you can see- the second version of this function is much- much cleaner than the first! There are no address1of operators floatin& around- so everythin& is e)pressed in terms of simpler pointer operations! 7ut while the second version is cleaner than the first- it duplicates the list in reverse order! This may initially seem pro#lematic #ut is actually perfectly safe! ,s the ori&inal o#*ect and the duplicate o#*ect contain the same elements in some order- they will #e semantically e5uivalent- and from the class interface we would #e una#le to distin&uish the ori&inal o#*ect and its copy! There is one implementation of *o$%=ther that is considera#ly more ele&ant than either of the two versions lis1 ted a#ove=
"2 5ersion D: 9u$li*ate list using the insert fun*tion 2" tem$late t%$ename ;) void 6ist5et ;)33*o$%=ther(*onst 6ist5et? other) { for(*ell;2 sour*e = other.list; sour*e 0= E@66; sour*e = sour*e,)ne7t) insert!source->data$; #
"otice that this implementation uses the 6ist5et's pu#lic interface to insert the elements from the source 6ist5et into the receiver o#*ect! This version of *o$%=ther is un5uestiona#ly the cleanest! 'f you'll notice- it doesn't matter e)actly how insert adds elements into the list Kindeed- insert could insert the elements at ran1 dom positionsL- #ut we're &uaranteed that at the end of the *o$%=ther call- the receiver o#*ect will #e semantic1 ally e5uivalent to the parameter!
Cha#ter 1$% Co#y Constructors and Assignment 5#erators Con7ersion Assignment 2&erators
1 ;@? 1
0hen workin& with copy constructors- we needed to define an additional function- the assi&nment operator- to handle all the cases in which an o#*ect can #e copied or assi&ned! /owever- in the chapter on conversion con1 structors- we provided a conversion constructor without a matchin& 4conversion assi&nment operator!6 't turns out that this is not a pro#lem #ecause of how the assi&nment operator is invoked! Suppose that we have a 45tring class that has a defined copy constructor- assi&nment operator- and conversion constructor that con1 verts raw C++ *har 2 pointers into 45tring o#*ects! "ow- suppose we write the followin& code=
45tring m%45tring; m%45tring = .;his is a 4 string0.;
/ere- in the second line- we assi&n an e)istin& 45tring a new value e5ual to a raw C strin&! 2espite the fact that we haven't defined a special assi&nment operator to handle this case- the a#ove is perfectly le&al code! 0hen we write the line
m%45tring = .;his is a 4 string0.;
This synta) may look entirely forei&n- #ut is simply a direct call to the assi&nment operator! Fecall that the as1 si&nment operator is a function named o$erator =- so this code passes the C strin& .;his is a 4 string0. as a parameter to o$erator =! 7ecause o$erator = accepts a 45tring o#*ect rather than a raw C strin&- C++ will invoke the 45tring conversion constructor to initialize the parameter to o$erator =! Thus this code is e5uivalent to
m%45tring.o$erator =!;String!"This is a ; string%"$$;
'n other words- the conversion constructor converts the raw C strin& into a 45tring o#*ect- then the assi&nment operator sets the receiver o#*ect e5ual to this temporary 45tring! 'n &eneral- you need not provide a 4conversion assi&nment operator6 to pair with a conversion constructor! ,s lon& as you've provided well1defined copy #ehavior- C++ will link the conversion constructor and assi&nment operator toðer to perform the assi&nment! 9isabling Co&ying 'n CS1067IJ we provide you the 9(5A66=1B4=<R(EG macro- which causes a compile error if you try to assi&n or copy o#*ects of the specified type! 9(5A66=1B4=<R(EG- however- is not a standard C++ feature! 0ithout usin& the CS1067IJ li#rary- how can we replicate the functionality> 0e can't prevent o#*ect copyin& #y simply not definin& a copy constructor and assi&nment operator! ,ll this will do is have C++ provide its own default version of these two functions- which is not at all what we want! To solve this pro#lem- instead we'll provide an assi&nment operator and copy constructor- #ut declare them private so that class clients can't access them! 8or e)ample=
1 ;@6 1
*lass 4annotOe4o$ied { $ubli*3 4annotOe4o$ied(); "2 =ther member fun*tions. 2" $rivate3 ;annot(e;opied!const ;annot(e;opiedS other$; ;annot(e;opiedS operator , !const ;annot(e;opiedS other$; #;
0e'll &et a compile1time error on the second line #ecause we're tryin& to invoke the copy constructor- which has #een declared private! 0e'll &et similar #ehavior when tryin& to use the assi&nment operator! This trick is almost one hundred percent correct- #ut does have one ed&e case= what if we try to invoke the copy constructor or assi&nment operator inside a mem#er function of the class> The copy functions mi&ht #e private#ut that doesn't mean that they don't e)ist- and if we call them inside a mem#er function mi&ht accidentally cre1 ate a copy of an otherwise uncopya#le o#*ect! To prevent this from happenin&- we'll use a cute trick! ,lthou&h we'll #rototy#e the copy functions inside the private section of the class- we won't im#lement them! This means that if we accidentally do mana&e to call either function- we will &et a linker error #ecause the compiler can't find code for either function! This is admittedly a #it hackish- so in C++0)- the ne)t revision of C++- there will #e a way to e)plicitly indicate that a class is uncopya#le! 'n the meantime- thou&h- the a#ove approach is per1 haps your #est option! 0e'll see another way to do this later when we cover inheritance! -ractice -roblems The only way to learn copy constructors and assi&nment operators is to play around with them to &ain e)peri1 ence! /ere are some practice pro#lems and thou&ht 5uestions to &et you started= 1! Fealizin& that the copy constructor and assi&nment operator for most classes have several commonalit1 ies- you decide to implement a class's copy constructor usin& the class's assi&nment operator! 8or e)1 ample- you try implementin& the 9ebugDe*tor's copy constructor as
tem$late t%$ename ;) 9ebugDe*tor ;)339ebugDe*tor(*onst 9ebugDe*tor? other) { Rthis , other; #
KSince this is a pointer to the receiver o#*ect- 2this is the receiver o#*ect- so 2this = other means to assi&n the receiver o#*ect the value of the parameter otherL This idea- while well1intentioned- has a serious flaw that causes the copy constructor to almost always cause a crash! 0hy is this> <+int% &ere any o" the 0ebug1ector data members initiali:ed be"ore calling the assignment o#erator' &al* through the assignment o#erator and see what ha##ens i" the recei!er object(s data members ha!en(t been initiali:ed)= M ;! 't is ille&al to write a copy constructor that accepts its parameter #y value! 0hy is this> /owever- it's perfectly accepta#le to have an assi&nment operator that accepts its parameter #y value! 0hy is this le&1 al> 0hy the difference> M A! 'n the previous chapter- we saw the class 45tring used as a case study for conversion constructors! 45tring is simply a class that wraps a C strin&- storin& as data mem#ers the *har 2 pointer to the C strin&! 0rite a copy constructor and assi&nment operator for 45tring!
1 ;@@ 1
B! Suppose you're implementin& the 6e7i*on class and- for efficiency reasons- you decide to store the words in a sorted ve*tor of dynamically1allocated C strin&s Kve*tor *har 2)L! ,ssume that the constructor has #een provided to you and that it correctly initializes the ve*tor #y fillin& it with strin&s dynamically allocated from ne'[]! 0rite the copy constructor- assi&nment operator- and destructor for 6e7i*on! <+int% &hen the vector destructor in!o*es, it will not call delete 23 on all o" the internally stored char 4s, so you will ha!e to do this yoursel"= ?! 'f the a#ove 6e7i*on used a ve*tor string) instead of a ve*tor *har 2)- would you need any of the functions mentioned in the rule of three> 6! ,n alternative implementation of the assi&nment operator uses a techni5ue called co#y-and-swa#! The copy1and1swap approach is #roken down into two steps! 8irst- we write a mem#er function that accepts a reference to another instance of the class- then e)chan&es the data mem#ers of the receiver o#*ect and the parameter! 8or e)ample- when workin& with the 9ebugDe*tor- we mi&ht write a function called s'a$1ith as follows=
tem$late t%$ename Elem;%$e) void 9ebugDe*tor Elem;%$e)33s'a$1ith(9ebugDe*tor? other) { s6ap!arrayB other.array$; s6ap!logical1engthB other.logical1ength$; s6ap!allocated1engthB other.allocated1ength$; #
/ere- we use the ST s'a$ al&orithm to e)chan&e data mem#ers! "otice that we never actually make a deep1copy of any of the elements in the array ( we simply swap pointers with the other 9ebugDe*tor! 0e can then implement the assi&nment operator as follows=
tem$late t%$ename ;) 9ebugDe*tor ;)? 9ebugDe*tor ;)33o$erator= (*onst 9ebugDe*tor? other) { Debug5ector temp!other$; s6ap=ith!temp$; return Rthis; #
Trace throu&h this implementation of the assi&nment operator and e)plain how it sets the receiver o#*ect to #e a deep1copy of the parameter! 0hat function actually deep1copies the data> 0hat function is re1 sponsi#le for cleanin& up the old data mem#ers> @! 0hen writin& an assi&nment operator usin& the pattern covered earlier in the chapter- we had to e)pli1 citly check for self1assi&nment in the #ody of the assi&nment operator! D)plain why this is no lon&er ne1 cessary usin& the copy1and1swap approach- #ut why it still mi&ht #e a &ood idea to insert the self1assi&n1 ment check anyway!
1 ;@E 1
E! , singleton class is a class that can have at most one instance! Typically- a sin&leton class has its default constructor and destructor marked private so that clients cannot instantiate the class directly- and e)ports a static mem#er function called get(nstan*e() that returns a reference to the only instance of the class! That one instance is typically a private static data mem#er of the class! 8or e)ample=
*lass 5ingleton { $ubli*3 static SingletonS getInstance!$; $rivate3 Singleton!$; "" 4lients *annot *all this fun*tion be*ause itAs $rivate ^Singleton!$; "" ... or this one static Singleton instance; "" ... but the% *an be used here be*ause "" instan*e is $art of the *lass. #; 5ingleton 5ingleton33instan*e;
2oes it make sense for a sin&leton class to have a copy constructor or assi&nment operator> 'f so- imple1 ment them! 'f not- modify the 5ingleton interface so that they are disa#led! <! 3iven this chapter's description a#out how to disa#le copyin& in a class- implement a macro 9(5A66=1B4=<R(EG that accepts as a parameter the name of the current class such that if 9(5A66=1B4=<R(EG is placed into the private section of a class- that class is uncopya#le! "ote that it is le&al to create macros that span multiple lines #y endin& each line with the T character! 8or e)ample- the followin& is all one macro=
-define 4CEA;EB<C(E;EC(str) void <rint--str()T {T *out -str endl;T #
10! Consider the followin& alternative mechanism for disa#lin& copyin& in a class= instead of markin& those functions private- instead we implement those functions- #ut have them call abort Kthe function from *stdlib) that immediately terminates the pro&ramL after printin& out an error messa&e! 8or e)ample=
*lass <seudo@n*o$%able { $ubli*3 <seudo@n*o$%able(*onst <seudo@n*o$%able? other) { abort(); # <seudo@n*o$%able? o$erator= (*onst <seudo@n*o$%able? other) { abort(); return 2this; "" Eever rea*hed; su$$resses *om$iler 'arnings # #;
0hy is this approach a #ad idea> 11! Should you copy static data mem#ers in a copy constructor or assi&nment operator> 0hy or why not>
Hver the past few chapters we've introduced many important lan&ua&e concepts ( *onst- templates- initializer lists- stati*- conversion constructors- and copy functions to name a few! Dach of these chapters had their own set of rules and caveats- and it feel overwhelmin& tryin& to commit each to memory! These are skills that can only come with practice- and hopefully this e)tended e)ample can help e)ercise your newfound C++ knowled&e! The other e)tended e)amples in this course reader demonstrate how to solve pro#lems ele&antly and effectively in C++! This e)ample- however- is 5uite the reverse! 0e'll #e&in with a #roken solution to a pro#lem- then e)1 plore how we can transform it into a workin& solution #y applyin& the techni5ues of the past chapters! Suppose that we want to implement a class encapsulatin& a forward1linked list! Dach element is stored in a cell that contains a pointer to the ne)t cell in the list- and the final element in the list points to E@66! 7ecause the list is sin&ly1linked- the overhead for each item is as small as possi#le and we can rapidly traverse over the entire container! /owever- #ecause the list is sin&ly1linked- insertions and deletions at ar#itrary points are e)pensiveso we will only concern ourselves with addition and deletion at the front of the list! 'n particular- our sin&ly1 linked list will support the followin& operations= ,ddition and deletion of elements at the #e&innin& of the list! 'teration over the entire list! Size and empty 5ueries!
There are several other functions we mi&ht want to add to this list- #ut for simplicity we'll only concern ourselves with this set! 'f you'd like an implementation challen&e- feel free to desi&n and implement some other operations! 8or this e)tended e)ample- we will desi&n a class called &or'ard6ist that supports these a#ove operations! /owever- we will only concern ourselves with the inter"ace for the 8orward ist class rather than the im#lementation! This will let us e)ercise the techni5ues in the previous chapters more directly! This is not to say that im1 plementin& a forward1linked list class is unimportant- of course- and ' encoura&e you to do so on your own! Suppose that we need to implement this &or'ard6ist class for a software pro*ect we're workin& on and that another student in the &roup- who has a rudimentary understandin& of C++- volunteers to desi&n and implement the class! ,fter several hours- he returns with the followin& code=
1 ;E0 1
"2 A *lass re$resenting a for'ard,lin+ed list of strings. ;his *ode *ontains 2 several serious errors, so don5t use it as a reference6 2" class For6ard1ist { $ubli*3 "22 2 4onstru*tor3 &or'ard6ist(numElems, defaultDalue) 2 @sage3 &or'ard6ist m%6ist(80); "" 4ontains ten *o$ies of the em$t% string 2 @sage3 &or'ard6ist m%6ist(80, ./ello0.); "" 4ontains ten *o$ies of ./ello0. 2 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 2 4onstru*ts a ne' list of the s$e*ified siUe 'here ea*h element is 2 initialiUed to the s$e*ified value. (f no siUe is s$e*ified, defaults to 2 Uero. (f no default value is s$e*ified, the em$t% string is used. 2" For6ard1ist!int num2lems , -B string de ault5alue , ""$; "22 2 4onstru*tor3 &or'ard6ist(ve*tor string)33iterator begin, 2 ve*tor string)33iterator end); 2 @sage3 &or'ard6ist m%6ist(v.begin(), v.end()); 2 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 2 4onstru*ts a ne' list 'hose elements are eVual to those s$e*ified b% 2 the in$ut $air of ve*tor string)33iterators. 2" For6ard1ist!)ector.string>::iterator beginB )ector.string>::iterator end$; "22 2 9estru*tor3 ^&or'ard6ist(); 2 @sage3 delete m%6ist<tr; 2 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 2 4leans u$ an% memor% allo*ated b% this &or'ard6ist ob>e*t. 2" ^For6ard1ist!$; "22 2 ;%$e3 iterator 2 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 2 A for'ard iterator that *an read and 'rite values stored in the 2 &or'ard6ist. 2" typede something-implementation-specific iterator; "22 2 &un*tions begin() and end() 2 @sage3 for(&or'ard6ist33iterator itr = l.begin(); itr 0= l.end(); !!itr) 2 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 2 Ceturns iterators to the first element in the list and the first element 2 $ast the end of the list, res$e*tivel%. 2" iterator begin!$; iterator end!$;
1 ;E1 1
"22 2 &un*tions3 $ushBfront(val), $o$Bfront(), front() 2 @sage3 v.$ushBfront(.;his is a string0.); s = v.front(); v.$o$Bfront(); 2 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 2 $ushBfront $re$ends the s$e*ified element to the lin+ed list, 2 in*reasing its siUe b% one. $o$Bfront removes the first element of the 2 list 'ithout returning its value; use front() before *alling $o$Bfront() 2 if %ou 'ant to retrieve the value. 2 2 front() returns a referen*e to the first element in the list, so itAs 2 legal to 'rite either val = front() or front() = val. 2" )oid push& ront!string )alue$; )oid pop& ront!$; stringS ront!$; "22 2 &un*tions3 em$t%(), siUe() 2 @sage3 if(v.em$t%()) ... if(v.siUe() ) 80) ... 2 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 2 em$t%() returns 'hether the list is em$t% (i.e. begin() == end()). 2 siUe() returns the number of elements stored in the list. 2" bool empty!$; int siLe!$; $rivate3 "2 ... im$lementation details ... 2" #;
The class is certainly well1commented- #ut contains several errors in its interface! 7efore turnin& to the ne)t pa&e where we'll discuss e)actly what's wron&- take a few minutes to look over the interface! Gark all the errors you find! "ot all of the mistakes are misuses of lan&ua&e featuresR at least one of the functions defined here is fine as written #ut could #e dramatically improved with the help of additional lan&ua&e features! Feady> 3reatU et's take a look at what we can fi) a#out this class!
The chapter on *onst1correctness should have instilled in you the fact that classes should #e *onst1correct! /opefully you noticed that this class is in dire need of *onstifyin&! "one of the mem#er functions are marked *onst- and all the string function parameters are all accepted #y value instead of reference1to1*onst! et's see how we can fi) this! 8or simplicity- here's the entire class interface with the commentin& removed=
*lass &or'ard6ist { $ubli*3 &or'ard6ist(int numElems = 0, string defaultDalue = ..); &or'ard6ist(ve*tor string)33iterator begin, ve*tor string)33iterator end); ^&or'ard6ist(); t%$edef something-implementation-specific iterator; iterator begin(); iterator end(); void $ushBfront(string value); void $o$Bfront(); string? front(); bool em$t%(); int siUe();
#;
Hur first order of #usiness is to modify the class so that functions take their parameters #y reference1to1*onst instead of #y value! This yields the followin& updated class=
*lass &or'ard6ist { $ubli*3 &or'ard6ist(int numElems = 0, const stringS defaultDalue = ..); &or'ard6ist(ve*tor string)33iterator begin, ve*tor string)33iterator end); ^&or'ard6ist(); t%$edef something-implementation-specific iterator; iterator begin(); iterator end(); void $ushBfront(const stringS value); void $o$Bfront(); string? front(); bool em$t%(); int siUe(); #;
"otice that the parameters to the second constructor are still passed #y value instead of #y reference1to1 *onst! This is deli#erate! 7ecause iterators are almost always modified inside of functions that re5uire them- it is ac1 cepta#le to pass them #y value! 'terators tend to #e li&htwei&ht o#*ects- so the cost of the pass1#y1value is usu1 ally not a pro#lem! /owever- these iterator parameters do present another *onstness pro#lem! The parameters to this function are used to construct a new &or'ard6ist- so none of the elements in the ran&e they delimit should #e modified! To e)plicitly indicate that we won't modify elements in the ran&e- we'll accept the paramet1
1 ;EA 1
ers as ve*tor string)33*onstBiterators instead of re&ular ve*tor string)33iterators! This leads to the followin& interface=
*lass &or'ard6ist { $ubli*3 &or'ard6ist(int numElems = 0, *onst string? defaultDalue = ..); &or'ard6ist()ector.string>::const&iterator begin, )ector.string>::const&iterator end); ^&or'ard6ist(); t%$edef something-implementation-specific iterator; iterator begin(); iterator end(); void $ushBfront(*onst string? value); void $o$Bfront(); string? front(); bool em$t%(); int siUe(); #;
"e)t- let's mark non1mutatin& mem#er functions *onst! em$t% and siUe clearly should not modify the &or, 'ard6ist- so we'll mark them *onst- as shown here=
*lass &or'ard6ist { $ubli*3 &or'ard6ist(int numElems = 0, *onst string? defaultDalue = ..); &or'ard6ist(ve*tor string)33*onstBiterator begin, ve*tor string)33*onstBiterator end); ^&or'ard6ist(); t%$edef something-implementation-specific iterator; iterator begin(); iterator end(); void $ushBfront(*onst string? value); void $o$Bfront(); string? front(); bool em$t%() const; int siUe() const; #;
et's now consider $ushBfront- $o$Bfront- and front! $ushBfront and $o$Bfront are clearly mutatin& #ecause they chan&e the size of the container- #ut what a#out front> The description of front &iven in the comments says that front should return a reference to the first element in the container- meanin& that clients of &or'ard6ist can modify the first element in the list #y writin& code similar to this=
m%6ist.front() = .:odified0.;
0e therefore cannot mark front *onst- since this would su#vert *onstness! /owever- we should definitely consider a *onst overload for front that returns a *onst reference to the first element of the list! 'f we do notclients of a *onst &or'ard6ist could not read the front element without havin& to use iterators! ,s a &eneral
1 ;EB 1
rule- if you have a mem#er function that returns a reference to an o#*ect- you should pro#a#ly also provide a *onst overload for that function that returns a *onst reference! The updated &or'ard6ist looks like this=
*lass &or'ard6ist { $ubli*3 &or'ard6ist(int numElems = 0, *onst string? defaultDalue = ..); &or'ard6ist(ve*tor string)33*onstBiterator begin, ve*tor string)33*onstBiterator end); ^&or'ard6ist(); t%$edef something-implementation-specific iterator; iterator begin(); iterator end(); void $ushBfront(*onst string? value); void $o$Bfront(); string? front(); const stringS ront!$ const; bool em$t%() *onst; int siUe() *onst;
#;
,ll that's left now are the begin and end functions! ,s mentioned in the first chapter on *onst- these functions should #e overloaded so that we can &et *onstBiterators when the list is marked *onst! This will re5uire us to also provide a *onstBiterator type! The resultin& interface is shown here=
*lass &or'ard6ist { $ubli*3 &or'ard6ist(int numElems = 0, *onst string? defaultDalue = ..); &or'ard6ist(ve*tor string)33*onstBiterator begin, ve*tor string)33*onstBiterator end); ^&or'ard6ist(); t%$edef something-implementation-specific iterator; t%$edef something-implementation-specific const&iterator; iterator begin(); iterator end(); const&iterator begin!$ const; const&iterator end!$ const; void $ushBfront(*onst string? value); void $o$Bfront(); string? front(); *onst string? front() *onst; bool em$t%() *onst; int siUe() *onst; #;
,t this point our &or'ard6ist class is *onst1corrected and we can move on to consider other issues with the class desi&n!
Cha#ter 1/% E7tended E7am#le% CritiAuing Class esign -roblem 15 Constructors :one ,ad
1 ;E? 1
The &or'ard6ist class has two constructors- one of which contains a serious error and the other of which is not desi&ned particularly well! et's take a look at how these constructors are prototyped and what we can do to make them #etter! et's take a look at the first &or'ard6ist constructor! 't has the followin& prototype=
&or'ard6ist(int numElems = 0, *onst string? defaultDalue = ..);
Fecall that C++ interprets any constructor that can #e called with one ar&ument as a conversion constructor! This constructor has two parameters- #ut #ecause the second has a default value Kthe empty strin&L it's le&al to construct a &or'ard6ist #y writin&
&or'ard6ist m%6ist(8FG); "" 4onstru*ts m%6ist(8FG, ..);
Conse5uently- C++ treats this as a conversion constructor and it's now le&al to write
&or'ard6ist my1ist , 7DE;
This is not intended- and to prevent this pro#lem we need to indicate that this constructor is not an implicit con1 version constructor! This is a *o# for the e7$li*it keyword- resultin& in the followin& interface=
*lass &or'ard6ist { $ubli*3 e#plicit &or'ard6ist(int numElems = 0, *onst string? defaultDalue = ..); &or'ard6ist(ve*tor string)33*onstBiterator begin, ve*tor string)33*onstBiterator end); ^&or'ard6ist(); t%$edef something-implementation-specific iterator; t%$edef something-implementation-specific *onstBiterator; iterator begin(); iterator end(); *onstBiterator begin() *onst; *onstBiterator end() *onst; void $ushBfront(*onst string? value); void $o$Bfront(); string? front(); *onst string? front() *onst; bool em$t%() *onst; int siUe() *onst; #;
This constructor e)ists so that we can take an e)istin& list of strings and convert it into a &or'ard6ist! 'n particular- if we have a ve*tor string) that contains the strings we want- we can use its elements as initial values for our &or'ard6ist! 7ut why sin&le out ve*tor string)> 0hat if we wanted to construct a &or,
1 ;E6 1
'ard6ist from a deVue string) or a set string)> ,nd as alon& as we're usin& iterators- why not allow iterators from another &or'ard6ist or even a pair of istreamBiterator string)s> The idea of usin& iter1
ators is a no#le one- #ut the approach in this implementation is incorrect! To fi) this- we'll rewrite the constructor so that it accepts a pair of iterators of any type! ,s in the @nion&ind e)ample- we accomplish this #y rewritin& the constructor as a constructor tem#late parameterized over the par1 ticular type of iterators used! This is shown here=
*lass &or'ard6ist { $ubli*3 e7$li*it &or'ard6ist(int numElems = 0, *onst string? defaultDalue = ..); template .typename InputIterator> For6ard1ist!InputIterator beginB InputIterator end$; ^&or'ard6ist(); t%$edef something-implementation-specific iterator; t%$edef something-implementation-specific *onstBiterator; iterator begin(); iterator end(); *onstBiterator begin() *onst; *onstBiterator end() *onst; void $ushBfront(*onst string? value); void $o$Bfront(); string? front(); *onst string? front() *onst; bool em$t%() *onst; int siUe() *onst; #;
"otice that we've named the type of the template ar&ument (n$ut(terator to indicate that this function should take in an iterator that's at least as powerful as an (n$ut(terator! ,fter all- to construct a forward1linked listwe don't need to #e a#le to write to the iterator ran&e or *ump around randomly! -roblem 5 Co&y ,eha7ior The &or'ard6ist class declared a#ove doesn't have a declared copy constructor or assi&nment operator- so it relies on C++'s automatically1&enerated copy functions! The class encapsulates a forward1linked list- which usu1 ally re5uires e)plicit memory mana&ement! 7ecause of this- relyin& on the automatically1&enerated implementa1 tion of the copy functions mi&ht #e dan&erous! 7ut then a&ain- we're *ust &iven the inter"ace of &or'ard6istnot the im#lementation- so it's possi#le that the class is implemented so that the default copy #ehavior mi&ht #e correct! 's there a way that we can check if the interface has a pro#lem without lookin& at the implementation> ,#solutely- #ecause the class has a destructor! Femem#er that the Fule of Three states that a class with a de1 structor should also have a copy constructor and assi&nment operator- and the lone destructor in this class defini1 tion su&&ests that the class contains an error! 0e'll thus update the class to add the copy functions- as shown here=
1 ;E@ 1
#;
-roblem "5 27ers&eciali3ation The &or'ard6ist class defined a#ove is considera#ly #etter than the initial version! 't's *onst correct- sports a &eneric constructor to make a &or'ard6ist out of any ran&e of strings- and has support for deep copyin&! 7ut there's one more pro#lem with the class! ,s written- the class is hardcoded to only work with strings! 0hy is this the case> There's nothin& in this interface that re5uires the list to store strings- and we could use this e)act interface with string su#stituted for int to have a &or'ard6ist that works for ints! ,s a last step- we'll templatize the &or'ard6ist class over the type of element to store in it! This is shown here=
1 ;EE 1
template .typename 2lemType> *lass &or'ard6ist { $ubli*3 e7$li*it &or'ard6ist(int numElems = 0, *onst 2lemType? defaultDalue = TR see belo6 RT); tem$late t%$ename (n$ut(terator) &or'ard6ist((n$ut(terator begin, (n$ut(terator end); &or'ard6ist(*onst &or'ard6ist? other); &or'ard6ist? o$erator= (*onst &or'ard6ist? other); ^&or'ard6ist(); t%$edef something-implementation-specific iterator; t%$edef something-implementation-specific *onstBiterator; iterator begin(); iterator end(); *onstBiterator begin() *onst; *onstBiterator end() *onst; void $ushBfront(*onst 2lemType? value); void $o$Bfront(); 2lemType? front(); *onst 2lemType? front() *onst; bool em$t%() *onst; int siUe() *onst;
#;
0e're almost done and all that's left is to specify the default value in the first constructor! 'n the case of the string version of the &or'ard6ist we could hardcode that the default value should #e the empty strin&- #ut what a#out the more &eneral template case> That is- what should the default value #e for elements in this list> Hne ele&ant solution re5uires a new piece of synta) called the tem#orary object synta7! 'n C++- it is le&al to e)1 plicitly call a class's constructor to create a temporary instance of that class constructed with the specified ar&u1 ments! .ou have already seen this with istreamBiterator and ostreamBiterator! 8or e)ample- in the code *o$%(v.begin(), v.end(), ostream&iterator.int>!coutB "[n"$)- the third parameter is a temporary ostreamBiterator int) constructed with parameters *out and .Tn.! ostreamBiterator is a ty#e- not a "unction- so the synta) ostreamBiterator int)(*out, .Tn.) is a constructor call rather than a function call! 0e can also use this synta) to construct temporary primitive values! 8or e)ample- int() constructs a tempor1 ary inte&er- and double() constructs a temporary double! 'nterestin&ly- if you construct a temporary primitive type this way- the primitive will #e initialized to zero! That is- int() always produces an inte&er of value zerorather than an inte&er with a &ar#a&e value! The temporary o#*ect synta) is curious- #ut how is it relevant to our a#ove discussion> Simple ( we'll specify that the default value for the final parameter to the constructor is a temporary instance of an o#*ect of type Elem;%$e! 8or e)ample=
1 ;E< 1
#;
Crovided that the type #ein& stored in the &or'ard6ist has a zero1parameter constructor- this code will work correctly! Summary This e)tended e)ample focused e)clusively on &or'ard6ist as an e)ample- #ut the techni5ues we used were completely &eneral! Gake sure that you understand the sorts of thou&ht processes that went into transformin& &or'ard6ist from its initial state into its sleek final version! 'f you take the time to think throu&h class desi&nyou will end up with code that is more ro#ust and less error1prone! ,n interestin& o#servation is that we didn't need to look at the implementation of the &or'ard6ist class at any point durin& our analysis! This is no coincidence! The decisions we made here to remedy a #roken class can also #e used durin& class desi&n to construct a class interface #efore even decidin& on a particular implementa1 tion! 'n fact- this is how you should desi&n your classes! Think a#out what operations you will need to supportand whether or not they should #e *onst! 'f your class can #e parameterized over an ar#itrary type- make it a template! Hnly after you've thou&ht this throu&h should you actually sit down to implement the necessary fea1 tures!
Cha&ter
5 2&erator 27erloading
_________________________________________________________________________________________________________
/ere- we create a ve*tor string) of a certain size- then iterate over it concatenatin& 4"ow lon&erU6 to each of the strin&s! Code like this is u#i5uitous in C++- and initially does not appear all that e)citin&! /owever- let's take a closer look at how this code is structured! 8irst- let's look at e)actly what operations we're performin& on the iterator=
ve*tor string) m%De*tor(E@:B5;C(EG5); for(ve*tor string)33iterator itr = m%De*tor.begin(); itr %, my5ector.end!$; 33itr) Ritr != .Eo' longer0.;
'n this simple piece of code- we're comparin& the iterator a&ainst m%De*tor.end() usin& the 0= operator- in1 crementin& the iterator with the !! operator- and dereferencin& the iterator with the 2 operator! ,t a hi&h levelthis doesn't seem all that out of the ordinary since ST iterators are desi&ned to look like re&ular pointers and these operators are all well1defined on pointers! 7ut the key thin& to notice is that ST iterators aren(t pointersthey're objects- and 0=- 2- and !! aren't normally defined on o#*ects! 0e can't write code like !!m%De*tor or 2m%:a$ = 8FG- so why can these operations #e applied to ve*tor string)33iterator> Similarly- notice how we're concatenatin& the strin& 4"ow lon&erU6 onto the end of the strin&=
ve*tor string) m%De*tor(E@:B5;C(EG5); for(ve*tor string)33iterator itr = m%De*tor.begin(); itr 0= m%De*tor.end(); !!itr) 2itr 3, .Eo' longer0.;
2espite the fact that string is an o#*ect- somehow C++ 4knows6 what it means to apply != to strings! ,ll of the a#ove e)amples are instances of o#erator o!erloading- the a#ility to specify how operators normally applica#le to primitive types can interact with custom classes! Hperator overloadin& is u#i5uitous in profession1 al C++ code and- used correctly- can make your pro&rams more concise- more reada#le- and more template1 friendly! There are two overarchin& purposes of operator overloadin&! 8irst- operator overloadin& ena#les your custom classes to act like primitive types! That is- if you have a class like ve*tor that mimics a standard C++ arrayyou can allow clients to use array notation to access individual elements! Similarly- when desi&nin& a class en1 capsulatin& a mathematical entity Kfor e)ample- a comple) num#erL- you can let clients apply mathematical op1 erators like !- ,- and 2 to your type as thou&h it were #uilt into the lan&ua&e! Second- operator overloadin& en1 a#les your code to interact correctly with template and li#rary code! 8or e)ample- you can overload the oper1 ator to make a class compati#le with the streams li#rary- or the operator to interface with ST containers! This chapter discusses &eneral topics in operator overloadin&- demonstratin& how to overload some of the more common operators! 't also includes tricks and pitfalls to #e aware of when overloadin& certain operators! /owever- we will not focus e)tensively on how to im#lement most of the operators outlined in this chapter- and will instead defer this discussion for the ne)t three chapters- each of which showcases one particular aspect of operator overloadin&!
' would #e remiss to discuss operator overloadin& without first prefacin& it with a warnin&= operator overloadin& is a dou#le1ed&ed sword! 0hen used correctly- operator overloadin& can lead to intuitive- template1friendly code that ele&antly performs comple) operations #ehind the scenes! /owever- incorrectly overloaded operators can lead to incredi#ly su#tle #u&s ( *ust think of the pro#lems associated with the assi&nment operator! "ow that we're delvin& deeper into operator overloadin&- you'll encounter more potential for these sorts of mistakes! There is a pearl of desi&n wisdom that is particularly applica#le to operator overloadin&= Theorem (The Principle of Least Astonishment)% , function's name should communicate its #ehavior and should #e consistent with other namin& conventions and idioms! The principle of least astonishment should #e fairly o#vious ( you should desi&n functions so that clients can un1 derstand what those functions do simply #y lookin& at the functions' namesR that is- clients of your code should not #e 4astonished6 that a function with one name does somethin& entirely different! 8or e)ample- a function named 9o5omething violates the principle of least astonishment #ecause it doesn't communicate what it doesand a function called 4om$ute<rimes that reads a &rocery list from a file violates the principle #ecause the name of the function is completely different from the operation it performs! /owever- other violations of the principle of least astonishment are not as #latant! 8or e)ample- a custom container class whose em$t% mem#er function erases the contents of the container violates the principle of least astonishment #ecause C++ pro&ram1 mers e)pect em$t% to mean 4is the container empty>6 rather than 4empty the container!6 Similarly- a class with a copy constructor #ut no assi&nment operator violates the principle of least astonishment #ecause pro&rammers e)pect those functions to come in pairs! 0hen workin& with operator overloadin&- it is crucial to adhere to the principle of least astonishment! C++ lets you redefine almost all of the #uilt1in operators however you choose- meanin& that it's possi#le to create code that does somethin& completely different from what C++ pro&rammers mi&ht e)pect! 8or e)ample- suppose that you are workin& on a &roup pro*ect and that one of your teammates writes a class 4ustomDe*tor that acts like the ST ve*tor #ut which performs some additional operations #ehind the scenes! .our pro&ram contains a small #u&- so you look over your teammate's code and find the followin& code at one point=
4ustomDe*tor one = "2 ... 2", t'o = "2 ... 2"; one X, t6o;
0hat does the line one Z= t'o do> Syntactically- this says 4take the remainder when dividin& one #y t'othen store the result #ack in one!6 7ut this makes no sense ( how can you divide one 4ustomDe*tor #y anoth1 er> .ou ask your teammate a#out this- and he informs you that the Z= operator means 4remove all elements from one that are also in t'o!6 This is an e&re&ious violation of the principle of least astonishment! The code neither communicates what it does nor adheres to e)istin& convention- since the semantics of the Z= operator are meanin&less when applied to linear data structures! This is not to say- of course- that havin& the a#ility to re1 move all elements from one 4ustomDe*tor that are contained in another is a #ad idea ( in fact- it can #e 5uite useful ( #ut this functionality should #e provided #y a properly1named mem#er function rather than a cryptic1 ally1overloaded operator! 8or e)ample- consider the followin& code=
4ustomDe*tor one = "2 ... 2", t'o = "2 ... 2"; one.remo)eUllIn!t6o$;
This code accomplishes the same task as the a#ove code- #ut does so #y e)plicitly indicatin& what operation is #ein& performed! This code is much less likely to confuse readers and is far more descriptive than #efore! ,s another e)ample- suppose that your teammate also implements a class called 4ustom5tring that acts like the standard string type- #ut provides additional functionality! .ou write the followin& code=
1 ;<A 1
'ntuitively- this should create two strin&s- then concatenate the second onto the end of the first! "e)t- the code prints out one followed #y an e)clamation point- yieldin& 4/ello 0orldU6 Unfortunately- when you compile this code- you &et an unusual error messa&e ( for some reason the code one != t'o compiles correctly- #ut the compiler re*ects the code one ! .0.! 'n other words- your teammate has made it le&al to concatenate two strin&s usin& !=- #ut not #y usin& !! ,&ain- think #ack to the principle of least astonishment! Cro&rammers ta1 citly e)pect that o#*ects that can #e added with ! can #e added with != and vice1versa- and providin& only half of this functionality is likely to confuse code clients! The moral of this story is simple= when overloadin& operators- make sure that you adhere to e)istin& conven1 tions! 'f you don't- you will end up with code that compiles and is either incorrect or confusin&! /opefully this &rim introduction has not discoura&ed you from usin& operator overloadin&! Hperator overload1 in& is e)traordinarily useful and you will not #e disappointed with the possi#ilities that are a#out to open up to you! 0ith that said- let's #e&in discussin& the mechanics #ehind this powerful techni5ue! :eneral 2&erator 27erloading -rinci&les To define an overloaded operator- declare a function whose name is o$erator op- where op represents whatever operator you're overloadin&! Thus the overloaded assi&nment operator is o$erator =- while the overloaded operator is o$erator ! 0henever you use a #uilt1in operator on instances of a class- C++ will replace the use of that operator with a call to the proper o$erator op function! 8or e)ample- the follow1 in& code=
string one, t'o, three; one = t'o ! three;
is e5uivalent to
string one, t'o, three; one.operator, !operator3 !t6oB three$$;
The second version- while syntactically le&al- is e)tremely rare in practice since the point of operator overload1 in& is to make the synta) more intuitive! ,lso note that o$erator= is a mem#er function of string and that o$erator! is a free function! 0ith a few e)ceptions- overloaded operators can #e defined either as class mem1 #ers or free functions- and we will e)plore #oth cases in this chapter! "otice that operator overloadin& is simply synta7 sugar- a way of rewritin& one operation Kin this case- function callsL usin& a different synta)! Hverloaded operators are not somehow 4more efficient6 than other functions simply #ecause the function calls aren't e)plicit- nor are they treated any different from re&ular functions! 0hen overloadin& operators- you cannot define #rand1new operators like - or [! ,fter all- C++ wouldn't know the associativity or proper synta) for the operator Kis one - t'o ! three interpreted as (one - t'o) ! three or one - (t'o ! three)>L ,dditionally- you cannot overload any of the followin& operators- #ecause they are needed at compile1time=
1 ;<B 1
33 . S3 .2 siUeof t%$eid :%4lass33value one.value a ) b S ,8 3 8 a.2m%4lass<tr; siUeof(:%4lass) t%$eid(:%4lass)
Cha#ter 11% 5#erator 5!erloading Scope resolution Gem#er selection Ternary conditional Cointer1to1mem#er selection K#eyond the scope of this te)tL Size of o#*ect Funtime type information operator K#eyond the scope of this te)tL
.ou also cannot overload any of C++'s typecastin& operators Ke!&! stati*B*astL! The num#er of parameters to an overloaded operator depends on the particular operator #ein& overloaded! ,s you saw in the chapter on copy constructors and assi&nment operators- the assi&nment operator Ko$erator =L takes a sin&le parameter representin& the value #ein& assi&ned! Hther operators like o$erator ! usually have two parameters- one for each operand- while some operators like the pointer dereference operator o$erator 2 take none! "ote that operator overloadin& only lets you define what #uilt1in operators mean when applied to objects! .ou cannot use operator overloadin& to redefine what addition means as applied to ints- nor can you chan&e how pointers are dereferenced! Then a&ain- #y the principle of least astonishment- you wouldn't want to do this any1 way! L7alues and 17alues 7efore discussin& the specifics of operator overloadin&- we need to take a 5uick detour to e)plore two concepts from pro&rammin& lan&ua&e theory called l!alues and r!alues! values and rvalues stand for 4left values6 and 4ri&ht values6 are are so1named #ecause of where they can appear in an assi&nment statement! 'n particular- an lvalue is a value that can #e on the left1hand side of an assi&nment- and an rvalue is a value that can only #e on the ri&ht1hand side of an assi&nment! 8or e)ample- in the statement 7 = K- 7 is an lvalue and K is an rvalue! Similarly- in 2itr = 8FG- 2itr is the lvalue and 8FG is the rvalue! 't is ille&al to put an rvalue on the left1hand side of an assi&nment statement! 8or e)ample- 8FG = JH is ille&al #ecause 8FG is an rvalue- and Get(nteger() = 7 is ille&al #ecause the return value of Get(nteger() is an rvalue! /owever- it is le&al to put an lvalue on the ri&ht1hand side of an assi&nment- as in 7 = % or 7 = 2itr! ,t this point the distinction #etween lvalues and rvalues seems purely academic! 4Hkay-6 you mi&ht say- 4some thin&s can #e put on the left1hand side of an assi&nment statement and some thin&s can't! So what>6 0hen writ1 in& overloaded operators- the lvalueIrvalue distinction is e)tremely important! 7ecause operator overloadin& lets us define what the #uilt1in operators mean when applied to o#*ects of class type- we have to #e very careful that overloaded operators return lvalues and rvalues appropriately! 8or e)ample- #y default the ! operator returns an rvalueR that is- it makes no sense to write
(7 ! %) = K;
Since this would assi&n the value ? to the result of addin& 7 and %! /owever- if we're not careful when overload1 in& the ! operator- we mi&ht accidentally make the a#ove statement le&al and pave the way for nonsensical #ut le&al code! Similarly- it is le&al to write
m%Arra%[K] = 8FG;
So the element1selection operator Kthe #rackets operatorL should #e sure to return an lvalue instead of an rvalue! 8ailure to do so will make the a#ove code ille&al when applied to custom classes!
1 ;<? 1
Fecall that an overloaded operator is a specially1named function invoked when the operator is applied to an o#1 *ect of a custom class type! Thus the code
(7 ! %) = K;
is e5uivalent to
operator3 !#B y$ = K;
is e5uivalent to
m%Arra%.operator9:!I$ = 8FG;
To ensure that these functions have the correct semantics- we need to make sure that o$erator! returns an rvalue and that o$erator[] returns an lvalue! /ow can we enforce this restriction> The answer has to do with the return type of the two functions! To make a function that returns an lvalue- have that function return a non1 *onst reference! 8or e)ample- the followin& function returns an lvalue=
stringS 6Dalue&un*tion();
7ecause a reference is *ust another name for a varia#le or memory location- this function hands #ack a reference to an lvalue and its return value can #e treated as such! To have a function return an rvalue- have that function return a *onst o#*ect #y value! Thus the function
const string CDalue&un*tion();
returns an rvalue!Q The reason that this trick works is that if we have a function that returns a *onst o#*ect- then code like
CDalue&un*tion() = 8FG;
is ille&al #ecause the return value of CDalue&un*tion is marked *onst! values and rvalues are difficult to understand in the a#stract- #ut as we #e&in to actually overload particular op1 erators the difference should #ecome clearer! 27erloading the $lement Selection 2&erator et's #e&in our descent into the realm of operator overloadin& #y discussin& the overloaded element selection operator Kthe [ ] operator- used to select elements from arraysL! .ou've #een usin& the overloaded element se1 lection operator ever since you encountered the string and ve*tor classes! 8or e)ample- the followin& code uses the ve*tor's overloaded element selection operator=
for(int i = 0; i m%De*tor.siUe(); !!i) my5ector9i: , 7DE;
Q Technically speakin& any non1reference value returned from a function is an rvalue! /owever- when returnin& o#*ects from a function- the rvalueIlvalue distinction is #lurred #ecause the assi&nment operator and other operators are mem#er functions that can #e invoked re&ardless of whether the receiver is an rvalue or lvalue! The additional *onst closes this loophole!
1 ;<6 1
'n the a#ove e)ample- while it looks like we're treatin& the ve*tor as a primitive array- we are instead callin& the a function named o$erator []- passin& i as a parameter! Thus the a#ove code is e5uivalent to
for(int i = 0; i m%De*tor.siUe(); !!i) m%De*tor.operator 9:!i$ = 8FG;
To write a custom element selection operator- you write a mem#er function called o$erator [] that accepts as its parameter the value that &oes inside the #rackets! "ote that while this parameter can #e of any type Kthink of the ST ma$L- you can only have a sin&le value inside the #rackets! This may seem like an ar#itrary restriction#ut makes sense in the conte)t of the principle of least astonishment= you can't put multiple values inside the #rackets when workin& with raw C++ arrays- so you shouldn't do so when workin& with custom o#*ects! 0hen writin& o$erator []- as with all overloaded operators- you're free to return o#*ects of whatever type you'd like! /owever- remem#er that when overloadin& operators- it's essential to maintain the same functionality you'd e)pect from the naturally1occurrin& uses of the operator! 'n the case of the element selection operator- this means that the return value should #e an lvalue- and in particular a reference to some internal class data determ1 ined #y the inde)! 8or e)ample- here's one possi#le prototype of the C++ string's element selection operator=
*lass string { $ubli*3 "2 ... 2" charS operator 9: !int position$; #;
/ere- o$erator[] takes in an int representin& the inde) and returns a reference to the character at that posi1 tion in the string! 'f string is implemented as a wrapper for a raw C strin&- then one possi#le implementa1 tion for o$erator[] mi&ht #e
*har? string::operator9: (int inde7) { return theString9inde#:; "" Assuming the5tring is a ra' 4 string #
7ecause o$erator[] returns a reference to an element- it is common to find o$erator[] paired with a *onst1overload that returns a *onst reference to an element in the case where the receiver o#*ect is immuta#le! There are e)ceptions to this rule- such as the ST ma$- #ut in the case of string we should provide a *onst overload- as shown here=
*lass string { $ubli*3 "2 ... 2" *har? o$erator [] (int $osition); const charS operator 9: !int position$ const; #;
The implementation of the *onst o$erator[] function is identical to the non1*onst version! 0hen writin& the element selection operator- it's completely le&al to modify the receiver o#*ect in response to a re5uest! 8or e)ample- with the ST ma$- o$erator[] will silently create a new o#*ect and return a reference to it if the key isn't already in the ma$! This is part of the #eauty of overloaded operators ( you're allowed to per1 form any necessary steps to ensure that the operator makes sense!
1 ;<@ 1
Unfortunately- if your class encapsulates a multidimensional o#*ect- such as a matri) or hierarchical key1value system- you cannot 4overload the [][] operator!6 , class is only allowed to overload one level of the #racket synta)R it's not le&al to desi&n o#*ects that dou#ly1overload []!Q 27erloading Com&ound Assignment 2&erators The compound assi&nment operators are operators of the form op= Kfor e)ample- != and 2=L that update an o#1 *ect's value #ut do not overwrite it! Compound assi&nment operators are often declared as mem#er functions with the followin& #asic prototype=
0y;lassS operator 3, !const ParameterTypeS param$
8or e)ample- suppose we have the followin& class- which represents a vector in three1dimensional space=
*lass De*torF9 { $ubli*3 "2 ... 2" $rivate3 stati* *onst int E@:B4==C9(EA;E5 = F; double *oordinates[E@:B4==C9(EA;E5]; #;
't is le&al to add two mathematical vectors to one anotherR the result is the vector whose components are the pair1 wise sum of each of the components of the source vectors! 'f we wanted to define a != operator for De*torF9 to let us perform this addition- we would modify the interface of De*torF9 as follows=
*lass De*torF9 { $ubli*3 "2 ... 2" 5ectorDDS operator3, !const 5ectorDDS other$; $rivate3 stati* *onst int E@:B4==C9(EA;E5 = F; double *oordinates[E@:B4==C9(EA;E5]; #;
'f you'll notice- o$erator!= returns 2this- a reference to the receiver o#*ect! Fecall that when overloadin& operators- you should make sure to define your operators such that they work identically to the C++ #uilt1in op1 erators! 't turns out that the != operator yields an lvalue- so the code #elow- thou&h the 5uintessence of a#ysmal style- is perfectly le&al=
int one, t'o, three, four; (one != t'o) != (three != four); Q There is a techni5ue called #ro7y objects that can make code alon& the lines of m%=b>e*t[7][%] le&al! The trick is to define an o$erator[] function for the class that returns another o#*ect that itself overloads o$erator[]! 0e'll see this trick used in the upcomin& chapter on a custom grid class!
1 ;<E 1
Since overloaded operators let custom types act like primitives- the followin& code should also compile=
5ectorDD one, t'o, three, four; (one != t'o) != (three != four);
'f we e)pand out the calls to the overloaded != operator- we find that this is e5uivalent to
De*torF9 one, t'o, three, four; one.operator3,!t6o$.operator 3,!three.operator 3,! our$$;
"ote that the reference returned #y one.o$erator!=(t'o) then has its own != operator invoked! Since o$, erator != is not marked *onst- had the != operator returned a *onst reference- this code would have #een ille&al! Gake sure to have any KcompoundL assi&nment operator return 2this as a non1*onst reference! Unlike the re&ular assi&nment operator- with the compound assi&nment operator it's commonly meanin&ful to accept o#*ects of different types as parameters! 8or e)ample- we mi&ht want to make e)pressions like m%De*tor 2= 8FG for De*torF9s meanin&ful as a scalin& operation! 'n this case- we can simply define an o$erator 2= that accepts a double as its parameter! 8or e)ample=
*lass De*torF9 { $ubli*3 "2 ... 2" De*torF9? o$erator != (*onst De*torF9? other); 5ectorDDS operator R, !double scaleFactor$; $rivate3 stati* *onst int E@:B4==C9(EA;E5 = F; double *oordinates[E@:B4==C9(EA;E5]; #;
2espite the fact that the receiver and parameter have different types- this is perfectly le&al C++! /ere's one pos1 si#le implementation=
De*torF9? 5ectorDD::operatorR, (double s*ale&a*tor) { for(int + = 0; + E@:B4==C9(EA;E5; !!+) *oordinates[+] 2= s*ale&a*tor; return 2this; #
,lthou&h we have implemented o$erator!= and o$erator2= for the De*torF9 class- C++ will not automat1 ically provide us an implementation of o$erator,= and o$erator"=- despite the fact that those functions can easily #e implemented as wrapped calls to the operators we've already implemented! This mi&ht seem counterin1 tuitive- #ut prevents errors from cases where seemin&ly symmetric operations are undefined! 8or e)ample- it is le&al to multiply a vector and a matri)- thou&h the division is undefined! 8or completeness' sake- we'll prototype o$erator,= and o$erator"= as shown here=
1 ;<< 1
"ow- how mi&ht we &o a#out implementin& these operators> o$erator"= is the simplest of the two and can #e implemented as follows=
De*torF9? De*torF933o$erator "= (double s*ale&a*tor) { Rthis R, 7.- T scaleFactor; return 2this; #
This implementation- thou&h cryptic- is actually 5uite ele&ant! The first line- 2this 2= 8.0 " s*ale&a*t, or- says that we should multiply the receiver o#*ect K 2thisL #y the reciprocal of s*ale&a*tor! The 2= oper1 ator is the compound multiplication assi&nment operator that we wrote a#ove- so this code invokes o$erator2= on the receiver! 'n fact- this code is e5uivalent to
De*torF9? De*torF933o$erator "= (double s*ale&a*tor) { operatorR, !7.- T scaleFactor$; return 2this; b
2ependin& on your taste- you mi&ht find this particular synta) more reada#le than the first version! 8eel free to use either version! "ow- how would we implement o$erator,=- which performs a componentwise su#traction of two De*, torF9s> ,t a hi&h level- su#tractin& one vector from another is e5ual to addin& the inverse of the second vector to the first- so we mi&ht want to write code like this=
De*torF9? De*torF933o$erator ,= (*onst De*torF9? other) { Rthis 3, -other; return 2this; #
That is- we add ,other to the receiver o#*ect! 7ut this code is ille&al #ecause we haven't defined the unary minus operator as applied to De*torF9s! "ot to worry ( we can overload this operator as well! The synta) for this function is as follows=
1 A00 1
*lass De*torF9 { $ubli*3 "2 ... 2" De*torF9? o$erator != (*onst De*torF9? other); De*torF9? o$erator ,= (*onst De*torF9? other); De*torF9? o$erator 2= (double s*ale&a*tor); De*torF9? o$erator "= (double s*ale&a*tor); const 5ectorDD operator- !$ const; $rivate3 stati* *onst int E@:B4==C9(EA;E5 = F; double *oordinates[E@:B4==C9(EA;E5]; bR
There are four pieces of information a#out this function that deserve attention=
The name of the unary minus function is o$erator ,! The function takes no parameters! This lets C++ know that the function is the unary minus function K'!e! ,m%De*torL rather than the binary minus function Km%De*tor Q m%=therDe*torL! The function returns a *onst De*torF9! The unary minus function returns an r!alue rather than an l!alue- since code like ,7 = 8FG is ille&al! ,s mentioned a#ove- this means that the return value of this function should #e a *onst De*torF9! The function is marked *onst! ,pplyin& the unary minus to an o#*ect doesn't chan&e its value- and to enforce this restriction we'll mark o$erator Q *onst!
"ote that the return type of this function is *onst De*torF9 while the type of result inside the function is De*torF9! This isn't a pro#lem- since returnin& an o#*ect from a function yields a new temporary o#*ect and it's le&al to initialize a *onst De*torF9 usin& a De*torF9! 0hen writin& compound assi&nment operators- as when writin& re&ular assi&nment operators- you must #e care1 ful that self1assi&nment works correctly! 'n the a#ove e)ample with De*torF9's compound assi&nment operat1 ors we didn't need to worry a#out this #ecause the code was structured correctly! /owever- when workin& with the C++ string's != operator- since the string needs to allocate a new #uffer capa#le of holdin& the current string appended to itself- it would need to handle the self1assi&nment case- either #y e)plicitly checkin& for self1assi&nment or throu&h some other means! 27erloading #athematical 2&erators 'n the previous section- we provided overloaded versions of the != family of operators! Thus- we can now write classes for which e)pressions of the form one != t'o are valid! /owever- the seemin&ly e5uivalent e)pres1 sion one = one ! t'o will still not compile- since we haven't provided an implementation of the lone ! oper1
1 A01 1
ator! C++ will not automatically provide implementations of related operators &iven a sin&le overloaded operat1 or- since in some cases this could result in nonsensical or meanin&less #ehavior! The #uilt1in mathematical operators yield rvalues- so code like (7 ! %) = 8FG will not compile! Con1 se5uently- when overloadin& the mathematical operators- make sure they return rvalues as well #y havin& them return *onst o#*ects! et's consider an implementation of o$erator ! for a 45tring type that encapsulates a dynamically1allocated C1style strin&! 45tring has the followin& definition=
*lass 45tring { $ubli*3 "2 ... *onstru*tor, destru*tor, et* ... 2" 45tring? o$erator != (*onst 45tring? other); $rivate3 *har2 the5tring; #;
0e want to write an implementation of o$erator !! 7ecause the operator yields an rvalue- we're supposed to return a *onst 45tring- and #ased on our knowled&e of parameter passin&- we know that we should accept a *onst 45tring ? as a parameter! There's one more #it we're for&ettin&- thou&h- and that's to mark the o$er, ator ! function *onst- since o$erator ! creates a new o#*ect and doesn't modify either of the values used in the arithmetic statement! This results in the followin& code=
*lass 45tring { $ubli*3 "2 ... *onstru*tor, destru*tor, et* ... 2" 45tring? o$erator != (*onst 45tring? other); const ;String operator3 !const ;StringS other$ const; $rivate3 *har2 the5tring; #;
/owever- we mi&ht run into some trou#le writin& o$erator ! since the code for concatenatin& two C strin&s is tricky! 'f you'll notice- thou&h- we already have a workin& version of strin& concatenation in the #ody of o$er, ator !=! To unify our code- we'll therefore implement o$erator ! in terms of o$erator !=! The full ver1 sion of this code is shown #elow=
const ;String ;String::operator 3!const ;StringS other$ const { 45tring result = 2this; "" :a+e a dee$ *o$% of this 45tring. result 3, other; "" @se e7isting *on*atenation *ode. return result; #
"ow- all of the code for o$erator ! is unified- which helps cut down on codin& errors! There is an interestin& and common case we haven't addressed yet ( what if one of the operands isn't of the same type as the class> 8or e)ample- if you have a :atri7 class that encapsulates a A)A matri)- as shown here=
1 A0; 1
*lass :atri7 { $ubli*3 "2 ... 2"
0atri#S operator R, !double scalar$; "" 5*ale all entries $rivate3 stati* *onst int :A;C(PB5(]E = F; double entries[:A;C(PB5(]E][:A;C(PB5(]E]; #;
"ote that there is a defined 2= operator that scales all elements in the matri) #y a double factor! Thus code like m%:atri7 2= H.G8IHI is well1defined! /owever- since there's no defined o$erator 2- currently we cannot write m%:atri7 = m%:atri7 2 H.G8IHI! 'nitially- you mi&ht think that we could define o$erator 2 *ust as we did o$erator ! in the previous e)1 ample! 0hile this will work in most cases- it will lead to some pro#lems we'll need to address later! 8or nowhowever- let's add the mem#er function o$erator 2 to :atri7- which is defined as
*onst :atri7 :atri733o$erator 2(double s*alar) *onst { :%:atri7 result = 2this; result 2= s*alar; return result; #
"ow- we can write e)pressions like m%:atri7 = m%:atri7 2 H.G8IHI! /owever- what happens if we write code like m%:atri7 = H.G8IHI 2 m%:atri7> This is a semantically meanin&ful e)pression- #ut unfortu1 nately it won't compile! 0hen interpretin& overloaded operators- C++ will always preserve the order of values in an e)pression!Q Thus H.G8IHI 2 m%:atri7 is not the same as m%:atri7 2 H.G8IHI! Femem#er that the reason that m%:atri7 2 H.G8IHI is le&al is #ecause it's e5uivalent to m%:atri7.o$erator 2(H.G8IHI)! The e)pression H.G8IHI 2 m%:atri7- on the other hand- is ille&al #ecause C++ will try to e)pand it into (H.G8IHI).o$erator 2(m%:atri7)- which makes no sense! Up to this point- we've only seen overloaded operators as mem#er functions- usually #ecause the operators act relative to some receivin& o#*ect- #ut in C++ it's also le&al to define overloaded operators as free functions! 0hen definin& an overloaded operator as a free function- you simply define a &lo#al function named o$erator op Kwhere op is the operator in 5uestionL that accepts the proper num#er of parameters! 0hen usin& this over1 loaded operator in code- it will e)pand to a call to the &lo#al function! 8or e)ample- if there is a &lo#al o$erat, or ! function- then the line one ! t'o would e)pand into o$erator !(one, t'o)! This is e)actly what we need to solve this pro#lem! et's make o$erator 2 a free function that accepts two parameters- a double and a :atri7- and returns a *onst :atri7! Thus code like H.G8IHI 2 m%:atri7 will e)pand into calls to o$erator 2(H.G8IHI, m%:atri7)! The new version of o$erator 2 is defined #elow=
const 0atri# operator R !double scalarB const 0atri#S matri#$ { :atri7 result = 2matri7; matri7 2= s*alar; return result; #
Q Hne ma*or reason for this is that sometimes the arithmetic operators won't #e commutative! 8or e)ample- &iven matrices A and ,- A, is not necessarily the same as ,A- and if C++ were to ar#itrarily flip parameters it could result in some e)1 tremely difficult1to1track #u&s!
1 A0A 1
7ut here we run into the same pro#lem as #efore if we write m%:atri7 2 H.G8IHI- since we haven't defined a function acceptin& a :atri7 as its first parameter and an double as its second! To fi) this- we'll define a second free function o$erator 2 with the parameters reversed that's implemented as a call to the other version=
const 0atri# operator R!const 0atri#S matri#B double scalar$ { return scalar R matri#; "" 4alls o$erator2 (s*alar, matri7) #
,s a &eneral rule- mathematical operators like ! should always #e implemented as free functions! This prevents pro#lems like those descri#ed here! Hne important point to notice a#out overloadin& the mathematical operators versus the compound assi&nment operators is that it's considera#ly faster to use the compound assi&nment operators over the standalone mathem1 atical operators! "ot only do the compound assi&nment operators work in1place Kthat is- they modify e)istin& o#*ectsL- #ut they also return references instead of full o#*ects! 8rom a performance standpoint- this means that &iven these three strings=
string one = .;his .; string t'o = .is a .; string three = .string0.;
Hddly- the second version of this code is considera#ly slower than the first #ecause the ! operator &enerates tem1 porary o#*ects! Femem#er that one ! t'o ! three is e5uivalent to
operator 3!oneB operator 3!t6oB three$$
Dach call to o$erator ! returns a new string formed #y concatenatin& the parameters- so the code one ! t'o ! three creates two temporary string o#*ects! The first version- on the other hand- &enerates no tem1 porary o#*ects since the != operator works in1place! Thus while the first version is less si&htly- it is si&nific1 antly faster than the second! 27erloading 33 and -Hverloadin& the increment and decrement operators can #e a #it tricky #ecause there are two versions of each operator= #re"i7 and #ost"i7! Fecall that 7!! and !!7 are different operations ( the first will evaluate to the value of 7- then increment 7- while the second will increment 7 and then evaluate to the updated value of 7! .ou can see this #elow=
int 7 = 0 *out 7!! *out 7 7 = 0; *out *out !!7 7 endl; "" <rints3 0 endl; "" <rints3 8 endl; "" <rints3 8 endl; "" <rints3 8
1 A0B 1
,lthou&h this distinction is su#tle- it's tremendously important for efficiency reasons! 'n the postfi) version of !!- since we have to return the value of the varia#le was #efore it was incremented- we'll need to make a full copy of the old version and then return it! 0ith the prefi) !!- since we're returnin& the current value of the vari1 a#le- we can simply return a reference to it! Thus the postfi) !! can #e noticea#ly slower than the prefi) ver1 sionR this is the reason that when advancin& an ST iterator it's faster to use the prefi) increment operator! The ne)t 5uestion we need to address is how we can le&ally use !! and ,, in re&ular code! Unfortunately- it can &et a #it complicated! 8or e)ample- the followin& code is totally le&al=
int 7 = 0; !!!!!!!!!!!!!!7; "" (n*rements 7 seven times.
The prefi) ++ operator returns the varia#le #ein& incremented as an l!alue- so this statement means 4increment )- then increment ) a&ain- etc!6 /owever- if we use the postfi) version of !!- as seen here=
7!!!!!!!!!!!!!!; "" Error
0e &et a compile1time error #ecause 7!! returns the ori&inal value of ) as an r!alue- which can't #e incremented #ecause that would re5uire puttin& the rvalue on the left side of an assi&nment Kin particular- ) Y ) + 1L! "ow- let's actually &et into some code! Unfortunately- we can't *ust sit down and write o$erator !!- since it's unclear which o$erator !! we'd #e overloadin&! C++ uses a hack to differentiate #etween the prefi) and post1 fi) versions of the increment operator= when overloadin& the prefi) version of !! or ,,- you write o$erator !! as a function that takes no parameters! To overload the postfi) version- you'll overload o$erat, or !!- #ut the overloaded operator will accept as a parameter the inte&er value 0! 'n code- these two declara1 tions look like
:%4lass? o$erator !!(); "" <refi7 *onst :%4lass o$erator !!(int dummy); "" <ostfi7
"ote that the prefi) version returns a :%4lass? as an lvalue and the postfi) version a *onst :%4lass as an rvalue! 0e're allowed to implement !! and ,, in any way we see fit! /owever- one of the more common tricks is to write the !! implementation as a wrapped call to o$erator !=! ,ssumin& you've provided this function- we can then write the prefi) o$erator !! as
:%4lass? :%4lass33o$erator !!() { 2this != 8; return 2this; #
1 A0? 1
27erloading 1elational 2&erators Cerhaps the most commonly overloaded operators Kother than o$erator =L are the relational operatorsR for e)1 ample- and ==! Unlike the assi&nment operator- #y default C++ does not provide relational operators for your o#*ects! This means that you must e)plicitly overload the == and related operators to use them in code! The pro1 totype for the relational operators looks like this Kwritten for - #ut can #e for any of the relational operatorsL=
bool o$erator (*onst :%4lass? other) *onst;
.ou're free to choose any means for definin& what it means for one o#*ect to #e 4less than6 another! 0hat's im1 portant is consistency! That is- if one t'o- we should also have one 0= t'o and 0(one )= t'o)! 'n factyou may want to consider definin& *ust the operator and then implementin& ==- =- 0=F )- and )= as wrapper calls! Storing 2b?ects in STL maps Up to this point we've avoided storin& o#*ects as keys in ST ma$s! "ow that we've covered operator overload1 in&- thou&h- you have the necessary knowled&e to store o#*ects in the ST ma$ and set containers! 'nternally- the ST ma$ and set are layered on #inary trees that use the relational operators to compare ele1 ments! 2ue to some clever desi&n decisions- ST containers and al&orithms only re5uire the operator to com1 pare two o#*ects! Thus- to store a custom class inside a ma$ or set- you simply need to overload the operator and the ST will handle the rest! 8or e)ample- here's some code to store a <oint stru*t in a ma$=
stru*t $oint; { int 7, %; bool operator . !const pointTS other$ const * i !# %, other.#$ return # . other.#; return y . other.y; 4 #; ma$ $oint;, int) m%:a$; "" Eo' 'or+s using the default
o$erator.
"ormally- when you mark a class's data mem#ers private- only instances of that class are allowed to access them! /owever- in some cases you mi&ht want to allow specific other classes or functions to modify private data! 8or e)ample- if you were implementin& the ST ma$ and wanted to provide an iterator class to traverse it- you'd want that iterator to have access to the ma$'s underlyin& #inary tree! There's a sli&ht pro#lem here- thou&h! ,l1 thou&h the iterator is an inte&ral component of the ma$- like all other classes- the iterator cannot access private data and thus cannot traverse the tree!
1 A06 1
/ow are we to resolve this pro#lem> .our initial thou&ht mi&ht #e to make some pu#lic accessor methods that would let the iterator modify the o#*ect's internal data representation! Unfortunately- this won't work particularly well- since then any class would #e allowed to use those functions- somethin& that violates the principle of en1 capsulation! 'nstead- to solve this pro#lem- we can use the C++ friend keyword to &rant the iterator class ac1 cess to the ma$ or set's internals! 'nside the ma$ declaration- we can write the followin&=
tem$late t%$ename Xe%;%$e, t%$ename Dalue;%$e) *lass ma$ { $ubli*3 "2 ... 2" riend class iterator; *lass iterator { "2 ... iterator im$lementation here ... 2" #; #;
"ow- since iterator is a friend of ma$- it can read and modify the ma$'s private data mem#ers! $ust as we can &rant other classes friend access to a class- we can &ive friend access to &lo#al functions! 8or e)ample- if we had a free function :odif%:%4lass that accepted a :%4lass o#*ect as a reference parameterwe could let :odif%:%4lass modify the internal data of :%4lass if inside the :%4lass declaration we added the line
*lass :%4lass { $ubli*3 "2 ... 2" #; riend )oid 0odi y0y;lass!0y;lassS param$;
The synta) for friend can #e misleadin&! Dven thou&h we're prototypin& :odif%:%4lass inside the :%4, lass function- #ecause :odif%:%4lass is a friend of :%4lass it is not a mem#er function of :%4lass! ,fter all- the purpose of the friend declaration is to &ive outside classes and functions access to the :%4lass internals! 0hen usin& friend- there are two key points to #e aware of! 8irst- the friend declaration must precede the actual implementation of the friend class or function! Since C++ compilers only make a sin&le pass over the source file- if they haven't seen a friend declaration for a function or class- when the function or class tries to modify your o#*ect's internals- the compiler will &enerate an error! Second- note that while friend is 5uite use1 ful in some circumstances- it can 5uickly lead to code that entirely defeats the purpose of encapsulation! 7efore you &rant friend access to a piece of code- make sure that the code has a le&itimate reason to #e modifyin& your o#*ect! That is- don't make code a friend simply #ecause it's easier to write that way! Think of friend as a way of e)tendin& a class definition to include other pieces of code! The class- toðer with all its friend code- should comprise a lo&ical unit of encapsulation! 0hen overloadin& an operator as a free function- you mi&ht want to consider &ivin& that function friend access to your class! That way- the functions can efficiently read your o#*ect's private data without havin& to &o throu&h &etters and setters! Unfortunately- friend does not interact particularly intuitively with template classes! Suppose we want to provide a friend function <Wueue&riend for a template version of the CS1067IJ <Wueue! 'f <Wueue&riend is declared like this=
1 A0@ 1
.ou'll notice that <Wueue&riend itself is a template function! This means that when declarin& <Wueue&riend a friend of the template <Wueue- we'll need to make the friend declaration templatized- as shown here=
tem$late t%$ename ;) *lass <Wueue { $ubli*3 template .typename T> friend <Wueue&riend(*onst <Wueue ;)? $V); "2 ... 2" #;
'f you for&et the tem$late declaration- then your code will compile correctly #ut will &enerate a linker error! 0hile this can #e a #it of nuisance- it's important to remem#er since it arises fre5uently when overloadin& the stream operators- as you'll see #elow! 27erloading the Stream Insertion 2&erator /ave you ever wondered why *out ./ello, 'orld0. endl is syntactically le&al> 't's throu&h the overloaded operator in con*unction with ostreams!Q 'n fact- the entire streams li#rary can #e thou&ht of as a &i&antic li#rary of massively overloaded and )) operators! The C++ streams li#rary is desi&ned to &ive you ma)imum fle)i#ility with your input and output routines and even lets you define your own stream insertion and e)traction operators! This means that you are allowed to define the and )) operators so that e)pressions like *out m%4lass endl and *in )) m%4lass are well1defined! /owever- when writin& stream insertion and e)traction operators- there are hu&e num#er of considerations to keep in mind- many of which are #eyond the scope of this te)t! This ne)t section will discuss #asic strate&ies for overloadin& the operator- alon& with some limitations of the simple approach! ,s with all overloaded operators- we need to consider what the parameters and return type should #e for our overloaded operator! 7efore considerin& parameters- let's think of the return type! 0e know that it should #e le&al to chain stream insertions toðer ( that is- code like *out 8 H endl should compile cor1 rectly! The operator associates to the left- so the a#ove code is e5ual to
(((*out 8) H) endl)P
Thus- we need the operator to return an ostream! "ow- we don't want this stream to #e *onst- since then we couldn't write code like this=
*out .;his is a string0. set'(80) endl;
Since if *out .;his is a string0. evaluated to a *onst o#*ect- we couldn't set the width of the ne)t operation to 10! ,lso- we cannot return the stream #y value- since stream classes have their copy functions marked private! Cuttin& these two thin&s toðer- we see that the stream operators should return a non1 *onst reference to whatever stream they're referencin&! "ow let's consider what parameters we need! 0e need to know what stream we want to write to or read from- so initially you mi&ht think that we'd define overloaded stream operators as mem#er functions that look like this=
Q ,s a reminder- the ostream class is the #ase class for output streams! This has to do with inheritance- which we'll cover in a later chapter- #ut for now *ust realize that it means that #oth stringstream and ofstream are specializations of the more &eneric ostream class!
1 A0E 1
*lass :%4lass { $ubli*3 ostream? o$erator #;
Unfortunately- this isn't correct! Consider the followin& two code snippets=
*out m%4lass; m%4lass *out;
The first of these two versions makes sense- while the second is #ackwards! Unfortunately- with the a#ove definition of o$erator - we've accidentally made the second version syntactically le&al! The reason is that these two lines e)pand into calls to
*out.o$erator (m%4lass); m%4lass.o$erator (*out);
The first of these two isn't defined- since *out doesn't have a mem#er function capa#le of writin& our o#*ect Kif it did- we wouldn't need to write a stream operator in the first placeUL! /owever- #ased on our previous defini1 tion- the second version- while semantically incorrect- is syntactically le&al! Somehow we need to chan&e how we define the stream operator so that we are allowed to write *out m%4lass! To fi) this- we'll make the overloaded stream operator a free function that takes two parameters ( an ostream to write to and a m%4lass o#*ect to write! The code for this is=
ostreamS operator .. !ostreamS streamB const 0y;lassS mc$ * "2 ... im$lementation ... 2" return stream; 4
0hile this code will work correctly- #ecause o$erator is a free function- it doesn't have access to any of the private data mem#ers of :%4lass! This can #e a nuisance- since we'd like to directly write the contents of :%4lass out to the stream without havin& to &o throu&h the Kpossi#ly inefficientL &etters and setters! Thus- we'll declare o$erator a friend inside the :%4lass declaration- as shown here=
*lass :%4lass { $ubli*3 "2 :ore fun*tions. 2" riend ostreamS operator ..!ostreamS streamB const 0y;lassS mc$; #;
"ow- we're all set to do readin& and writin& inside the #ody of the insertion operator! 't's not particularly diffi1 cult to write the stream insertion operator ( all that we need to do is print out all of the meanin&ful class informa1 tion with some formattin& information! So- for e)ample- &iven a <oint class representin& a point in ;12 spacewe could write the insertion operator as
ostream? o$erator { stream A(A return stream; # (ostream? stream, *onst <oint? $t) $t.7 ., . $t.% A)A;
0hile this code will work in most cases- there are a few spots where it *ust won't work correctly! 8or e)amplesuppose we write the followin& code=
1 A0< 1
ookin& at this code- you'd e)pect that it would cause m%<oint to #e printed out and padded with space charac1 ters until it is at least twenty characters wide! Unfortunately- this isn't what happens! Since o$erator writes the o#*ect one piece at a time- the output will look somethin& like this=
08HFJKMGIL08HFJKMGIL (0, J)
That's nineteen spaces- followed #y the actual <oint data! The pro#lem is that when we invoke o$erator the function writes a sin&le ( character to stream! 't's this operation- not the 7oint as a whole- that will &et ali&ned to ;0 characters! There are many ways to circumvent this pro#lem- #ut perhaps the simplest is to #uffer the output into a stringstream and then write the contents of the stringstream to the destination in a sin&le operation! This can &et a #it complicated- especially since you'll need to copy the stream formattin& information over! 0ritin& a correct stream e)traction operator Ko$erator ))L is complicated! 8or more information on writin& stream e)traction operators- consult a reference! 27erloading R and -> Consider the followin& code snippet=
for(set string)33iterator itr = m%5et.begin(); itr 0= m%5et.end(); !!itr) *out 2itr . has length . itr,)length() endl;
/ere- we traverse a set string) usin& iterators- printin& out each strin& and its len&th! 'nterestin&ly- even thou&h set iterators are not raw pointers Kthey're o#*ects capa#le of traversin& #inary treesL- thanks to operator overloadin&- they can respond to the 2 and ,) operators as thou&h they were re&ular C++ pointers! 'f you create a custom class that acts like a C++ pointer Kperhaps a custom iterator or 4smart pointer-6 a topic we'll return to laterL- you can provide implementations of the pointer dereference and mem#er selection operat1 ors 2 and ,) #y overloadin& their respective operator functions! The simpler of these two functions is the point1 er dereference operator! To make an o#*ect that can #e dereferenced to yield an o#*ect of type ;- the synta) for its 2 operator is
TS operator R!$ const;
.ou can invoke the o$erator 2 function #y 4dereferencin&6 the custom pointer o#*ect! 8or e)ample- the fol1 lowin& code=
2m%4ustom<ointer = 8FG;
is completely e5uivalent to
m%4ustom<ointer.operator R!$ = 8FG;
7ecause we can assi&n a value to the result of o$erator 2- the o$erator 2 function should return an lvalue Ka non1*onst referenceL! There are two other points worth notin& here! 8irst- how can C++ distin&uish this o$erator 2 for pointer dereference from the o$erator 2 used for multiplication> The answer has to do with the num#er of paramet1 ers to the function! Since a pointer dereference is a unary operator- the function prototype for the pointer1
1 A10 1
dereferencin& o$erator 2 takes no parameters! /ad we wanted to write o$erator 2 for multiplication- we would have written a function o$erator 2 that accepts a parameter Kor a free function acceptin& two paramet1 ersL! Second- why is o$erator 2 marked *onst> This has to do with the difference #etween *onst pointers and pointers1to1*onst! Suppose that we have a *onst instance of a custom pointer class! Since the pointer object is *onst- it acts as thou&h it is a *onst pointer rather than a pointer1to1*onst! Conse5uently- we should #e a#le to dereference the o#*ect and modify its stored pointer without affectin& its *onstness! The arrow operator o$erator ,) is sli&htly more complicated than o$erator 2! 'nitially- you mi&ht think that o$erator ,) would #e a #inary operator- since you use the arrow operator in statements like m%4, lass<tr,)m%Element! /owever- C++ has a rather clever mechanism for o$erator ,) that makes it a unary operator! , class's o$erator ,) function should return a pointer to the o#*ect that the arrow operator should actually #e applied to! This may #e a #it confusin&- so an e)ample is in order! Suppose we have a class 4us, tom5tring<ointer that acts as thou&h it's a pointer to a C++ string o#*ect! Then if we have the followin& code=
4ustom5tring<ointer m%4ustom<ointer; *out m%4ustom<ointer,)length() endl;
'n the first version of the code- we treated the m%4ustom<ointer o#*ect as thou&h it was a real pointer #y usin& the arrow operator to select the length function! This code e)pands out into two smaller steps= 1! The 4ustom5tring<ointer's o$erator ,) function is called to determine which pointer the arrow should #e applied to! ;! The returned pointer then has the ,) operator applied to select the length function! Thus when writin& the o$erator ,) function- you simply need to return the pointer that the arrow operator should #e applied to! 'f you're writin& a custom iterator class- for e)ample- this is pro#a#ly the element #ein& iterator over! 0e'll e)plore one e)ample of overloadin& these operators in a later chapter! List of 27erloadable 2&erators The followin& ta#le lists the most commonly1used operators you're le&ally allowed to overload in C++- alon& with any restrictions a#out how you should define the operator!
1 A11 1
Oields value
Esage
:%4lass? o$erator =(*onst :%4lass? other);
value
0hen writin& compound assi&nment operators- make sure that you correctly handle 4self1compound1assi&nment!6 Fvalue
*onst :%4lass o$erator ! (*onst :%4lass? one, *onst :%4lass? t'o);
Fvalue
(*onst :%4lass? other) *onst; (*onst :%4lass? one, *onst :%4lass? t'o);
'f you're plannin& to use relational operators only for the ST container classes- you *ust need to overload the operator! Htherwise- you should overload all si) so that users aren't surprised that one 0= t'o is ille&al while 0(one == t'o) is defined!
[]
value
Elem;%$e? o$erator [](*onst Xe%;%$e? +e%); *onst Elem;%$e? o$erator [](*onst Xe%;%$e? +e%) *onst;
Gost of the time you'll need a *onst1overloaded version of the #racket operator! 8or1 &ettin& to provide one can lead to a real headacheU
!! ,, , 2
Crefi) version= :%4lass? o$erator !!(); Costfi) version= *onst :%4lass o$erator !!(int dumm%);
*onst :%4lass o$erator ,() *onst; Elem;%$e? o$erator 2() *onst;
0ith this function- you're allowin& your class to act as thou&h it's a pointer! The return type should #e a reference to the o#*ect it's 4pointin&6 at! This is how the ST iterators and smart pointers work! "ote that this is the unary 2 operator and is not the same as the 2 multiplicative operator!
,)
value
'f the ,) is overloaded for a class- whenever you write m%4lass,)m%:ember- it's e5uivalent to m%4lass.o$erator ,)(),)m%:ember! "ote that the function should #e *onst even thou&h the o#*ect returned can still modify data! This has to do with how pointers can le&ally #e used in C++! 8or more information- refer to the chapter on *onst!
))
value
(ostream? out, *onst :%4lass? m*); friend istream? o$erator )) (istream? in, :%4lass? m*);
()
Taries
Hperator overloadin& is an enormous topic in C++ and there's simply not enou&h space to cover it all in this chapter! 'f you're interested in some more advanced topics- consider readin& into the followin&= 1! 27erloaded ne6 and delete= .ou are allowed to overload the ne' and delete operators- in case you want to chan&e how memory is allocated for your class! "ote that the overloaded ne' and delete op1 erators simply chan&e how memory is allocated- not what it means to write ne' :%4lass! Hverloadin& ne' and delete is a complicated task and re5uires a solid understandin& of how C++ memory mana&e1 ment works- so #e sure to consult a reference for details! ;! Con7ersion functions= 'n an earlier chapter- we covered how to write conversion constructors- functions that convert o#*ects of other types into instances of your new class! /owever- it's possi#le to use operat1 or overloadin& to define an implicit conversion from o#*ects of your class into o#*ects of other types! The synta) is o$erator ;%$e()- where ;%$e is the data type to convert your o#*ect to! Gany profes1 sional pro&rammers advise a&ainst conversion functions- so make sure that they're really the #est option #efore proceedin&! -ractice -roblems Hperator overloadin& is 5uite difficult #ecause your functions must act as thou&h they're the #uilt1in operators! /ere are some practice pro#lems to &et you used to overloadin& operators= 1! 'n Cython- it is le&al to use ne&ative array indices to mean 4the element that many positions from the end of the array!6 8or e)ample- m%Arra%[,8] would #e the last element of an array- m%Arra%[,H] the penultimate element- etc! Usin& operator overloadin&- it's possi#le to implement this functionality for a custom array class! 2o you think it's a &ood idea to do so> 0hy or why not> Think a#out the principle of least astonishment when answerin&! ;! 0hy is it #etter to implement ! in terms of != instead of != in terms of !> <+int% Thin* about the number o" objects created using 89 and using 8)= A! D)plain how it's possi#le to define all of the relational operators in terms of ! M B! Fecall that the De*torF9 has the followin& data mem#ers in its private section=
stati* *onst int E@:B4==C9(EA;E5 = F; double *oordinates[E@:B4==C9(EA;E5];
Suppose that you want to #e a#le to store De*torF9s inside an ST set! .ou dele&ate this task to a friend who returns with the followin& implementation of o$erator =
bool De*torF933o$erator (*onst De*torF9? other) *onst { for(int + = 0; + E@:B4==C9(EA;E5; !!+) if(*oordinates[+] other.*oordinates[+]) return true; return false; #
This implementation of o$erator will cause serious pro#lems if De*torF9s are stored in an ST set or ma$! 0hy is this> <+int% &hat are the mathematical #ro#erties o" the less-than o#erator, and do they all a##ly to this im#lementation'= ?! 3iven a 45tring class that stores a C strin& as *har2 the5tring- implement o$erator!= for the 45tring class to perform a strin& concatenation! Gake sure that you handle the case where a 45tring is concatenated with itself!
Cha#ter 11% 5#erator 5!erloading 6! Consider the followin& definition of a 5$an struct=
stru*t 5$an { int start, sto$; 5$an(int begin, int end) 3 start(begin), sto$(end) {# #;
1 A1A 1
The 5$an struct allows us to define the ran&e of elements from Ostart- sto$L as a sin&le varia#le! 3iv1 en this definition of 5$an and assumin& start and sto$ are #oth non1ne&ative- provide another #rack1 et operator for 45tring that accepts a 5$an #y reference1to1*onst and returns another 45tring e5ual to the su#strin& of the initial 45tring from start to sto$! @! Consider the followin& interface for a class that iterates over a container of Elem;%$es=
*lass iterator { $ubli*3 bool o$erator== (*onst iterator? other); bool o$erator0= (*onst iterator? other); iterator o$erator!! (); Elem;%$e2 o$erator2 () *onst; Elem;%$e2 o$erator,) () *onst; #;
There are several mistakes in the definition of this iterator! 0hat are they> /ow would you fi) them> M E! valarra% is a template class declared in valarra%) that encapsulates a fi)ed1size se5uence of ele1 ments! Unlike ve*tor- valarra% is desi&ned to #e treated like a mathematical matri) or vector! 8or e)ample- &iven two valarra%s one and t'o- the code one ! t'o yields a new valarra% that is the componentwise sum of the two valarra%s- while one 2 t'o would #e their componentwise product! 't is also possi#le to write code like one != K to add five to each element in the valarra%! Suppose that you are implementin& valarra% Elem;%$e) and you choose to implement it as a wrapper around a ve*tor Elem;%$e) called arra%!Q 'mplement the ! and != functions for valarra% so that it is possi#le to add two valarra%s or a valarra% and an Elem;%$e! .ou can assume that any two valarra%s #ein& added have the same size! <! The findBif al&orithm takes three parameters ( two iterators and a predicate function ( and returns an iterator to the first element in the input ran&e for which the predicate returns true! 'f the predicate never returns true- findBif returns the end of the ran&e as a sentinel! Hne possi#le implementation of findBif is shown here=
tem$late t%$ename (n$ut(terator, t%$ename <redi*ate) inline (n$ut(terator findBif((n$ut(terator start, (n$ut(terator end, <redi*ate fn) { 'hile(start 0= end) { if(fn(2start)) return start; !!start; # return end; # ,ssumin& that findBif is called on a pair of ve*tor int)33iterators- identify three places in this
ve*tor
'n C++ parlance- a raw pointer like an int2 or a *har2 is sometimes called a dumb #ointer #ecause the pointer has no 4knowled&e6 of the resource it owns! 'f an int2 &oes out of scope- it doesn't inform the o#*ect it's point1 in& at and makes no attempt whatsoever to clean it up! The int2 doesn't own its resource- and assi&nin& one int2 to another doesn't make a deep copy of the resource or inform the other int2 that another pointer now ref1 erences its pointee! 7ecause raw pointers are so pro#lematic- many C++ pro&rammers prefer to use smart #ointers- o#*ects that mimic raw pointers #ut which perform functions #eyond merely pointin& at a resource! 8or e)ample- the C++ standard li#rary class autoB$tr- which we'll cover in the chapter on e)ception handlin&- acts like a re&ular pointer e)cept that it automatically calls delete on the resource it owns when it &oes out of scope! Hther smart pointers are custom1tuned for specific applications and mi&ht perform functions like lo&&in& access- synchroniz1 in& multithreaded applications- or preventin& accidental null pointer dereferences! Thanks to operator overload1 in&- smart pointers can #e #uilt to look very similar to re&ular C++ pointers! 0e can provide an implementation of o$erator 2 to support dereferences like 2m%5mart<ointer- and can define o$erator ,) to let clients write code to the effect of m%5mart<ointer,)*lear()! Similarly- we can write copy constructors and assi&n1 ment operators for smart pointers that do more than *ust transfer a resource! 1eference Counting Gemory mana&ement in C++ is tricky! .ou must #e careful to #alance every ne' with e)actly one delete- and must make sure that no other pointers to the resource e)ist after delete1in& it to ensure that later on you don't access invalid memory! 'f you delete memory too many times you run into undefined #ehavior- and if you delete it too few you have a memory leak! 's there a #etter way to mana&e memory> 'n many cases- yes- and in this e)tended e)ample we'll see one way to accomplish this usin& a techni5ue called re"erence counting! 'n particular- we'll desi&n a smart pointer class called 5mart<ointer which acts like a re&ular C++ pointer- e)cept that it uses reference countin& to prevent resource leaks! To motivate reference countin&- let's suppose that we have a smart pointer class that stores a pointer to a re1 source! The destructor for this smart pointer class can then delete the resource automatically- so clients of the smart pointer never need to e)plicitly clean up any resources! This system is fine in restricted circumstances- #ut runs into trou#le as soon as we have several smart pointers pointin& to the same resource! Consider the scenario #elow= Smart Point'r R'(o-r&' Smart Point'r 7oth of these pointers can access the stored resource- #ut unfortunately neither smart pointer knows of the other's e)istence! /ere we hit a sna&! 'f one smart pointer cleans up the resource while the other still points to it- then the other smart pointer will point to invalid memory! 'f #oth of the pointers try to reclaim the dynamic1 ally1allocated memory- we will encounter a runtime error from dou#le1delete1in& a resource! 8inally- if neither pointer tries to clean up the memory- we'll &et a memory leak!
1 A16 1
To resolve this pro#lem- we'll use a system called re"erence counting where we will e)plicitly keep track of the num#er of pointers to a dynamically1allocated resource! 0hile there are several ways to make such a system work- perhaps the simplest is to use an intermediary o#*ect! This can #e seen visually= Smart Point'r Int'rm'diary
R'(o-r&'
"ow- the smart pointer stores a pointer to an intermediary o#*ect rather than a pointer directly to the resource! This intermediary o#*ect has a counter Kcalled a re"erence counterL that tracks the num#er of smart pointers ac1 cessin& the resource- as well as a pointer to the mana&ed resource! This intermediary o#*ect lets the smart point1 ers tell whether or not they are the only pointer to the stored resourceR if the reference count is anythin& other than one- some other pointer shares the resource! Crovided that we accurately track the reference count- each pointer can tell if it's the last pointer that knows a#out the resource and can determine whether to deallocate it! To see how reference countin& works- let's walk throu&h an e)ample! 3iven the a#ove system- suppose that we want to share the resource with another smart pointer! 0e simply make this new smart pointer point to the same intermediary o#*ect as our ori&inal pointer- then update the reference count! The resultin& scenario looks like this= Smart Point'r Int'rm'diary ! R'(o-r&'
Smart Point'r ,lthou&h in this dia&ram we only have two o#*ects pointin& to the intermediary- the reference1countin& system allows for any num#er of smart pointers to share a sin&le resource! "ow- suppose one of these smart pointers needs to stop pointin& to the resource ( may#e it's #ein& assi&ned to a different resource- or perhaps it's &oin& out of scope! That pointer decrements the reference count of the inter1 mediary varia#le and notices that the reference count is nonzero! This means that at least one smart pointer still references the resource- so the smart pointer simply leaves the resource as it is! Gemory now looks like this=
1 A1@ 1
Smart Point'r
R'(o-r&'
8inally- suppose this last smart pointer needs to stop pointin& to this resource! 't decrements the reference count#ut this time notices that the reference count is zero! This means that no other smart pointers reference this re1 source- and the smart pointer knows that it needs to deallocate the resource and the intermediary o#*ect- as shown here= Smart Point'r Int'rm'diary 0
R'(o-r&'
The resource has now #een deallocated and no other pointers reference the memory! 0e've safely and effect1 ively cleaned up our resources! Goreover- this process is completely automatic ( the user never needs to e)pli1 citly deallocate any memory! The followin& summarizes the reference1countin& scheme descri#ed a#ove= 0hen creatin& a smart pointer to mana&e newly1allocated memory- first create an intermediary o#*ect and make the intermediary point to the resource! Then- attach the smart pointer to the intermediary and set the reference count to one! To make a new smart pointer point to the same resource as an e)istin& one- make the new smart pointer point to the old smart pointer's intermediary o#*ect and increment the intermediary's reference count! To remove a smart pointer from a resource Keither #ecause the pointer &oes out of scope or #ecause it's #ein& reassi&nedL- decrement the intermediary o#*ect's reference count! 'f the count reaches zero- deal1 locate the resource and the intermediary o#*ect!
0hile reference countin& is an e)cellent system for mana&in& memory automatically- it does have its limitations! 'n particular- reference countin& can sometimes fail to clean up memory in 4reference cycles-6 situations where multiple reference1counted pointers hold references to one another! 'f this happens- none of the reference coun1 ters can ever drop to zero- since the cyclically1linked elements always refer to one another! 7ut #arrin& this sort of setup- reference countin& is an e)cellent way to automatically mana&e memory! 'n this e)tended e)amplewe'll see how to implement a reference1counted pointer- which we'll call 5mart<ointer- and will e)plore how the correct cocktail of C++ constructs can make the resultin& class slick and efficient!
The a#ove section details the im#lementation the 5mart<ointer class- #ut we have not talked a#out its inter"ace! 0hat functions should we provide> 0e'll try to make 5mart<ointer resem#le a raw C++ pointer as closely as possi#le- meanin& that it should support o$erator 2 and o$erator ,) so that the client can dereference the 5mart<ointer! /ere is one possi#le interface for the 5mart<ointer class=
template .typename T> class SmartPointer * public: e#plicit SmartPointer!TR memory$; SmartPointer!const SmartPointerS other$; SmartPointerS operator ,!const SmartPointerS other$; ^SmartPointer!$; TS operator R !$ const; TR operator -> !$ const;
4;
Constructs a new 5mart<ointer that mana&es the resource specified as the parameter! The reference count is initially set to one! 0e will assume that the provided pointer came from a call to ne'! This function is marked e7$li*it so that we cannot accidentally convert a re&ular C++ pointer to a 5mart, <ointer! ,t first this mi&ht seem like a stran&e desi&n decision- #ut it prevents a wide ran&e of su#tle #u&s! 8or e)ample- suppose that this constructor is not e7$li*it and consider the followin& function=
void <rint5tring(*onst 5mart<ointer string)? $tr) { *out 2$tr endl; #
This function accepts a 5mart<ointer #y reference1to1*onst- then prints out the stored strin&! "ow- what happens if we write the followin& code>
string2 $tr = ne' string(.Ra%0.); <rint5tring($tr); delete $tr;
The first line dynamically1allocates a string- passes it to <rint5tring- and finally deallocates it! Unfortu1 nately- this code will almost certainly cause a runtime crash! The pro#lem is that <rint5tring e)pects a 5mart<ointer string) as a parameter- #ut we've provided a string2! C++ notices that the 5mart, <ointer string) has a conversion constructor that accepts a string2- and constructs a temporary 5mart, <ointer string) usin& the pointer we passed as a parameter! This new 5mart<ointer starts trackin& the pointer with a reference count of one! ,fter the function returns- the parameter is cleaned up and its destructor invokes! This decrements the reference count to zero- and then deallocates the pointer stored in the 5mart, <ointer! The a#ove code then tries to delete $tr a second time- causin& a runtime crash! To prevent this pro#lem- we'll mark the constructor e7$li*it- which makes the implicit conversion ille&al and prevents this #u&&y code from compilin&!
1 A1< 1
Constructs a new 5mart<ointer that shares the resource contained in another 5mart<ointer- updatin& the reference count appropriately!
SmartPointerS operator,!const SmartPointerS other$;
Causes this 5mart<ointer to stop pointin& to the resource it's currently mana&in& and to share the resource held #y another 5mart<ointer! 'f the smart pointer was the last pointer to its resource- it deletes it!
^SmartPointer!$;
2etaches the 5mart<ointer from the resource it's sharin&- freein& the associated memory if necessary!
TS operatorR !$ const;
42ereferences6 the pointer and returns a reference to the o#*ect #ein& pointed at! "ote that o$erator2 is *onstR see the last chapter for more information why!
TR operator-> !$ const;
Feturns the o#*ect that the arrow operator should really #e applied to if the arrow is used on the 5mart<oint, er! ,&ain- see the last chapter for more information on this! 3iven this pu#lic interface for 5mart<ointer- we can now #e&in implementin& the class! 0e first need to de1 cide on how we should represent the reference1countin& information! Hne simple method is to define a private stru*t inside 5mart<ointer that represents the reference1countin& intermediary! This looks as follows=
tem$late t%$ename ;) *lass 5mart<ointer { $ubli*3 e7$li*it 5mart<ointer(;2 memor%); 5mart<ointer(*onst 5mart<ointer? other); 5mart<ointer? o$erator =(*onst 5mart<ointer? other); ^5mart<ointer(); ;? o$erator 2 () *onst; ;2 o$erator ,) () *onst; $rivate3 struct Intermediary * TR resource; int re ;ount; 4; IntermediaryR data; #;
/ere- the resour*e field of the (ntermediar% is the actual pointer to the stored resource and ref4ount is the reference count! 3iven this setup- we can implement the 5mart<ointer constructor #y creatin& a new (n, termediar% that points to the specified resource and has an initial reference count of one=
1 A;0 1
template .typename T> SmartPointer.T>::SmartPointer!TR res$ * data , ne6 Intermediary; data->resource , res; data->re ;ount , 7; 4
't's very important that we allocate the (ntermediar% o#*ect on the heap rather than as a data mem#er! That way- when the 5mart<ointer is cleaned up Keither #y &oin& out of scope or #y an e)plicit call to deleteL- if it isn't the last pointer to the shared resource- the intermediary o#*ect isn't cleaned up! 0e can similarly implement the destructor #y decrementin& the reference count- then cleanin& up memory if ap1 propriate! "ote that if the reference count hits zero- we need to delete #oth the resource and the intermediary! 8or&ettin& to deallocate either of these leads to memory leaks- the e)act pro#lem we wanted to avoid! The code for this is shown here=
template .typename T> SmartPointer.T>::^SmartPointer!$ * --data->re ;ount; i !data->re ;ount ,, -$ * delete data->resource; delete data; 4 4
This is an interestin& destructor in that it isn't &uaranteed to actually clean up any memory! Hf course- this is e)1 actly the #ehavior we want- since the memory mi&ht #e shared amon& multiple 5mart<ointers! 'mplementin& o$erator 2 and o$erator ,) simply re5uires us to access the pointer stored inside the 5mart<ointer! These two functions can #e implemented as follows=Q
template .typename T> TS SmartPointer.T>::operator R !$ const * return Rdata->resource; 4 template .typename T> TR SmartPointer.T>::operator -> !$ const * return data->resource; 4
"ow- we need to implement the copy #ehavior for this 5mart<ointer! ,s you saw in the chapter on copy con1 structors and assi&nment operators- one way to do this is to write helper functions *lear and *o$%=ther which perform deallocation and copyin&! 0e will use a similar approach here- e)cept usin& functions named deta*h and atta*h to make e)plicit the operations we're performin&! This leads to the followin& definition of 5mart, <ointer=
Q 't is common to see o$erator
,)
implemented as
is interpreted #y the compiler as ?(2(2this))- which means 4dereference the this pointer to &et the receiver o#1 *ect- then dereference the receiver! 8inally- return the address of the referenced o#*ect!6 ,t times this may #e the #est way to implement o$erator ,)- #ut ' advise a&ainst it in &eneral #ecause it's fairly cryptic!
?22this
1 A;1 1
#;
"ow- what should these functions do> The first of these- deta*h- should detach the 5mart<ointer from the shared intermediary and clean up the memory if it was the last pointer to the shared resource! 'n case this sounds familiar- it's #ecause this is e)actly the #ehavior of the 5mart<ointer destructor! To avoid code duplic1 ation- we'll move the code from the destructor into deta*h as shown here=
tem$late t%$ename ;) void 5mart<ointer ;)33deta*h() { ,,data,)ref4ount; if(data,)ref4ount == 0) { delete data,)resour*e; delete data; # #
0e can then implement the destructor as a wrapped call to deta*h- as seen here=
template .typename T> SmartPointer.T>::^SmartPointer!$ * detach!$; 4
The atta*h function- on the other hand- makes this 5mart<ointer #e&in pointin& to the specified (nterme, diar% and increments the reference count! /ere's one possi#le implementation of atta*h=
template .typename T> )oid SmartPointer.T>::attach!IntermediaryR to$ * data , to; 33data->re ;ount; 4
3iven these two functions- we can implement the copy constructor and assi&nment operator for 5mart<ointer as follows=
1 A;; 1
template .typename T> SmartPointer.T>::SmartPointer!const SmartPointerS other$ * attach!other.data$; 4 template .typename T> SmartPointer.T>S SmartPointer.T>::operator, !const SmartPointerS other$ * i !this %, Sother$ * detach!$; attach!other.data$; 4 return Rthis; 4
't is crucial that we check for self1assi&nment inside the o$erator= function- since otherwise we mi&ht destroy the data that we're tryin& to keep track ofU ,t this point we have a rather slick 5mart<ointer class! /ere's some code demonstratin& how a client mi&ht use 5mart<ointer=
5mart<ointer string) m%<tr(ne' string); 2m%<tr = .;his is a string0.; *out 2m%<tr endl; 5mart<ointer string) other = m%<tr; *out 2other endl; *out other,)length() endl;
The #eauty of this code is that client code usin& a 5mart<ointer string) looks almost identical to code us1 in& a re&ular C++ pointer! 'sn't operator overloadin& wonderful> $%tending SmartPointer The 5mart<ointer defined a#ove is useful #ut lacks some important functionality! 8or e)ample- suppose that we have the followin& function=
void 9o5omething(string2 $tr);
Suppose that we have a 5mart<ointer string) mana&in& a resource and that we want to pass the stored strin& as a parameter to 9o5omething! 2espite the fact that 5mart<ointer string) mimics a string2- it technically is not a string2 and C++ won't allow us to pass the 5mart<ointer into 9o5omething! Somehow we need a way to have the 5mart<ointer hand #ack the resource it mana&es! "otice that the only 5mart<ointer mem#er functions that &ive #ack a pointer or reference to the actual re1 source are o$erator2 and o$erator,)! Technically speakin&- we could use these functions to pass the stored string into 9o5omething- #ut the synta) would #e messy Kin the case of o$erator2L or ni&htmarish Kfor o$, erator ,)L! 8or e)ample=
1 A;A 1
"2 ;o use o$erator2 to get the stored resour*e, 'e have to first dereferen*e the 2 5mart<ointer, then use the address,of o$erator to *onvert the returned referen*e 2 into a $ointer. 2" DoSomething!SRmyPtr$; "2 ;o use o$erator,) to get the stored resour*e, 'e have to e7$li*itl% *all the 2 o$erator,) fun*tion. Ri+es0 2" DoSomething!myPtr.operator-> !$$;
Somethin& is clearly amiss and we cannot reasona#ly e)pect clients to write code like this routinely! 0e'll need to e)tend the 5mart<ointer class to provide a way to return the stored pointer directly! 'nitially- we mi&ht consider addin& a mem#er function to the 5mart<ointer interface to returned the stored pointer! 8or e)ample- we mi&ht add a get mem#er function so that we could use 5mart<ointer as follows=
9o5omething(myPtr.get!$);
There is nothin& technically wron& with this approach and in fact most smart pointer classes e)port a function like this one! /owever- this approach opens up the potential for e)tremely hard1to1detect #u&s! 'n particularsuppose that we have a 5mart<ointer that points to an o#*ect that itself defines a get function! Then we can write #oth m%<tr.get()- which retrieves the stored pointer- and m%<tr->get()- which invokes the get func1 tion of the stored resource! 5mart<ointer clients mi&ht accidentally call the wron& function #ut have trou#le trackin& down the source of the error #ecause the two are almost syntactically identical! To sidestep these sorts of pro#lems- instead we'll define a "ree "unction which accepts the 5mart<ointer as a parameter and returns the stored resource!Q 8or e)ample- if the function were called Get- we could do the followin&=
9o5omething(Fet!myPtr$);
0hich is clearly syntactically distinct from m%<tr,)get() and avoids this sort of pro#lem! The main pro#lem with this approach is that this free function Get needs to #e a#le to access the private data mem#ers of 5mart<ointer in order to return the stored resource! Conse5uently- we'll define Get as a friend of 5mart<ointer- as shown here=
Q The idea of makin& this operation a free function is #ased on the discussion of smart pointers in ,ndrei ,le)andrescu's e)cellent #ook -odern C++ esign!
1 A;B 1
tem$late t%$ename ;) *lass 5mart<ointer { $ubli*3 e7$li*it 5mart<ointer(;2 memor%); 5mart<ointer(*onst 5mart<ointer? other); 5mart<ointer? o$erator =(*onst 5mart<ointer? other); ^5mart<ointer(); ;? o$erator 2 () *onst; ;2 o$erator ,) () *onst; template .typename U> $rivate3 stru*t (ntermediar% { ;2 resour*e; int ref4ount; #; (ntermediar%2 data; void deta*h(); void atta*h((ntermediar%2 other); riend UR Fet!const SmartPointer.U>S toFet$;
#;
Since the Get function we'll #e writin& is itself a template- we have to make the friend declaration a template as well! 3iven this declaration- we can then implement Get as follows=
template .typename T> TR Fet!const SmartPointer.T>S toFet$ * return toFet.data->resource; 4
!urther $%tensions There are several more e)tensions to the 5mart<ointer class that we mi&ht want to consider- of which this sec1 tion e)plores two! The first is rather strai&htforward! ,t times- we mi&ht want to know e)actly how many 5mart<ointers share a resource! This mi&ht ena#le us to perform some optimizations- in particular a tech1 ni5ue called co#y-on-write! 0e will not e)plore this techni5ue here- thou&h you are encoura&ed to do so on your own! Usin& the same lo&ic as a#ove- we'll define another friend function called Get5hare4ount which accepts a 5mart<ointer and returns the num#er of 5mart<ointers accessin& that resource- includin& the 5mart, <ointer itself! This results in the followin& class definition=
1 A;? 1
tem$late t%$ename @) friend @2 Get(*onst 5mart<ointer @)? toGet); template .typename U> riend int FetShare;ount!const SmartPointer.U>S toFet$; $rivate3 stru*t (ntermediar% { ;2 resour*e; int ref4ount; #; (ntermediar%2 data; void deta*h(); void atta*h((ntermediar%2 other); #;
The last piece of functionality we'll consider is the a#ility to 4reset6 the 5mart<ointer to point to a different resource! 0hen workin& with a 5mart<ointer- at times we may *ust want to drop whatever resource we're holdin& and #e&in mana&in& a new one! ,s you mi&ht have suspected- we'll add yet another friend function called Ceset which resets the 5mart<ointer to point to a new resource! The final interface and code for Ce, set is shown here=
1 A;6 1
tem$late t%$ename ;) *lass 5mart<ointer { $ubli*3 e7$li*it 5mart<ointer(;2 memor%); 5mart<ointer(*onst 5mart<ointer? other); 5mart<ointer? o$erator =(*onst 5mart<ointer? other); ^5mart<ointer(); ;? o$erator 2 () *onst; ;2 o$erator ,) () *onst; tem$late t%$ename @) friend @2 Get(*onst 5mart<ointer @)? toGet); tem$late t%$ename @) friend int Get5hare4ount(*onst 5mart<ointer @)? toGet); template .typename U> riend )oid Ceset!SmartPointer.U>S toSetB UR resource$; $rivate3 stru*t (ntermediar% { ;2 resour*e; int ref4ount; #; (ntermediar%2 data; void deta*h(); void atta*h((ntermediar%2 other);
#;
template .typename T> )oid Ceset!SmartPointer.T>S toSetB TR resource$ { "2 1eAre no longer asso*iated 'ith our *urrent resour*e, so dro$ it. 2" toSet.detach!$; "2 Atta*h to a ne' intermediar% ob>e*t. 2" toSet.data , ne6 typename SmartPointer.T>::Intermediary; toSet.data->resource , resource; toSet.data->re ;ount , 7 #
0hen we chan&e the data field to point to a new (ntermediar% o#*ect- we use the synta) ne' t%$ename 5mart<ointer ;)33(ntermediar%! (ntermediar% is defined inside 5mart<ointer- a template classand so we must use the t%$ename keyword to indicate that 5mart<ointer ;)33(ntermediar% is the name of a type! See the chapter on templates if you need a refresher on t%$ename!
1 A;@ 1
Smart pointers e)tend #eyond the interface we've provided here! 'f you're up for a challen&e and want to fle) your C++ muscles- try implementin& some of these chan&es to 5mart<ointer= 1! The 5mart<ointer class desi&ned here simply performs resource mana&ement- #ut it's possi#le to #uild smart pointers that perform other tasks as well! 8or e)ample- the smart pointer mi&ht check that the resource is non1E@66 #efore dereferencin& it- or mi&ht perform usa&e lo&&in& to help monitor per1 formance! Godify the e)istin& 5mart<ointer class to support one of these e)tra pieces of functional1 ity! ;! ,s mentioned at the start of this chapter- reference countin& can lead to memory leaks due to reference cycles! Hne method for #reakin& these cycles is to pair smart pointers with wea* #ointers! 0eak point1 ers can look at the resource pointed at #y a smart pointer- #ut do not increment its reference count! 'n other words- weak pointers can !iew shared resources- #ut they don't own them! 0eak pointers can then #e 4locked-6 returnin& a new 5mart<ointer pointin& to the shared resource! 'mplement a 1ea+, <ointer class to pair with the 5mart<ointer implementation shown here! A! The implementation of reference countin& we implemented here allocates an intermediary o#*ect that keeps track of the num#er of smart pointers sharin& the stored resource! ,lternatively- we can desi&n the classes that will #e pointed at #y our 5mart<ointer class so that they contain their own internal refer1 ence count which can #e accessed only #y the 5mart<ointer class! This eliminates the need for the in1 termediary o#*ect and increases the overall efficiency of the reference countin&! D)periment with this means of reference countin& ( do you notice any performance improvements> B! ,nother way of performin& reference countin& without e)plicitly allocatin& an intermediary o#*ect uses a techni5ue called re"erence lin*ing! 'n addition to trackin& the shared resource- each smart pointer acts as a cell in a dou#ly1linked list- storin& a pointer to the ne)t and previous smart pointer that points to the resource! 0henever a new smart pointer is attached to an e)istin& pointer- it is spliced into the list- and each time a smart pointer is detached from the resource it is removed from the list! , resource is cleaned up when a smart pointer with no ne)t or previous pointer is detached from it! Fewrite the 5mart, <ointer class to use reference linkin&! 0hat chan&es to its pu#lic interface do you need to make> ?! 5mart<ointer cleans up resources allocated #y ne' #y ensurin& that there is e)actly one call to de, lete! /owever- there are other pairs of allocatorIdeallocator functions for which a 5mart<ointer1like class mi&ht #e useful! 8or e)ample- the C1style file readin& routine fo$en returns an o#*ect which must #e closed with a call to f*lose! D)tend the 5mart<ointer so that the stored resource can #e allocated and deallocated with an ar#itrary allocator and deallocator! 6! 'f you are familiar with inheritance Kor have read the later chapters of this readerL- then you have seen how in some cases it is possi#le to convert pointers of one type into pointers of a different type! See if you can use the template system to devise a way of convertin& 5mart<ointers that point to a derived class type to 5mart<ointers which point to a #ase class type!
tem$late t%$ename ;) *lass 5mart<ointer { $ubli*3 e7$li*it 5mart<ointer(;2 memor%); 5mart<ointer(*onst 5mart<ointer? other); 5mart<ointer? o$erator =(*onst 5mart<ointer? other); ^5mart<ointer(); ;? o$erator 2 () *onst; ;2 o$erator ,) () *onst; tem$late tem$late tem$late t%$ename @) friend @2 Get(*onst 5mart<ointer @)? toGet); t%$ename @) friend int Get5hare4ount(*onst 5mart<ointer @)? toGet); t%$ename @) friend void Ceset(5mart<ointer @)? to5et, @2 resour*e);
$rivate3 stru*t (ntermediar% { ;2 resour*e; int ref4ount; #; (ntermediar%2 data; void deta*h(); void atta*h((ntermediar%2 other);
#;
"2 4onstru*tor sets the 5mart<ointer to manage a resour*e, 'hi*h no' has referen*e 2 *ount one. 2" tem$late t%$ename ;) 5mart<ointer ;)335mart<ointer(;2 resour*e) { data = ne' (ntermediar%; data,)resour*e = resour*e; data,)ref4ount = 8; # "2 9estru*tor $ulls us off of this resour*e and *leans it u$ if ne*essar%. 2" tem$late t%$ename ;) 5mart<ointer ;)33^5mart<ointer() { deta*h(); # "2 atta*h a**e$ts an intermediar% and sets the 5mart<ointer to $oint to it. 2 then in*rements the referen*e,*ount. 2" tem$late t%$ename ;) void 5mart<ointer ;)33atta*h((ntermediar%2 to) { data = to; !!data,)ref4ount; # (t
1 A;< 1
"2 deta*h removes this $ointer from the resour*e, dro$$ing the referen*e *ount, and 2 *leaning u$ the resour*e if ne*essar%. 2" tem$late t%$ename ;) void 5mart<ointer ;)33deta*h() { ,,data,)ref4ount; if(data,)ref4ount == 0) { "2 9onAt forget to delete both the resour*e and the intermediar%0 2" delete data,)resour*e; delete data; # # "2 4o$% *onstru*tor >ust atta*hes us to the resour*e stored in the other 2 5mart<ointer. 2" tem$late t%$ename ;) 5mart<ointer ;)335mart<ointer(*onst 5mart<ointer? other) { atta*h(other.data); # "2 Assignment o$erator does all of the Vuir+% assignment o$erator me*hani*s, 2 but ultimatel% deta*hes us from the *urrent resour*e and atta*hes us to the 2 other resour*e. 2" tem$late t%$ename ;) 5mart<ointer ;)? 5mart<ointer ;)33o$erator= (*onst 5mart<ointer? other) { if(this 0= ?other) { deta*h(); atta*h(other.data); # return 2this; # "2 <ointer,dereferen*e o$erator returns a referen*e to the resour*e. 2" tem$late t%$ename ;) ;? 5mart<ointer ;)33o$erator 2 () *onst { return 2data,)resour*e; # "2 Arro' o$erator >ust returns the stored resour*e. 2" tem$late t%$ename ;) ;2 5mart<ointer ;)33o$erator ,) () *onst { return data,)resour*e; # "2 &ree fun*tion Get retrieves the stored resour*e. 2" tem$late t%$ename ;) ;2 Get(*onst 5mart<ointer ;)? toGet) { return toGet.data,)resour*e; # "2 &ree fun*tion Get5hare4ount returns the referen*e *ount. 2" tem$late t%$ename ;) int Get5hare4ount(*onst 5mart<ointer ;)? toGet) { return toGet.data,)ref4ount; #
1 AA0 1
"2 &ree fun*tion Ceset resets the $ointer to refer to a ne' resour*e. 2" tem$late t%$ename ;) void Ceset(5mart<ointer ;)? to5et, ;2 resour*e) { "2 1eAre no longer asso*iated 'ith our *urrent resour*e, so dro$ it. 2" to5et.deta*h(); "2 Atta*h to a ne' intermediar% ob>e*t. 2" to5et.data = ne' t%$ename 5mart<ointer ;)33(ntermediar%; to5et.data,)resour*e = resour*e; to5et.data,)ref4ount = 8; #
6e!er #ut o"" until run time what you can do at com#ile time) ( 2avid 3ries Compilers are e)cellent at detectin& type errors! 'f you try to treat an int as a string- the compiler will re1 5uire you to fi) the error #efore you can run the pro&ram! Crovided that you stay away from unsafe typecasts and make sure not to use &ar#a&e pointers- if the compiler compiles your pro&ram- you can #e reasona#ly con1 fident that you didn't mi) and match varia#les of different types! /owever- when it comes to other sorts of #u&s- C++ compilers are silent! 8or e)ample- the followin& is le&al C++ code- even thou&h it's &uaranteed to cause a runtime crash=
int2 m%<tr = E@66; 2m%<tr = 8FG; "" 9ereferen*e E@660
2e#u&&in& is never pleasant and we'd like as much as possi#le for the compiler to automatically detect #u&s for us! 7ut as mentioned a#ove- the compiler only checks for a small set of errors- mostly involvin& #reaches of the type system! 0hat if there was some way that we could encode information a#out what the pro&ram is doin& at the type level> That way- in the course of checkin& the types of the varia#les in the pro&ram- the compiler would also check for #u&s in the code! .ou have already seen an e)ample of this sort of checkin& with *onst- which allows the compiler to verify that code that should not overwrite data does not overwrite data! 'n this e)tended e)ample- we'll e)plore one instance where harnessin& the type system leads to #etter- simpler- and less error1 prone code! 'n doin& so- we'll see a novel use of templates that is sure to chan&e the way you think a#out codin& in C++! 9imensional Analysis Suppose that we want to write a physics simulation where o#*ects interact with each other accordin& to "ewton's laws! 8or e)ample- we mi&ht drop a ?k& mass onto a lever of len&th 10m with the fulcrum Am from one side with &ravity operatin& at <!EmIs;! Since each of these 5uantities has a real1num#ered value- we initially store them as doubles! 8or e)ample=
double mass = K; "" K+g double length = 80; "" 80m double gravit% = L.I; "" L.Im"sH
"ow- let's suppose that we want to compute the wei&ht of the ?k& #lock in the &ravity field! This value is com1 puted #y takin& the product of the mass of the o#*ect and &ravitational acceleration- so we could write
double 'eight = mass 2 gravit%;
/owever- suppose that we accidentally make a typo in our code and instead write
double 'eight = mass ! gravit%;
1 AA; 1
This value is incorrect and if we try to use it in a critical system we mi&ht have a catastrophic failure on our hands! 7ut worse- the C++ compiler won't &ive us any indication that this code contains an error #ecause it's perfectly le&al C++! There's nothin& wron& with addin& doubles to doubles- and if the formula we're usin& is incorrect there's no way for the compiler to fi&ure this out! The pro#lem here is that the values we're storin& are not real1num#ered values! The value of ?k& is five *ilograms- not *ust "i!e- and similarly <!EmIs; is <!E meters #er second sAuared- not *ust >)G! This additional inform1 ation is lost if we store the values as doubles! 'f the compiler did have this information- it could point out that the sum of ?k& and <!EmIs; is meanin&less! Goreover- if we indicated that the type of the 'eight varia#le should #e in newtons-Q the compiler could check that the e)pression #ein& assi&ned to 'eight was of the proper type! Thus we could not write code to the effect of 'eight = mass since mass has units k& and 'eight has units k&XmIs;! This system of checkin& that the units in a computation a&ree is known as dimensional analysis and will #e the focus of this e)tended e)ample! Hur &oal will #e to develop a system for encodin& the units associated with each varia#le into the type system so that we can detect unit errors effectively! The 5uestion- of course- is how we can do this! A !irst A&&roach Hne solution to the dimensional analysis pro#lem is to construct a series of types representin& values with spe1 cific units- then to define conversions #etween each of them! 8or e)ample- a varia#le representin& a 5uantity of kilo&rams could #e typed as a +ilogram;- whereas a varia#le holdin& a duration of time would #e in se*ond;! 'deally- we could write code to this effect=
+ilogramT mass(K.0); "" K.0 +g accelerationT gravit%(L.I); "" L.I m"sH ne6tonT 'eight = mass 2 gravit%;
0e can make the synta) mass 2 gravit% compile #y overloadin& the 2 operator #etween +ilogram; and a**eleration;- and could similarly define addition- su#traction- etc! ,s an e)ample- here's a possi#le imple1 mentation of +ilogram;- a**eleration;- and ne'ton;=
*lass +ilogram; { $ubli*3 e7$li*it +ilogram;(double amount) 3 Vuantit%(amount) {# "2 A**esses the stored value. 2" double getWuantit%() *onst { return Vuantit%; # $rivate3 double Vuantit%; #;
Q Hne newton K"L is e5ual to one kilo&ram meter per second s5uared and is a unit of force! 't may help to remem#er that a newton is rou&hly the wei&ht of an apple!
1 AAA 1
"2 9efine multi$li*ation as a free fun*tion. Eote that there are t'o versions here 2 be*ause 'e *an 'rite mass 2 a**eleration or a**eleration 2 mass. 2" const ne6tonT operator R !const +ilogramTS mB const accelerationTS a$ * return ne6tonT!m.getHuantity!$ R a.getHuantity$; 4 const ne6tonT operator R !const accelerationTS aB const +ilogramTS m$ * return ne6tonT!m.getHuantity!$ R a.getHuantity$; 4
The code a#ove should #e mostly self1e)planatory ( each class has a constructor that accepts a double and stores it internally and e)ports a getWuantit% mem#er function which returns the stored 5uantity! The two im1 plementations of o$erator 2 each accept a +ilogram; and an a**eleration;- call the getWuantit% mem#er functions to yield the stored 5uantity- and return a ne'ton; o#*ect with 5uantity e5ual to the product of the two 5uantities! 'n case you're not familiar with the synta) return ne'ton;(m.getWuantit%() 2 a.getWuantit%())this is perfectly le&al C++ and is a use of the tem#orary object synta7! The synta) ne'ton; (m.getWuant, it%() 2 a.getWuantit%()) looks like a call to the constructor of ne'ton; and this is not that far from the truth! 'n C++- you are allowed to create temporary o#*ects for the duration of a sin&le line of code #y e)plicitly callin& the o#*ect's constructor! 'n this code- we construct a temporary ne'ton; o#*ect and immediately return it! 'nitially this approach seems like it's e)actly what we're lookin& for ( a way of encodin& units into the type sys1 tem to prevent accidental misuse! 8or e)ample- we cannot write code like the followin&=
+ilogramT mass(K.0); ne6tonT 'eight = mass; "" Error Q no *onversion from +ilogram; to ne'ton;
1 AAB 1
This is an error #ecause the line ne'ton; 'eight = mass will try to find a way of convertin& a +ilogram; to a ne'ton; when no such conversion e)ists! Similarly- if we try writin&
+ilogram; mass(K.0); a**eleration; gravit%(L.I); ne'ton; 'eight = mass 3 gra)ity; "" Error3 o$erator! not defined here.
0e'll &et a compile1time error #ecause there is no ! operator defined on ar&uments of type +ilogram; and a*, *eleration;! /owever- this approach has its limitations! 8or e)ample- let's suppose that we now want to introduce two more types into the mi)- se*ond; representin& seconds and meter; representin& distance! 0e can follow the a#ove pattern and create the followin& types=
*lass meter; { $ubli*3 e7$li*it meter;(double amount) 3 Vuantit%(amount) {# "2 A**esses the stored value. 2" double getWuantit%() *onst { return Vuantit%; # $rivate3 double Vuantit%; #; *lass se*ond; { $ubli*3 e7$li*it se*ond;(double amount) 3 Vuantit%(amount) {# "2 A**esses the stored value. 2" double getWuantit%() *onst { return Vuantit%; # $rivate3 double Vuantit%; #;
"ow- one newton is e5ual to one kilo&ram meter per second s5uared- so if we multiply toðer a +ilogram;- a meter;- and then divide #y two se*ond;s- we should end up with a ne'ton;! 7ut how can we represent this in code> 0e'd like to #e a#le to write
+ilogram; mass(8.0); meter; distan*e(H.0); se*ond; time(F.0); ne'ton; for*e = mass R distance T !time R time$;
'f we want to #e a#le to do this usin& overloaded operators- we'd need to overload the 2 operator applied to +ilogram; and meter; as well as #etween se*ond; and se*ond;- plus the " operator applied to the results of these operations! 7ut we don't currently have a type representin& the product of a kilo&ram and a meter- so we'd need to introduce a +ilogram:eter; type to hold the product of a +ilogram; and a meter;- and simil1 arly would need to make a se*ond5Vuared; type to hold the product of a se*ond; and a se*ond;! 'f we were desi&nin& these classes as a li#rary- we'd need to enumerate all reasona#le com#inations of kilo&rams-
1 AA? 1
meters- and secondsR define a type for each of these com#inations of unitsR and then implement conversion oper1 ators #etween all of them! This is neither feasi#le nor sound! 8or e)ample- if we define classes representin& all com#inations of kilo&rams- meters- and seconds from k&110Xm110Xs110 to k&10Xm10Xs10- all it takes is one application re5uirin& a k&11 and our code would #e insufficient! Goreover- think a#out the num#er of different implementations of o$erator2 that would #e necessary for this approach to work! ,ny two values can #e multiplied re&ardless of their units- so if we define " classes- we'd need HK";L different o$erator2 functions defined #etween them! 0e'd similarly need HK"L different o$erator ! and o$erator , functions so that we can add and su#tract units of the same type! That's an a#1 surdly lar&e amount of code and 5uickly #ecomes infeasi#le to write or maintain! 'n short- this idea of creatin& custom classes for each com#ination of units is well1intentioned and works in small e)amples- #ut rapidly #lows up into an unmana&ea#le mess of code! 0e're &oin& to have to try somethin& else! A Second A&&roach Hne o#servation that can &reatly simplify the implementation of types with units is e)actly how values of differ1 ent units can #e multiplied and divided! 8or simplicity- this ne)t discussion assumes that we're only dealin& with kilo&rams- meters- seconds- and units derived from them Ki!e! k&Xm and k&Xm;IsAL- #ut this analysis easily scales to more #ase types! Suppose that we have two varia#les ) and y- each representin& a value with some associated units! 8or notation1 al simplicity- we'll say that ) Y K51- k&1- m1- s1L if ) Y 51 k&k&1Xmm1Xss1- where 51 is a scalar! 8or e)ample- if ) Y ?k&- then we'd represent this as ) Y K?- 1- 0- 0L and similarly if ) Y ;0k&XmXs 1;- we'd write that ) Y K;0- 1- 11;L! 3iven this notation- we can define addition of dimensioned types as follows Ksu#traction can #e defined similarlyL=
K51- k&1- m1- s1L + K5;- k&;- m;- s;L Y K51 + 5;- k&1- m1- s1L if k&1 Y k&;- m1 Y m;- and s1 Y s;! 'n other wordsaddin& two values with the same units forms a new value that's the sum of the two 5uantities with the same units! K51- k&1- m1- s1L + K5;- k&;- m;- s;L is undefined otherwise! That is- you cannot add two values unless the units a&ree!
0e can also define multiplication and division of dimensioned types like this=
K51- k&1- m1- s1L e K5;- k&;- m;- s;L Y K515;- k&1 + k;- m1 + m;- s1 + s;L! That is- multiplyin& two dimen1 sioned values yields a new dimensioned value that's the product of the two 5uantities where the units are the sum of the units in the two dimensioned values! 8or e)ample- ?k& e ;0k&Xs Y 100k&;Xs! K51- k&1- m1- s1L I K5;- k&;- m;- s;L Y K51 I 5;- k&1 1 k;- m1 1 m;- s1 1 s;L! That is- dividin& two dimensioned values yields a new dimensioned value that's the 5uotient of the two 5uantities where the units are the difference of the units in the two dimensioned values! 8or e)ample- ;0k& I ?k&Xs Y Bs11!
These mathematical relations #etween dimensioned types su&&ests an implementation of a dimensioned type o#1 *ect in C++! The o#*ect internally stores #oth a 5uantity and the e)ponents of the kilo&rams- meters- and seconds of the units! 0e can then define the 2 and " operators #y computin& the product or 5uotient of the num#ers and then addin& or su#tractin& the units from the operands! 8inally- we can define the ! and Q operators to check if the types a&ree and then return a new value with the same units and the value computed appropriately! 'n code- this looks like this=
1 AA6 1
*lass 9imension;%$e { $ubli*3 "2 9imension;%$e *onstru*tor a**e$ts four values Q the Vuantit% and the three 2 unit e7$onents Q then initialiUes the 9imension;%$e a$$ro$riatel%. 2" DimensionType!double GuantityB int +gB int mB int s$ : Guantity!Guantity$B +g!+g$B m!m$B s!s$ *4 "2 A**essor methods. 2" double getHuantity!$ const { return Vuantit%; # int getMilograms!$ const { return +g; # int get0eters!$ const { return m; # int getSeconds!$ const { return s; # $rivate3 double Vuantit%; int +g, m, s; #; "2 ;o add t'o 9imension;%$es, 'e assert() that the t%$es agree and then return the 2 $ro$er value. 5ubtra*tion is defined similarl%. 2" const DimensionType operator3 !const DimensionTypeS oneB const DimensionTypeS t6o$ { assert!one.getMilograms!$ ,, t6o.getMilograms!$$; assert!one.get0eters!$ ,, t6o.get0eters!$$; assert!one.getSeconds!$ ,, t6o.getSeconds!$$; return 9imension;%$e(one.getWuantit%() ! t'o.getWuantit%(), one.getXilograms(), one.get:eters(), one.get5e*onds()); # const DimensionType operator- !const DimensionTypeS oneB const DimensionTypeS t6o$ { assert!one.getMilograms!$ ,, t6o.getMilograms!$$; assert!one.get0eters!$ ,, t6o.get0eters!$$; assert!one.getSeconds!$ ,, t6o.getSeconds!$$; return 9imension;%$e(one.getWuantit%() , t'o.getWuantit%(), one.getXilograms(), one.get:eters(), one.get5e*onds()); #
1 AA@ 1
"2 :ulti$li*ation and division are done b% multi$l%ing values and then u$dating the 2 units a$$ro$riatel%. 9ivision is defined similarl%. 2" const DimensionType operatorR !const DimensionTypeS oneB const DimensionTypeS t6o$ { return 9imension;%$e(one.getWuantit%() 2 t'o.getWuantit%(), one.getMilograms!$ 3 t6o.getMilograms!$B one.get0eters!$ 3 t6o.get0eters!$B one.getSeconds!$ 3 t6o.getSeconds!$); # const DimensionType operatorT !const DimensionTypeS oneB const DimensionTypeS t6o$ { return 9imension;%$e(one.getWuantit%() " t'o.getWuantit%(), one.getMilograms!$ - t6o.getMilograms!$B one.get0eters!$ - t6o.get0eters!$B one.getSeconds!$ - t6o.getSeconds!$); #
There's a lot of code here- so let's take a few seconds to &o over it! The 9imension;%$e class e)ports a sin&le constructor which takes as input a 5uantity alon& with the e)ponents of the units- then stores these values! 't also defines four accessor methods to retrieve these values later! The implementation of o$erator! and o$erator, first assert that the units on the parameters a&ree- then perform the addition or su#traction! 8i1 nally- the implementation of o$erator2 and o$erator" compute the new values and units- then return an ap1 propriately1constructed 9imension;%$e! The main advanta&e of this solution over the previous one is that it allows us to have units of any values that we see fit! 0e can create a 9imension;%$e(K.0, H, K, ,F) to represent ? k&;Xm?IsA *ust as easily as we can create a 9imension;%$e(K.0, 0, 0, 8) to represent five seconds! /owever- this new 9imension;%$e has several serious weaknesses! The most prominent error is that this implementation of 9imension;%$e de1 fers unit checkin& to runtime! 8or e)ample- suppose that we write the followin& code=
9imension;%$e mass(K.0, 8, 0, 0); "" K+g 9imension;%$e a**eleration(L.I, 0, 8, ,H); "" L.Im"sH 9imension;%$e 'eight = mass 3 acceleration; "" 1hoo$s0
@ndefined0
The pro&ram will compile correctly- #ut will tri&&er an assert failure at runtime when we tryin& addin& mass and a**eleration! This is an improvement over usin& raw doubles to hold the values since we at least &et a notification that there is a type error- #ut we have to actually e)ecute the pro&ram to find the #u&! 'f the incor1 rect code is in a rarely1used part of the pro&ram- we mi&ht not notice the error- and furthermore if we can't test the code in isolation Kperhaps #ecause it's part of a comple) systemL we have no way of detectin& the error until it's too late! The other ma*or pro#lem with this system is that we have no way of communicatin& the e7#ected type of a value to the compiler! 8or e)ample- suppose that we want to compute the wei&ht of a #lock as follows=
9imension;%$e mass(K.0, 8, 0, 0); "" K+g 9imension;%$e a**eleration(L.I, 0, 8, ,H); "" L.Im_s,H 9imension;%$e 'eight = mass T acceleration; "" 1ell,defined, but in*orre*t
/ere- we've said that the wei&ht of the #lock is the Auotient of the mass and acceleration rather than their #roduct! This is incorrect- #ut the a#ove code will work compile and run without errors since all of the opera1 tions it uses are well1defined! The pro#lem is that we haven't indicated anywhere that the 'eight varia#le should #e in newtons! 0hile we can circumvent this pro#lem #y addin& more machinery into the mi)- it's not worth the effort! This solution- while clever- is little #etter than usin& raw doubles! ,&ain- we'll need to come up with a different approach!
The two approaches listed a#ove have complementary stren&ths and weaknesses! 2efinin& a multitude of types for each com#ination of units allows the compiler to confirm that all operations are well1defined- #ut re5uires us to write an infeasi#le amount of code! 0ritin& a sin&le 9imension;%$e that encapsulates the units allows us to write only a few functions which mana&e the units- #ut offloads unit1checkin& until runtime! 0hat if there was some way to hy#ridize this approach> That is- what if we could use the &eneric unit computations from 9imen, sion;%$e to create an assortment of types in the style of +ilogram; and meter;> There is indeed a way to do this- thanks to the C++ template system! ,s you've seen #efore- we can parameter1 ize classes or functions over varia#le types ( *ust think of ve*tor or ma$! /owever- it is also possi#le to para1 meterize classes and functions over integer !alues) 8or e)ample- the followin& declaration is perfectly le&al=
tem$late t%$ename ;, int /) stru*t arra%; { ; arra%[E]; #;
Cause for a moment to a#sor# what implications inte&er template ar&uments have with re&ards to dimensional analysis! 0e want to create a theoretically un#ounded num#er of types- each correspondin& to some com#ina1 tion of units- with the mathematical operators well1defined over them! Goreover- we have a simple formula for how these types should #e related #ased on what com#ination of units they store! This su&&ests the followin& solution! 0e'll create a sin&le tem#late class called 9imension;%$e that's parameterized over the e)ponents of the kilo&rams- meters- and seconds units! Then- we'll define tem#late implementations of the mathematical oper1 ators that perform all of the re5uisite operations! 7efore movin& on to the actual code- let's 5uickly summarize the advanta&es of this approach=
7ecause the units are hardcoded into the type of each 9imension;%$e varia#le- the compiler can check the units in the computation at compile1time rather than runtime! 7ecause the class and operators are templates- we only need to write a sin&le implementation of the class and operators and the compiler will handle the rest! 0e don't have to worry a#out usin& units outside of some fi)ed #ounds #ecause the compiler will instantiate the template wherever it's re5uired!
Im&lementing the new DimensionType "ow that we've familiarized ourself with why this new approach is effective- let's #e&in writin& the code for it! 0e'll start #y definin& the actual 9imension;%$e class! 0e first write the template header=
tem$late int +gB int mB int s) *lass 9imension;%$e { "2 ... 2" #;
"e)t- we'll write a constructor which accepts a double representin& the 5uantity to store! 0e'll mark the con1 structor e7$li*it so that we can't implicitly convert from doubles to 9imension;%$es! This is primarily to prevent clients of 9imension;%$e from accidentally #ypassin& the unit1checkin& system! 8or instance- if the constructor was not marked e7$li*it- then &iven the followin& function prototype=
1 AA< 1
Since C++ would perform the implicit conversion to a 9imension;%$e 8, 8, ,H)! This means that we've *ust used a scalar value K1A@L where a value in newtons was e)pected! Garkin& the constructor e7$li*it pre1 vents this from accidentally occurrin&! The constructor and the associated data mem#ers are shown here=
tem$late int +g, int m, int s) *lass 9imension;%$e { $ubli*3 e#plicit DimensionType!double amount$ : Guantity!amount$ *4 $rivate3 double Guantity; #;
0e'll also provide an accessor mem#er function getWuantit% so that clients can access the amount of the 5uantity availa#le=
tem$late int +g, int m, int s) *lass 9imension;%$e { $ubli*3 e7$li*it 9imension;%$e(double amount) 3 Vuantit%(amount) {# double getHuantity!$ const * return Guantity; 4 $rivate3 double Vuantit%; #;
This is the final workin& version of the 9imension;%$e class! 't's remarka#ly simple and looks surprisin&ly like the +ilogram; type that we defined earlier in this chapter! Gost of the interestin& work will show up when we #e&in definin& mathematical operators! 'f you'll notice- we haven't defined a copy constructor or assi&nment operator for 9imension;%$e! 'n our caseC++'s default implementation of these functions is perfectly fine for our purposes! Femem#er the Fule of Three= since we don't have a destructor- we pro#a#ly don't need to implement either of the copy functions! Im&lementing the #athematical 2&erators "ow that we have the #ody of the 9imension;%$e class ready and written- let's implement the mathematical operators! 0e'll #e&in #y implementin& o$erator ! and o$erator ,! Fecall that it's only le&al to add or su#tract dimensioned values if the units a&ree! 0e'll therefore define template implementations of o$erator! and o$erator, that accept two 9imension;%$es with the same units and produce new 9imension;%$es ap1 propriately! This is shown here=
1 AB0 1
template .int +gB int mB int s> *onst 9imension;%$e +g, m, s) o$erator! (const DimensionType.+gB mB s>S one, const DimensionType.+gB mB s>S t6o) { return 9imension;%$e +g, m, s)(one.getHuantity!$ 3 t6o.getHuantity!$); #
This code is dense- #ut most of what we've written is type declarations! The overall o$erator! function is tem1 platized over the units in the 9imension;%$e- accepts as input two 9imension;%$es of those particular units K#y reference1to1*onst- of courseL and returns a *onst 9imension;%$e of the same units! The #ody of the function simply returns a 9imension;%$e of the proper units initialized to the sum of the 5uantities of the para1 meters! To #etter see what's &oin& on here- here's the same code- #ut with 9imension;%$e +g, m, s) short1 handed to DT! This is not le&al C++- #ut is illustrative of what's &oin& on=
tem$late int +g, int m, int s) *onst DT o$erator! (*onst DT? one, *onst DT? t'o) { return DT(one.getWuantit%() ! t'o.getWuantit%()); #
7ecause C++ can automatically infer the ar&uments to template functions- we will never need to e)plicitly write out the types of the ar&uments to o$erator!! 8or e)ample- here's some code for addin& several different masses=
9imension;%$e 8, 0, 0) mass8(K.0); 9imension;%$e 8, 0, 0) massH(80.0); 9imension;%$e 8, 0, 0) massF = mass7 3 mass8; "" 4alls operator 3 .7B -B ->
"ow- what happens if we try to add dimensioned types with different units> 8or e)ample- what will happen if we compile the followin& code>
9imension;%$e 8, 0, ,8) rate(K.0); "" K +g"s 9imension;%$e 0, 8, ,8) velo*it%(80.0); "" 80 m"s 9imension;%$e 8, 0, ,8) undefined = rate 3 )elocity;
8ortunately- this code doesn't compile and we'll &et an error directin& us to the spot where we tried to add the rate and velo*it% varia#les! 7ecause the ! operator is declared such that #oth the ar&uments must have the same type- if we try addin& 9imension;%$es of different units- C++ won't #e a#le to find an implementation of o$erator! and will raise a compiler error! Goreover- this error e)ists independently of the fact that we tried to assi&n the result to a varia#le of type 9imension;%$e 8, 0, ,8)! Simply tryin& to add the two varia#les is enou&h to cause the compiler to issue an error! 0e're movin& toward a system for automatically checkin& unitsU ,ddition and su#traction of 9imension;%$es are almost identical! /ere's an implementation of o$erator,where the only chan&e from o$erator ! is that the function #ody performs a su#traction rather than an addi1 tion=
tem$late int +g, int m, int s) *onst 9imension;%$e +g, m, s) operator- (*onst 9imension;%$e +g, m, s)? one, *onst 9imension;%$e +g, m, s)? t'o) { return 9imension;%$e +g, m, s)(one.getHuantity!$ - t6o.getHuantity!$); #
1 AB1 1
"ow- let's move on to multiplication and division of 9imension;%$es! Fecall that any two dimensioned types can #e multiplied Kor dividedL- and the resultin& type has units e5ual to the sum Kor differenceL of the dimensions of the input values! 0hen implementin& o$erator2 or o$erator"- we'll need to templatize the function over the units of #oth the 9imension;%$e ar&uments! This- unfortunately- means that the function will #e templatized over si7 di""erent !alues= three inte&ers for the units of the first parameter and three inte&ers for the units of the second parameter! 8ortunately- thanks to template function ar&ument inference- we'll never need to e)plicitly write out these ar&u1 ments as a client! ,s an implementer- thou&h- we'll have to #e a #it pedantic! To define o$erator2- we'll take in two parameters ( a 9imension;%$e +g8, m8, s8) and a 9imension, ;%$e +gH, mH, sH) and return a 9imension;%$e +g8 ! +gH, m8 ! mH, s8 ! sH) with the appropri1 ate 5uantity! "ote that we've named the template ar&uments +g8 and +gH- etc!- so that the two 9imension;%$e ar&uments can #e of any units! Goreover- notice that the return type is templatized over an arithmetic e)pression of the template ar&uments! This is perfectly le&al C++ and is one of the more powerful features of the template system! 3iven the a#ove description- the implementation of o$erator2 is shown here=
tem$late int +g7B int m7B int s7B int +g8B int m8B int s8) const DimensionType.+g7 3 +g8B m7 3 m8B s7 3 s8> o$erator2 (const DimensionType.+g7B m7B s7>S oneB const DimensionType.+g8B m8B s8>S t6o) { return 9imension;%$e +g7 3 +g8B m7 3 m8B s7 3 s8) (one.getWuantit%() 2 t'o.getWuantit%()); #
0ow ( that's 5uite a function si&natureU ,&ain for simplicity- if we let DT1 stand for the type of the first ar&u1 ment- DT2 stand for the type of the second- and DT3 stand for the return type- this reduces to the much simpler
"2 Again, this is not legal 4!! *ode but is illustrative of 'hatAs going on3 2" tem$late int +g8, int m8, int s8, int +gH, int mH, int sH) *onst DT3 o$erator2 (*onst DT1? one, *onst DT2 t'o) { return DT3(one.getWuantit%() 2 t'o.getWuantit%()); #
The implementation of o$erator" is similar to that of o$erator2- e)cept that we su#tract units instead of addin& them and divide the 5uantities instead of multiplyin& them=
tem$late int +g8, int m8, int s8, int +gH, int mH, int sH) *onst 9imension;%$e +g7 - +g8B m7 - m8B s7 - s8) o$erator" (*onst 9imension;%$e +g8, m8, s8)? one, *onst 9imension;%$e +gH, mH, sH)? t'o) { return 9imension;%$e +g7 - +g8B m7 - m8B s7 - s8) (one.getWuantit%() " t'o.getWuantit%()); #
"ow that we've defined all four of the #asic mathematical operators- we can write code usin& 9imension;%$e! C++'s a#ility to automatically infer template ar&uments is astoundin& here and the compiler will automatically fi&ure out the types of all of the e)pressions in this code and will notice that there is no unit error=
1 AB; 1
"2 <h%si*s Vuestion3 1e have a K+g blo*+ sitting on Earth 'here gravit% a**elerates 2 ob>e*ts at L.Im"sH. 1e 'ish to move the blo*+ verti*all% at a *onstant rate of 2 80m"s. /o' mu*h $o'er, in 'atts (+g_mH"sF) does this reVuireS 2 2 ;he ans'er is the $rodu*t of the 'eight of the blo*+ and the velo*it% at 'hi*h 2 'eAre lifting. (n *ode3 2" 9imension;%$e 9imension;%$e 9imension;%$e 9imension;%$e 8, 0, 0, 8, 0, 8, 8, H, 0) ,H) ,8) ,F) mass(K); "" K+g gravit%(L.I); "" L.Im"sH velo*it%(80); "" 80m"s $o'er = mass R gra)ity R )elocity;
'f this doesn't strike you as remarka#le- pause for a minute and think a#out what's &oin& on here! 'f we chan&e even the sli&htest detail of this code- the compiler will reco&nize the unit error and &ive us an error! 8or e)1 ample- suppose that we accidentally use kilo&rams per second instead of meters per second for the velocity at which we want to raise the #lock! Then we'd &et an error tryin& to assi&n mass 2 gravit% 2 velo*it% to $o'er since the type of mass 2 gravit% 2 $o'er would #e 9imension;%$e H, 8, ,F) instead of the e)pected 9imension;%$e 8, H, ,F)! Similarly- if we accidentally divided #y the velocity- we'd &et a result of type 9imension;%$e 8, 0, ,8)- which couldn't #e stored in the $o'er varia#le!
typede to the 1escue
The a#ove system works remarka#ly well- #ut is a #it clunky at times! 8or e)ample- to work with varia#les in units of kilo&rams- we have to use a 9imension;%$e 8, 0, 0) and to work with newtons must type out 9imension;%$e 8, 8, ,H)! 0e can &reatly simplify use of the 9imension;%$e class #y usin& t%$edef to create shorthand names for the lon&er types! 8or e)ample- if we do the followin&=
t%$edef 9imension;%$e 8, 0, 0) +ilogramT;
0e can then use +ilogram; to refer to a 5uantity in kilo&rams! 0e can do a similar trick for varia#les in new1 tons! 'n fact- we mi&ht want to use all of the followin& t%$edefs to simplify our work=
t%$edef t%$edef t%$edef t%$edef t%$edef t%$edef t%$edef 9imension;%$e 9imension;%$e 9imension;%$e 9imension;%$e 9imension;%$e 9imension;%$e 9imesnion;%$e 8, 0, 0, 8, 8, 8, 0, 0, 8, 0, 8, H, H, 0, 0) 0) 8) ,H) ,H) ,F) 0) +ilogramT; meterT; secondT; ne6tonT; \ouleT; 6attT; scalarT;
Darlier in this chapter we had to define all of these classes manually- #ut thanks to templates the compiler can now automatically &enerate them for us! 'sn't that simpler>
1 ABA 1
The 9imension;%$e class we've constructed here is marvelously useful and effectively solves the pro#lem we set out to solve! /owever- it is far from complete and there are many other operations we mi&ht want to define on 9imension;%$e! These are left as an e)ercise to the reader= 1! Com&ound assignment o&erators! The current implementation of 9imension;%$e does not have sup1 port for !=- ,=- 2=- or "=! ,dd these operators to 9imension;%$e- makin& sure that the operation is always well1defined! 8or e)ample- does it make sense to write mass "= other:ass> /ow a#out mass 2= 8K> ;! Scalar o&erations! 't is always le&al to multiply a dimensioned type #y a scalar- as in 10X?k&! 0ith the current implementation of 9imension;%$e- it is ille&al to write code such as 80 2 mass since o$er, ator2 re5uires #oth parameters to #e 9imension;%$es and 80 is not a 9imension;%$e! 'mplement versions of o$erator2 and o$erator" such that it is le&al to multiply or divide 9imension;%$es #y scalars! A! Stream insertion o&erators! 'mplement a stream insertion operator for 9imension;%$e that prints out the 5uantity and a human1reada#le representation of the units! 8or e)ample- printin& a 9imension, ;%$e 8, 0, 0) with value 1A@ mi&ht print 8FG+g! B! Tem&late Iuantity ty&es! The 9imension;%$e class we implemented in this e)ample always stores its 5uantities as doubles! /owever- we may want to store 5uantities in other forms- such as comple) or rational num#ers! Godify the a#ove code so that 9imension;%$e is parameterized over an additional ar&ument representin& what type to use as a 5uantity! ?! Ad7anced unit checking! The 9imension;%$e we've developed uses metric units for all of its values#ut some pro&rams mi&ht want to use imperial units Kpounds- feet- etc!L instead! Gi)in& and matchin& these units without multiplin& #y the correct conversion factor can result in spectacular failures! Hne prominent e)ample was the failed Gars Climate Hr#iter mission- which was supposed to enter Gartian or#it to study the planet's atmosphere! Unfortunately- the mission failed- and the official report con1 cluded that a mismatch #etween metric and imperial units was to #lame= O'Pt was discovered that the small forces ]Tgs reported #y the spacecraft en&ineers for use in or#it determination solutions was low #y a factor of B!B? K1 pound forceYB!B? "ewtonsL #e1 cause the impulse #it data contained in the ,G2 file was delivered in l#1sec instead of the specified and e)pected units of "ewton1sec! OGCHP 2evise a way of representin& multiple systems of units in a 9imension;%$e and safely convertin& #etween 5uantities e)pressed in two unit systems!
The ST encompasses a wide selection of associative and se5uence containers! /owever- one useful data type that did not find its way into the ST is a multidimensional array class akin to the CS1067IJ Grid! 'n this e)1 tended e)ample- we will implement an ST 1friendly version of the CS1067IJ Grid class- which we'll call grid- that will support ST 1compati#le iterators- intuitive element1access synta)- and relational operators! Hnce we're done- we'll have an industrial1stren&th container class we will use later in this #ook to implement more comple) e)amples! 'mplementin& a fully1functional grid may seem dauntin& at first- #ut fortunately it's easy to #reak the work up into several smaller steps that culminate in a workin& class! Ste& +5 Im&lement the ,asic grid Class. 7efore divin& into some of the grid's more advanced features- we'll #e&in #y implementin& the grid #asics! 7elow is a partial specification of the grid class that provides core functionality= Figure $% Jasic <incom#lete= inter"ace "or the grid class
tem$late t%$ename Elem;%$e) class grid { $ubli*3 "2 4onstru*tors, destru*tors. 2" grid!$; "" 4reate em$t% grid grid!int ro6sB int cols$; "" 4onstru*t to s$e*ified siUe "2 CesiUing o$erations. 2" )oid clear!$; )oid resiLe!int ro6sB int cols$; "2 Wuer% o$erations. 2" int numCo6s!$ const; int num;ols!$ const; bool empty!$ const; int siLe!$ const; "" Em$t% the grid "" CesiUe the grid "" "" "" "" Ceturns Ceturns Ceturns Ceturns number of ro's in the grid number of *olumns in the grid 'hether the grid is em$t% the number of elements
"2 Element a**ess. 2" 2lemTypeS getUt!int ro6B int col$; "" A**ess individual elements const 2lemTypeS getUt!int ro6B int col$ const; "" 5ame, but *onst #;
Constructs a new- empty grid! Constructs a new grid with the specified num#er of rows and columns! Dach element in the grid is initialized to its default value! 'n true ST style- we won't do any error checkin& to see if the dimensions are ne&ative! Fesizes the grid to 0)0!
)oid clear!$;
2iscards the current contents of the grid and resizes the grid to the specified size! Dach element in the grid is initialized to its default value! ,s with the constructor- we won't worry a#out the case where the dimensions are ne&ative! Feturns the num#er of rows and columns in the grid! Feturns whether the grid contains no elements! This is true if either the num#er of rows or columns is zero! Feturns the num#er of total elements in the grid! Feturns a reference to the element at the specified position! This function is *onst,overloaded! 0e won't worry a#out the case where the indices are out of #ounds!
int siLe!$ const; 2lemTypeS getUt!int ro6B int col$; const 2lemTypeS getUt!int ro6B int col$ const;
7ecause grids can #e dynamically resized- we will need to #ack grid with some sort of dynamic memory man1 a&ement! 7ecause the grid represents a two1dimensional entity- you mi&ht think that we need to use a dynam1 ically1allocated multidimensional array to store grid elements! /owever- workin& with dynamically1allocated multidimensional arrays is tricky and &reatly complicates the implementation! 8ortunately- we can sidestep this pro#lem #y implementin& the two1dimensional grid o#*ect usin& a sin&le1dimensional array! To see how this works- consider the followin& A)A &rid= 0 A 6 1 B @ ; ? E
0e can represent all of the elements in this &rid usin& a one1dimensional array #y layin& out all of the elements se5uentially- as seen here= 0 1 ; A B ? 6 @ E
'f you'll notice- in this orderin&- the three elements of the first row appear in order as the first three elementsthen the three elements of the second row in order- and finally the three elements of the final row in order! 7e1 cause this one1dimensional representation of a two1dimensional o#*ect preserves the orderin& of individual rowsit is sometimes referred to as row-major order! To represent a &rid in row1ma*or order- we need to #e a#le to convert #etween &rid coordinates and array indices! 3iven a coordinate Krow- colL in a &rid of dimensions Knrows- ncolsL- the correspondin& position in the row1ma*or order representation of that &rid is &iven #y inde7 K col + row K ncols! The intuition #ehind this formula is that #ecause the orderin& within any row is preserved- each horizontal step in the &rid translates into a sin&le step for1 ward or #ackward in the row1ma*or order representation of the &rid! /owever- each vertical step in the &rid re1 5uires us to advance forward to the ne)t row in the linearized &rid- skippin& over ncols elements! Usin& row1ma*or order- we can #ack the grid class with a re&ular ST ve*tor- as shown here=
1 AB@ 1
Serendipitously- implementin& the grid with a ve*tor allows us to use C++'s automatically1&enerated copy constructor and assi&nment operator for grid! Since ve*tor already mana&es its own memory- we don't need to handle it manually! "ote that we e)plicitly keep track of the num#er of rows and columns in the grid even thou&h the ve*tor stores the total num#er of elements! This is necessary so that we can compute indices in the row1ma*or orderin& for points in two1dimensional space! The a#ove functions have relatively strai&htforward implementations that are &iven #elow=
tem$late { # t%$ename Elem;%$e) grid33grid() 3 ro6s!-$B cols!-$
tem$late t%$ename Elem;%$e) grid33grid(int ro's, int *ols) 3 elems!ro6s R cols$B ro6s!ro6s$B cols!cols$ { # tem$late t%$ename Elem;%$e) void grid33*lear() { elems.clear!$; # tem$late t%$ename Elem;%$e) void grid33resiUe(int ro's, int *ols) { "2 5ee belo' for assign 2" elems.assign!ro6s R colsB 2lemType!$$; "2 E7$li*it this,) reVuired sin*e $arameters have same name as members. 2" this->ro6s , ro6s; this->cols , cols;
1 ABE 1
tem$late t%$ename Elem;%$e) int grid33numCo's() *onst { return ro6s; # tem$late t%$ename Elem;%$e) int grid33num4ols() *onst { return cols; # tem$late t%$ename Elem;%$e) bool grid33em$t%() *onst { return siLe!$ ,, -; # tem$late t%$ename Elem;%$e) int grid33siUe() *onst { return ro6s R cols; # "2 @se ro',ma>or ordering to tem$late t%$ename Elem;%$e) { return elems9col 3 ro6 R # tem$late t%$ename Elem;%$e) { return elems9col 3 ro6 R #
a**ess the $ro$er element of the ve*tor. 2" Elem;%$e? grid33getAt(int ro', int *ol) cols:; *onst Elem;%$e? grid33getAt(int ro', int *ol) *onst cols:;
Gost of these functions are one1liners and are e)plained in the comments! The only function that you may find interestin& is resiUe- which uses the ve*tor's assign mem#er function! assign is similar to resiUe in that it chan&es the size of the ve*tor- #ut unlike resiUe assign discards all of the current ve*tor contents and replaces them with the specified num#er of copies of the specified element! The use of Elem;%$e() as the second parameter to assign means that we will fill the ve*tor with copies of the default value of the type #e1 in& stored Ksince Elem;%$e() uses the temporary o#*ect synta) to create a new Elem;%$eL! Ste& 15 Add Su&&ort for Iterators "ow that we have the #asics of a grid class- it's time to add iterator support! This will allow us to plu& the grid directly into the ST al&orithms and will #e invalua#le in a later chapter! ike the ma$ and set- the grid does not naturally lend itself to a linear traversal ( after all- grid is two1dimen1 sional ( and so we must ar#itrarily choose an order in which to visit elements! Since we've implemented the grid in row1ma*or order- we'll have grid iterators traverse the &rid row1#y1row- top to #ottom- from left to ri&ht! Thus- &iven a A)B grid- the order of the traversal would #e 0 A 6 < 1 B @ 10 ; ? E 11
This order of iteration maps naturally onto the row1ma*or orderin& we've chosen for the grid! 'f we consider how the a#ove &rid would #e laid out in row1ma*or order- the resultin& array would look like this=
1 AB< 1 11
Thus this iteration scheme maps to a simple linear traversal of the underlyin& representation of the grid! 7e1 cause we've chosen to represent the elements of the grid usin& a ve*tor- we can iterate over the elements of the grid usin& ve*tor iterators! 0e thus add the followin& definitions to the grid class=
tem$late t%$ename Elem;%$e) *lass grid { $ubli*3 grid(); grid(int ro's, int *ols); void *lear(); void resiUe(int ro's, int *ols); int numCo's() *onst; int num4ols() *onst; bool em$t%() *onst; int siUe() *onst; Elem;%$e? getAt(int ro', int *ol); *onst Elem;%$e? getAt(int ro', int *ol) *onst; typede typename )ector.2lemType>::iterator iterator; typede typename )ector.2lemType>::const&iterator const&iterator; $rivate3 ve*tor Elem;%$e) elems; int ro's; int *ols; #;
"ow- clients of grid can create grid int)33iterators rather than ve*tor int)33iterators! This makes the interface more intuitive and increases encapsulationR since iterator is a t%$edef- if we later decide to replace the underlyin& representation with a dynamically1allocated array- we can chan&e the t%$edefs to
t%$edef 2lemTypeR iterator; t%$edef const 2lemTypeR *onstBiterator;
,nd clients of the grid will not notice any difference! "otice that in the a#ove t%$edefs we had to use the t%$ename keyword to name the type ve*tor Elem, ;%$e)33iterator! This is the pesky ed&e case mentioned in the chapter on templates and somehow mana&es to creep into more than its fair share of code! Since iterator is a nested type inside the template type ve*, tor Elem;%$e)- we have to use the t%$ename keyword to indicate that iterator is the name of a type rather than a class constant! 0e've now defined an iterator type for our grid- so what functions should we e)port to the grid clients> 0e'll at least want to provide support for begin and end- as shown here=
1 A?0 1
tem$late t%$ename Elem;%$e) *lass grid { $ubli*3 grid(); grid(int ro's, int *ols); void *lear(); void resiUe(int ro's, int *ols); int numCo's() *onst; int num4ols() *onst; bool em$t%() *onst; int siUe() *onst; Elem;%$e? getAt(int ro', int *ol); *onst Elem;%$e? getAt(int ro', int *ol) *onst;
t%$edef t%$ename ve*tor Elem;%$e)33iterator iterator; t%$edef t%$ename ve*tor Elem;%$e)33*onstBiterator *onstBiterator; iterator begin!$; const&iterator begin!$ const; iterator end!$; const&iterator end!$ const; $rivate3 ve*tor Elem;%$e) elems; int ro's; int *ols; #;
0e've provided two versions of each function so that clients of a *onst grid can still use iterators! These func1 tions are easily implemented #y returnin& the value of the underlyin& ve*tor's begin and end functions- as shown here=
tem$late t%$ename Elem;%$e) typename grid.2lemType>::iterator grid Elem;%$e)33begin() { return elems.begin!$; #
"otice that the return type of this function is t%$ename grid Elem;%$e)33iterator rather than *ust iter, ator! 7ecause iterator is a nested type inside grid- we need to use grid Elem;%$e)33iterator to spe1 cify which iterator we want- and since grid is a template type we have to use the t%$ename keyword to indic1 ate that iterator is a nested type! Htherwise- this function should #e strai&htforward! The rest of the functions are implemented here=
tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33*onstBiterator grid Elem;%$e)33begin() *onst { return elems.begin!$; # tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33iterator grid Elem;%$e)33end() { return elems.end!$; #
1 A?1 1
7ecause the grid is implemented in row1ma*or order- elements of a sin&le row occupy consecutive locations in the ve*tor! 't's therefore possi#le to return iterators delineatin& the start and end of each row in the grid! This is useful functionality- so we'll provide it to clients of the grid throu&h a pair of mem#er functions ro'Bbegin and ro'Bend Kplus *onst overloadsL! These functions are declared here=
tem$late t%$ename Elem;%$e) *lass grid { $ubli*3 grid(); grid(int ro's, int *ols); void *lear(); void resiUe(int ro's, int *ols); int numCo's() *onst; int num4ols() *onst; bool em$t%() *onst; int siUe() *onst; Elem;%$e? getAt(int ro', int *ol); *onst Elem;%$e? getAt(int ro', int *ol) *onst; t%$edef t%$ename ve*tor Elem;%$e)33iterator iterator; t%$edef t%$ename ve*tor Elem;%$e)33*onstBiterator *onstBiterator; iterator *onstBiterator iterator *onstBiterator iterator const&iterator iterator const&iterator begin(); begin() *onst; end(); end() *onst; ro6&begin!int ro6$; ro6&begin!int ro6$ const; ro6&end!int ro6$; ro6&end!int ro6$ const;
7efore implementin& these functions- let's take a minute to fi&ure out e)actly where the iterations we return should point to! Fecall that the element at position K row- 0L in a &rid of size Krows- colsL can #e found at position row Q cols! 0e should therefore have ro'Bbegin(ro') return an iterator to the ro' Q cols element of the ve*tor! Since there are cols elements in each row and ro'Bend should return an iterator to one position past the end of the row- this function should return an iterator to the position cols past the location returned #y ro'Bbegin! 3iven this information- we can implement these functions as shown here=
1 A?; 1
tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33iterator grid Elem;%$e)33ro'Bbegin(int ro') { return begin!$ 3 cols R ro6; # tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33*onstBiterator grid Elem;%$e)33ro'Bbegin(int ro') *onst { return begin!$ 3 cols R ro6; # tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33iterator grid Elem;%$e)33ro'Bend(int ro') { return ro6&begin!ro6$ 3 cols; # tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33*onstBiterator grid Elem;%$e)33ro'Bbegin(int ro') *onst { return ro6&begin!ro6$ 3 cols; #
0e now have an ele&ant iterator interface for the grid class! 0e can iterate over the entire container as a whole- *ust one row at a time- or some com#ination thereof! This ena#les us to interface the grid with the ST al&orithms! 8or e)ample- to zero out a grid int)- we can use the fill al&orithm- as shown here=
fill(myFrid.begin!$, myFrid.end!$, 0);
0ith only a handful of functions we're now capa#le of plu&&in& directly into the full power of the al&orithms! This is part of the #eauty of the ST ( had the al&orithms #een desi&ned to work on containers rather than iterat1 or ran&es- this would not have #een possi#le! Ste& 5 Add Su&&ort for the $lement Selection 2&erator 0hen usin& re&ular C++ multidimensional arrays- we can write code that looks like this=
int m%Arra%[8FG][JH]; m%Arra%[H][J] = HG8IHI; m%Arra%[L][0] = F8J8KL;
/owever- with the current specification of the grid class- the a#ove code would #e ille&al if we replaced the multidimensional array with a grid int)- since we haven't provided an implementation of o$erator []! ,ddin& support for element selection to linear classes like the ve*tor is simple ( we simply have the #rackets operator return a reference to the proper element! Unfortunately- it is much trickier to desi&n grid such that the #racket synta) works correctly! The reason is that if we write code that looks like this=
grid int) m%Grid(8FG, JH); int value = m%Grid[H][J];
Cha#ter 13% E7tended E7am#le% grid 7y replacin& the #racket synta) with calls to o$erator []- we see that this code e)pands out to
grid int) m%Grid(8FG, JH); int value = !myFrid.operator9: !8$$.operator9: !J$;
1 A?A 1
/ere- there are two calls to o$erator []- one invoked on m%Grid and the other on the value returned #y m%, Grid.o$erator[](H)! To make the a#ove code compile- the o#*ect returned #y the grid's o$erator[] must itsel" define an o$erator [] function! 't is this returned o#*ect- rather than the grid itself- which is re1 sponsi#le for retrievin& the re5uested element from the grid! Since this temporary o#*ect is used to perform a task normally reserved for the grid- it is sometimes known as a #ro7y object! /ow can we implement the grid's o$erator [] so that it works as descri#ed a#ove> 8irst- we will need to define a new class representin& the o#*ect returned #y the grid's o$erator []! 'n this discussion- we'll call it :utableCeferen*e- since it represents an o#*ect that can call #ack into the grid and mutate it! 8or simplicity and to ma)imize encapsulation- we'll define :utableCeferen*e inside of grid! This results in the followin& interface for grid=
tem$late t%$ename Elem;%$e) *lass grid { $ubli*3 "2 ... $reviousl%,defined fun*tions ... 2" class 0utableCe erence * public: riend class grid; 2lemTypeS operator9: !int col$; pri)ate: 0utableCe erence!gridR o6nerB int ro6$; gridR const o6ner; const int ro6;
4;
The :utableCeferen*e o#*ect stores some a pointer to the grid that created it- alon& with the inde) passed in to the grid's o$erator [] function when the :utableCeferen*e was created! That way- when we invoke the :utableCeferen*e's o$erator [] function specifyin& the col coordinate of the &rid- we can pair it with the stored row coordinate- then 5uery the grid for the element at Krow- colL! 0e have also made grid a friend of :utableCeferen*e so that the grid can call the private constructor necessary to initialize a :uta, bleCeferen*e! 0e can implement :utableCeferen*e as follows=
1 A?B 1
tem$late t%$ename Elem;%$e) grid Elem;%$e)33:utableCeferen*e33:utableCeferen*e(grid2 o'ner, int ro') 3 o6ner!o6ner$B ro6!ro6$ { # tem$late t%$ename Elem;%$e) Elem;%$e? grid Elem;%$e)33:utableCeferen*e33o$erator[] (int *ol) { return o6ner->getUt!ro6B col$; #
"otice that #ecause :utableCeferen*e is a nested class inside grid- the implementation of the :uta, bleCeferen*e functions is prefaced with grid Elem;%$e)33:utableCeferen*e instead of *ust :uta, bleCeferen*e! /owever- in this particular case the pesky t%$ename keyword is not necessary #ecause we are prototypin& a function inside :utableCeferen*e rather than usin& the type :utableCeferen*e in an e)1 pression! "ow that we've implemented :utableCeferen*e- we'll define an o$erator [] function for the grid class that constructs and returns a properly1initialized :utableCeferen*e! This function accepts an row coordinateand returns a :utableCeferen*e storin& that row num#er and a pointer #ack to the grid! That way- if we write
int value = m%Grid[8][H];
The followin& se5uences of actions occurs= 1! m%Grid.o$erator[] is invoked with the parameter 1! ;! m%Grid.o$erator[] creates a :utableCeferen*e storin& the row coordinate 1 and a means for communicatin& #ack with the m%Grid o#*ect! A! m%Grid.o$erator[] returns this :utableCeferen*e! B! The returned :utableCeferen*e then has its o$erator[] function called with parameter ;! ?! The returned :utableCeferen*e then calls #ack to the m%Grid o#*ect and asks for the element at pos1 ition K1- ;L! This se5uence of actions is admittedly comple)- #ut is transparent to the client of the grid class and runs effi1 ciently!
o$erator[] is defined and implemented as follows=
1 A?? 1
"otice that we've only provided an implementation of the non1*onst version of o$erator[]! 7ut what if we want to use o$erator[] on a *onst grid> 0e would similarly need to return a pro)y o#*ect- #ut that o#*ect would need to &uarantee that grid clients could not write code like this=
*onst grid int) m%Grid(8FG, JH); m%Grid[0][0] = HG8I; "" =oo$s0 :odified *onst ob>e*t0
To prevent this sort of pro#lem- we'll have the *onst version of o$erator[] return a pro)y o#*ect of a differ1 ent type- called (mmutableCeferen*e which #ehaves similarly to :utableCeferen*e #ut which returns *onst references to the elements in the grid! This results in the followin& interface for grid=
1 A?6 1
tem$late t%$ename Elem;%$e) *lass grid { $ubli*3 "2 ... $reviousl%,defined fun*tions ... 2" *lass :utableCeferen*e { $ubli*3 friend *lass grid; Elem;%$e? o$erator[] (int *ol); $rivate3 :utableCeferen*e(grid2 o'ner, int ro'); grid2 *onst o'ner; *onst int ro'; #; :utableCeferen*e o$erator[] (int ro'); class ImmutableCe erence * public: riend class grid; const 2lemTypeS operator9: !int col$ const; pri)ate: 0utableCe erence!const gridR o6nerB int ro6$; const gridR const o6ner; const int ro6;
4; ImmutableCe erence operator9: !int ro6$ const; $rivate3 ve*tor Elem;%$e) elems; int ro's; int *ols; #; (mmutableCeferen*e and the *onst version of o$erator[] are similar to :utableCeferen*e and the non1*onst version of o$erator[]- and to save space we won't write it here! The complete listin& of the grid
class at the end of this chapter contains the implementation if you're interested! Ste& "5 9efine 1elational 2&erators "ow that our grid has full support for iterators and a nice #racket synta) that lets us access individual elementsit's time to put on the finishin& touches! ,s a final step in the pro*ect- we'll provide implementations of the rela1 tional operators for our grid class! 0e #e&in #y updatin& the grid interface to include the followin& functions=
1 A?@ 1
"ote that of the si) operators listed a#ove- only the == and 0= operators have intuitive meanin&s when applied to grids! /owever- it also makes sense to define a operator over grids so that we can store them in ST ma$ and set containers- and to ensure consistency- we should define the other three operators as well! 7ecause there is no natural interpretation for what it means for one grid to #e 4less than6 another- we are free to implement these functions in any way that we see fit- provided that the followin& invariants hold for any two grids one and t'o= 1! one == t'o if and only if the two grids have the same dimensions and each element compares identical #y the == operator! ;! ,ll of the mathematical properties of - =- ==- 0=- )=- and ) hold! 8or e)ample if one t'o- 0(one )= t'o) should #e true and one = t'o should also hold! Similarly- one == one- and if one == t'o- t'o == one! A! 8or any one and t'o- e)actly one of one == t'o- one t'o- or one ) t'o should #e true! ,s mentioned in the section on operator overloadin&- it is possi#le to implement all si) of the relational operators in terms of the less1than operator! Hne strate&y for implementin& the relational operators is thus to implement *ust the less1than operator and then to define the other five as wrapped calls to o$erator ! 7ut what is the #est way to determine whether one grid compares less than another> Hne &eneral approach is to define a le7icogra#hical ordering over grids! 0e will compare each field one at a time- checkin& to see if the fields are e5ual! 'f so- we move on to the ne)t field! Htherwise- we immediately return that one grid is less than another without lookin& at the remainin& fields! 'f we &o throu&h every field and find that the grids are e5ual- then we can return that neither &rid is less than the other! This is similar to the way that we mi&ht order words alpha#et1 ically ( we find the first mismatched character- then return which word compares first! 0e can #e&in #y imple1 mentin& o$erator as follows=
tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator (*onst grid? other) *onst { TR ;ompare the number o ro6s and return immediately i i !ro6s %, other.ro6s$ return ro6s . other.ro6s; TR /e#t compare the number o columns the same 6ay. RT i !cols %, other.cols$ return cols . other.cols; # "2 ... 2"
there@s a mismatch. RT
1 A?E 1
/ere- we compare the ro's fields of the two o#*ects and immediately return if they aren't e5ual! 0e can then check the *ols fields in the same way! 8inally- if the two grids have the same num#er of rows and columnswe need to check how the elements of the grids compare! 8ortunately- this is strai&htforward thanks to the ST le7i*ogra$hi*alB*om$are al&orithm! le7i*ogra$hi*alB*om$are accepts four iterators delineatin& two ran&es- then le)ico&raphically compares the elements in those ran&es and returns if the first ran&e compares le)1 ico&raphically less than the second! Usin& le7i*ogra$hi*alB*om$are- we can finish our implementation of o$erator as follows=
tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator (*onst grid? other) *onst { "2 4om$are the number of ro's and return immediatel% if thereAs a mismat*h. 2" if(ro's 0= other.ro's) return ro's other.ro's; "2 Ee7t *om$are the number of *olumns the same 'a%. 2" if(*ols 0= other.*ols) return *ols other.*ols; return le#icographical&compare!begin!$B end!$B other.begin!$B other.end!$$; #
,ll that's left to do now is to implement the other five relational operators in terms of o$erator done #elow=
tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator )=(*onst grid? other) *onst { return %!Rthis . other$; # tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator ==(*onst grid? other) *onst { return %!Rthis . other$ SS %!other . Rthis$; # tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator 0=(*onst grid? other) *onst { return !Rthis . other$ >> !other . Rthis$; # tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator )(*onst grid? other) *onst { return other . Rthis; # tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator = (*onst grid? other) *onst { return %!other . Rthis$; #
! This is
1 A?< 1
,t this point we're doneU 0e now have a complete workin& implementation of the grid class that supports iter1 ation- element access- and the relational operators! To #oot- it's implemented on top of the ve*tor- meanin& that it's slick and efficient! This class should #e your one1stop solution for applications that re5uire a two1dimen1 sional array! #ore to $%&lore 0hile the grid class we've written is useful in a wide variety of circumstances- there are several e)tra features you may want to consider addin& to grid! /ere are some su&&estions to &et you started= 1! Column iterators= The iterators used in this grid class are useful for iteratin& across rows #ut not columns! Consider definin& a custom class *olumnBiterator that can iterate over grid and columns usin& iterator synta)! Gake sure to provide *onst overloadsU ;! Smart resi3ing= Fi&ht now- the only way to resize a grid is #y discardin& the old contents entirely! 0ouldn't it #e nice if there was a way to resize the grid #y insertin& or deletin& rows and columns in the e)istin& structure> This is not particularly difficult to implement- #ut is a nice feature to add! A! Subgrid functions= $ust as the C++ string e)ports a substr function to &et a su#strin& from that strin&- it mi&ht #e useful to define a function that &ets a 4su#&rid6 out of the grid! B! Tem&lati3ed Con7ersion !unctions= 0hen workin& with two grids of different types that are impli1 citly converti#le to one another Ksay- a grid int) and a grid double)L- it mi&ht #e useful to allow e)pressions like m%9oubleGrid = m%(ntGrid! Consider addin& a templatized conversion construct1 or to the grid class to make these assi&nments le&al! 'f you're interested in some client1side applications of the grid- consider the followin& pro#lems= 1! , magic sAuare is a s5uare array of num#ers such that the sum of every row- column- and dia&onal is the same! 8or e)ample- the followin& A)A &rid is a ma&ic s5uare where each row- column- and dia&onal sums to 1?= B A E < ? 1 ; @ 6
This ?)? &rid is also a ma&ic s5uare where each row- column- and dia&onal sums to 6?=
11 1E ;? B ;A 6 ?
< A
10 1; 1< ;1 @ 1
1A ;0 ;; 1B 16 E 1?
1@ ;B
Usin& the ne7tB$ermutation al&orithm and the grid class- write a function 6istAll:agi*, 5Vuares that accepts as input the size of a ma&ic s5uare- then prints out all ma&ic s5uares of that size!
1 A60 1
;! -ineswee#er is a lo&ic puzzle where the user is presented a two1dimensional &rid containin& a collection of mines! 'nitially every s5uare in the &rid is hidden- and the user may reveal s5uares to determine their contents! 'f the user reveals a mine- she loses the &ame! Htherwise- if the tile does not contain a minethen the tile contains a num#er indicatin& the num#er of ad*acent mines! The user wins if she reveals all tiles that do not contain mines! 0rite an implementation of Ginesweeper usin& the &rid to store the un1 derlyin& #oard! Com&lete grid Listing
tem$late t%$ename Elem;%$e) *lass grid { $ubli*3 "2 4onstru*tors either *reate an em$t% grid or a grid of the 2 s$e*ified siUe. 2" grid(); grid(int numCo's, int num4ols); "2 CesiUing fun*tions. 2" void *lear(); void resiUe(int 'idth, int height); "2 5iUe Vueries. 2" int numCo's() *onst; int num4ols() *onst; bool em$t%() *onst; int siUe() *onst; "2 Element a**ess. 2" Elem;%$e? getAt(int ro', int *ol); *onst Elem;%$e? getAt(int ro', int *ol) *onst; "2 (terator definitions. 2" t%$edef t%$ename ve*tor Elem;%$e)33iterator iterator; t%$edef t%$ename ve*tor Elem;%$e)33*onstBiterator *onstBiterator; "2 4ontainer iteration. 2" iterator begin(); iterator end(); *onstBiterator begin() *onst; *onstBiterator end() *onst; "2 Co' iteration. 2" iterator ro'Bbegin(int ro'); iterator ro'Bend(int ro'); *onstBiterator ro'Bbegin(int ro') *onst; *onstBiterator ro'Bend(int ro') *onst;
1 A61 1
"2 4onstru*ts a ne', em$t% grid. 2" tem$late t%$ename Elem;%$e) grid Elem;%$e)33grid() 3 ro's(0), *ols(0) { # "2 4onstru*ts a grid of the s$e*ified siUe. 2" tem$late t%$ename Elem;%$e) grid Elem;%$e)33grid(int ro's, int *ols) 3 elems(ro's 2 *ols), ro's(ro's), *ols(*ols) { # "2 Em$ties the grid. 2" tem$late t%$ename Elem;%$e) void grid Elem;%$e)33*lear() { elems.*lear(); #
1 A6; 1
"2 9is*ards all e7isting *ontent and redimensions the grid. 2" tem$late t%$ename Elem;%$e) void grid Elem;%$e)33resiUe(int ro's, int *ols) { elems.assign(ro's 2 *ols, Elem;%$e()); this,)ro's = ro's; this,)*ols = *ols; # "2 5iUe Vuer% fun*tions. 2" tem$late t%$ename Elem;%$e) int grid Elem;%$e)33numCo's() *onst { return ro's; # tem$late t%$ename Elem;%$e) int grid Elem;%$e)33num4ols() *onst { return *ols; # "2 ;he grid is em$t% if the siUe is Uero. 2" tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33em$t%() *onst { return siUe() == 0; # "2 ;he siUe of the grid is the $rodu*t of the number of ro's and *olumns. 2" tem$late t%$ename Elem;%$e) int grid Elem;%$e)33siUe() *onst { return ro's 2 *ols; # "2 A**essor member fun*tions use the ro',ma>or order inde7 *onversions to a**ess 2 elements. 2" tem$late t%$ename Elem;%$e) Elem;%$e? grid Elem;%$e)33getAt(int ro', int *ol) { return elems[*ol ! ro' 2 *ols]; # tem$late t%$ename Elem;%$e) *onst Elem;%$e? grid Elem;%$e)33getAt(int ro', int *ol) *onst { return elems[*ol ! ro' 2 *ols]; # "2 (terator su$$ort, in*luding *onst overloads. 2" tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33iterator grid Elem;%$e)33begin() { return elems.begin(); # tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33*onstBiterator grid Elem;%$e)33begin() *onst { return elems.begin(); # tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33iterator grid Elem;%$e)33end() { return elems.end(); #
1 A6A 1
"2 Co' iteration uses the ro',ma>or order formula to sele*t the $ro$er elements. 2" tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33iterator grid Elem;%$e)33ro'Bbegin(int ro') { return elems.begin() ! ro' 2 *ols; # tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33*onstBiterator grid Elem;%$e)33ro'Bbegin(int ro') *onst { return elems.begin() ! ro' 2 *ols; # tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33iterator grid Elem;%$e)33ro'Bend(int ro') { return ro'Bbegin(ro') ! *ols; # tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33*onstBiterator grid Elem;%$e)33ro'Bend(int ro') *onst { return ro'Bbegin(ro') ! *ols; # "2 (m$lementation of the :utableCeferen*e and (mmutableCeferen*e *lasses. 2" tem$late t%$ename Elem;%$e) grid Elem;%$e)33:utableCeferen*e33:utableCeferen*e(grid2 sour*e, int ro') 3 ref(sour*e), ro'(ro') { # tem$late t%$ename Elem;%$e) grid Elem;%$e)33(mmutableCeferen*e33(mmutableCeferen*e(*onst grid2 sour*e, int ro') 3 ref(sour*e), ro'(ro') { # "2 o$erator[] *alls ba*+ into the original grid. 2" tem$late t%$ename Elem;%$e) Elem;%$e? grid Elem;%$e)33:utableCeferen*e33o$erator [](int *ol) { return ref,)getAt(ro', *ol); # tem$late t%$ename Elem;%$e) *onst Elem;%$e? grid Elem;%$e)33(mmutableCeferen*e33o$erator [](int *ol) *onst { return ref,)getAt(ro', *ol); # "2 o$erator[] im$lementations *reate and return ((m):utableCeferen*es. 2" tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33:utableCeferen*e grid Elem;%$e)33o$erator[](int ro') { return :utableCeferen*e(this, ro'); #
1 A6B 1
tem$late t%$ename Elem;%$e) t%$ename grid Elem;%$e)33(mmutableCeferen*e grid Elem;%$e)33o$erator[](int ro') *onst { return (mmutableCeferen*e(this, ro'); #
"2 o$erator $erforms a le7i*ogra$hi*al *om$arison of t'o grids. 2" tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator (*onst grid? other) *onst { "2 6e7i*ogra$hi*all% *om$are b% dimensions. 2" if(ro's 0= other.ro's) return ro's other.ro's; if(*ols 0= other.*ols) return *ols other.*ols; "2 <erform a le7i*ogra$hi*al *om$arison. 2" return le7i*ogra$hi*alB*om$are(begin(), end(), other.begin(), other.end());
"2 =ther relational o$erators defined in terms of o$erator . 2" tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator )=(*onst grid? other) *onst { return 0(2this other); # tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator ==(*onst grid? other) *onst { return 0(2this other) ?? 0(other 2this); # tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator 0=(*onst grid? other) *onst { return (2this other) NN (other 2this); # tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator )(*onst grid? other) *onst { return other 2this; # tem$late t%$ename Elem;%$e) bool grid Elem;%$e)33o$erator = (*onst grid? other) *onst { return 0(other 2this); #
Cha&ter G5 !unctors
_________________________________________________________________________________________________________
Consider a simple task! Suppose you have a ve*tor string) you'd like to count the num#er of strings that have len&th less than five! .ou stum#le upon the ST *ountBif al&orithm- which accepts a ran&e of iterators and a predicate function- then returns the num#er of elements in the ran&e for which the function returns true! Since we want to count the num#er of strin&s with len&th less than five- we could write a function like this one=
bool 6ength(s6ess;han&ive(*onst string? str) { return str.length() K; #
,nd then call *ountBif(m%De*tor.begin(), m%De*tor.end(), 6ength(s6ess;han&ive) to &et the num#er of short elements! Similarly- if you wanted to count the num#er of strin&s with len&th less than ten- we could write a 6ength(s6ess;han;en function- and so on and so forth! The a#ove approach is le&al C++- #ut is not particularly ele&ant! 'nstead of writin& multiple functions to com1 pare strin& len&ths a&ainst constants- &ood pro&rammin& practice su&&ests that we should *ust write one function that looks like this=
bool 6ength(s6ess;han(*onst string? strB int length) { return str.length!$ . length; #
This way- we can specify the ma)imum len&th as the second parameter! 0hile this new function is more &eneric than the previous version- unfortunately we can't use it in con*unction with *ountBif! *ountBif re5uires a unary function Ka function takin& only one ar&umentL as its final para1 meter- and the new 6ength(s6ess;han is a binary function! This new function- while more &enerally applic1 a#le than the ori&inal version- is actually less useful! There must #e some way to compromise #etween the two approaches! 0e need a way to construct a function that takes in only one parameter Kthe strin& to testL- #ut which can #e customized to accept an ar#itrary ma)imum len&th! /ow can we do this> This pro#lem #oils down to a 5uestion of data flow! To construct this hy#rid function- we need to somehow communicate the upper #ound into the function so that it can perform the comparison! "ow- a raw C++ function can access the followin& information= 'ts local varia#les! 'ts parameters! 3lo#al varia#les!
's there some way that we can store the ma)imum len&th of the strin& in one of these locations> 0e can't store it in a local varia#le- since local varia#les don't persist #etween function calls! ,s mentioned a#ove- we can't store it in a parameter- since *ountBif is hardcoded to accept a unary function! That leaves &lo#al varia#les! 0e could solve this pro#lem usin& &lo#al varia#les= we store the ma)imum len&th in a &lo#al varia#le- then compare the strin& parameter len&th a&ainst the &lo#al! 8or e)ample=
1 A66 1
int g0a#1ength; "" Dalue to *om$are against bool 6ength(s6ess;han(*onst string? str) { return str.length!$ . g0a#1ength; #
This approach works= if our ve*tor string) is called v- then we can count the num#er of elements less than some value #y writin&
g:a76ength = "2 ... some value ... 2" int num5hort = *ountBif(v.begin(), v.end(), 6ength(s6ess;han);
7ut *ust #ecause this approach works does not mean that it is optimal! This approach is deeply flawed for sever1 al reasons- a handful of which are listed here=
It is errorC&rone! 7efore we use 6ength(s6ess;han- we need to set g:a76ength to the correct value! 'f we for&et- 6ength(s6ess;han will use the wron& value and we will &et the wron& answer! Goreover- the compiler will not dia&nose this sort of mistake! It is not scalable! 'f this pro#lem arises a&ain Ki!e! we find another case where we need a unary function with access to more dataL- we need to introduce even more &lo#al varia#les! This leads to names#ace #ollution- where too many varia#les are in scope and it is easy to accidentally use one when another is e)pected! It uses global 7ariables! ,ny use of &lo#al varia#les should send a shiver runnin& down your spine! 3lo#al varia#les should #e avoided at all costs- and the fact that we're usin& them here su&&ests that somethin& is wron& with this setup!
"one of the options we've considered are feasi#le or attractive! There has to #e a #etter way to solve this- #ut how> !unctors to the 1escue The pro#lem is that a raw C++ function doesn't have access to enou&h information to return the correct answer! To solve this pro#lem- we'll turn to a more powerful C++ entity= a "unctor! , functor Kor "unction objectL is an C++ class that acts like a function! 8unctors can #e called usin& the familiar function call synta)- and can yield values and accept parameters *ust like re&ular functions! 8or e)ample- suppose we create a functor class called :%4lass imitatin& a function acceptin& an int and returnin& a double! Then we could 4call6 an o#*ect of type :%4lass as follows=
:%4lass m%&un*tor; *out myFunctor!7DE$ endl; "" .4all. m%&un*tor 'ith $arameter 8FG
"otice that the synta) for callin& a functor is the same for callin& a function! This is no accident and is part of the appeal of functors! To create a functor- we create an o#*ect that overloads the function call operator- o$erator ()! The name of this function is a #it misleadin& ( it is a function called o$erator ()- not a function called o$erator that takes no parameters! 2espite the fact that the name looks like 4operator parentheses-6 we're not redefinin& what it means to parenthesize the o#*ect! 'nstead- we're definin& a function that &ets called if we invoke the o#*ect like a function! Thus in the a#ove code*out m%&un*tor(8FG) endl;
is e5uivalent to
1 A6@ 1
Unlike other operators we've seen so far- when overloadin& the function call operator- you're free to return an o#1 *ect of any type Kor even voidL and can accept any num#er of parameters! 8or e)ample- here's a sample functor that overloads the function call operator to print out a strin&=
*lass :%&un*tor { $ubli*3 )oid operator!$ !const stringS str$ const * cout .. str .. endl; 4 #;
"ote that in the function definition there are two sets of parentheses! The first &roup is for the function name ( o$erator () ( and the second for the parameters to o$erator ()! 'f we separated the implementation of o$erator () from the class definition- it would look like this=
*lass :%&un*tor { $ubli*3 )oid operator!$ !const stringS str$ const; #; )oid 0yFunctor::operator!$ !const stringS str$ const * cout .. str .. endl; 4
This code calls the functor and prints out 48unctor powerU6 ,t this point functors mi&ht seem like little more than a curiosity! 4Sure-6 you mi&ht say- 4' can make an o#*ect that can #e called like a function! 7ut what does it #uy me>6 , lot more than you mi&ht initially suspect- it turns out! The key difference #etween a function and a functor is that a functor's function call operator is a member "unction whereas a raw C++ function is a "ree "unction! This means that a functor can access the followin& in1 formation when #ein& called= 'ts local varia#les! 'ts parameters! 3lo#al varia#les! Class data members!
This last point is e)tremely important and is the key difference #etween a re&ular function and a functor! 'f a functor needs data #eyond what can #e communicated #y its parameters- we can store that information as a data mem#er inside the functor class! 8or e)ample- consider the followin& functor class=
1 A6E 1
*lass 5tringA$$ender { $ubli*3 "2 4onstru*tor ta+es and stores a string. 2" e#plicit StringUppender!const stringS str$ : toUppend!str$ *4 "2 o$erator() $rints out a string, $lus the stored suffi7. 2" )oid operator!$ !const stringS str$ const { cout .. str .. @ @ .. toUppend .. endl; # $rivate3 const string toUppend; #;
This functor's constructor takes in a strin& and stores it for later use! 'ts o$erator () function accepts a strin&- then prints it out suffi)ed with the strin& stored #y the constructor! 0e use the 5tringA$$ender functor like this=
5tringA$$ender m%&un*tor(.is a'esome.); myFunctor!";33"$;
This code will print out 4C++ is awesome-6 since the constructor stored the strin& 4is awesome6 and we passed 4C++6 as a parameter to the function! 'f you'll notice- thou&h- in the actual function call we only passed in one piece of information! This is precisely why functors are so useful ( they can mimic functions that take in a fi)ed amount of data- yet have access to as much information is necessary to solve the task at hand! et's return to the a#ove e)ample with *ountBif! Somehow we need to provide a unary function that can re1 turn whether a strin& is less than an ar#itrary len&th! To solve this pro#lem- instead of writin& a unary functionwe'll create a unary "unctor whose constructor stores the ma)imum len&th and whose o$erator () accepts a strin& and returns whether it's of the correct len&th! /ere's one possi#le implementation=
*lass 5horter;han { $ubli*3 "2 A**e$t and store an int $arameter 2" e#plicit ShorterThan!int ma#1ength$ : length!ma#1ength$ *4 "2 Ceturn 'hether the string length is less than the stored int. 2" bool operator!$ !const stringS str$ const * return str.length!$ . length; 4 $rivate3 const int length; #;
"ote that while o$erator () takes in only a sin&le parameter- it has access to the length field that was set up #y the constructor! This is e)actly what we want ( a unary function that knows what value to compare the para1 meter's len&th a&ainst! To tie everythin& toðer- here's the code we'd use to count the num#er of strin&s in the ve*tor that are shorter than the specified value=
5horter;han st(length); *ountBif(m%De*tor.begin(), m%De*tor.end(), st);
1 A6< 1
8unctors are incredi#le when com#ined with ST al&orithms for this very reason ( they look and act like re&ular functions #ut have access to e)tra information! This is *ust your first taste of functors- as you continue your e)1 ploration of C++ you will reco&nize e)actly how much they will influence your pro&ram desi&n! ook #ack to the a#ove code with *ountBif! 'f you'll notice- we created a new 5horter;han o#*ect- then fed it to *ountBif! ,fter the call to *ountBif- odds are we'll never use that particular 5horter;han o#*ect a&ain! This is an e)cellent spot to use temporary o#*ects- since we need a new 5horter;han for the function call #ut don't plan on usin& it afterwards! Thus- we can convert this code=
5horter;han st(length) *ountBif(m%De*tor.begin(), m%De*tor.end(), st);
/ere- 5horter;han(length) constructs a temporary 5horter;han functor with parameter length- then passes it to the *ountBif al&orithm! 2on't &et tripped up #y the synta) ( 5horter;han(length) does not call the 5horter;han's o$erator () function! 'nstead- it invokes the 5horter;han constructor with the parameter length to create a temporary o#*ect! Dven if we had written the o$erator() function to take in an int- C++ would realize that the parentheses here means 4construct an o#*ect6 instead of 4invoke o$erator()6 from conte)t! /riting !unctorCCom&atible Code 'n CS1067IJ- you've seen how to write code that accepts a function pointer as a parameter! 8or e)ample- the followin& code accepts a function that takes and returns a double- then prints a ta#le of some values of that function=
*onst *onst *onst *onst double double int double 6=1ECBO=@E9 @<<ECBO=@E9 E@:B5;E<5 5;E< = = = = 0.0; 8.0; HK; (@<<ECBO=@E9 Q 6=1ECBO=@E9) " E@:B5;E<5;
void ;abulate&un*tionDalues(double unction!double$) { for(double i = 6=1ECBO=@E9; i = @<<ECBO=@E9; i != 5;E<) *out .f(. i .) = . unction!i$ endl; #
8or any function acceptin& and returnin& a double- we can call ;abulate&un*tionDalues with that function as an ar&ument! 7ut what a#out functors> Can we pass them to ;abulate&un*tionDalues as well> ,s an e)ample- suppose we have some unary functor class Ce*i$ro*al that accepts a double and returns its recip1 rocal! Then is the followin& code le&al>
;abulate&un*tionDalues(Ceciprocal!$);
KFecall that Ce*i$ro*al() constructs a temporary Ce*i$ro*al o#*ect for use as the parameter to ;abulate, &un*tionDalues!L ,t a hi&h level- this code seems perfectly fine! ,fter all- Ce*i$ro*al o#*ects can #e called as thou&h they were functions takin& and returnin& doubles- so it seems perfectly reasona#le to pass a Ce*i$ro*al into ;abu, late&un*tionDalues! 7ut despite the similarities- Ce*i$ro*al is not a function ( it's a functor ( and so the a#ove code will not compile!
1 A@0 1
The pro#lem is that C++'s static type system prevents function pointers from pointin& to functors- even if the functor has the same parameter and return type as the function pointer! This is not without reason ( the machine code for callin& a function is very different from machine code for callin& a functor- and if C++ were to conflate the two it would result either in slower function calls or undefined runtime #ehavior! 0e know that this code won't compile- #ut how can we fi) it so that it will> et's #e&in with some o#servationsthen &eneralize to the optimal solution! The a#ove code will not compile #ecause we're tryin& to provide a Ce, *i$ro*al o#*ect to a function e)pectin& a function pointer! This su&&ests one option ( could we rewrite the ;abulate&un*tionDalues function such that it accepts a Ce*i$ro*al as a parameter instead of a function pointer> 8or e)ample- we could write the followin&=
void ;abulate&un*tionDalues(Ceciprocal unction) { for(double i = 6=1ECBO=@E9; i = @<<ECBO=@E9; i != 5;E<) *out .f(. i .) = . fun*tion(i) endl; #
"ow- the a#ove call will work correctly! 7ut what if we have another functor we want to use in ;abulate&un*tionDalues> 8or e)ample- we mi&ht write a functor called Ar**os that computes the inverse cosine of its parameter! 0e could then provide an im1 plementation of ;abulate&un*tionDalues that looks like this=
void ;abulate&un*tionDalues(Urccos unction) { for(double i = 6=1ECBO=@E9; i = @<<ECBO=@E9; i != 5;E<) *out .f(. i .) = . fun*tion(i) endl;
0e could continue with this approach- rewritin& the function once for every functor type we'd like to provide#ut this method does not scale well! 7ut is there some lesson we can take away from this method> et's reprint all three versions of ;abulate&un*tionDalues=
void ;abulate&un*tionDalues(double unction!double$) { for(double i = 6=1ECBO=@E9; i = @<<ECBO=@E9; i != 5;E<) *out .f(. i .) = . fun*tion(i) endl;
void ;abulate&un*tionDalues(Ceciprocal unction) { for(double i = 6=1ECBO=@E9; i = @<<ECBO=@E9; i != 5;E<) *out .f(. i .) = . fun*tion(i) endl;
void ;abulate&un*tionDalues(Urccos unction) { for(double i = 6=1ECBO=@E9; i = @<<ECBO=@E9; i != 5;E<) *out .f(. i .) = . fun*tion(i) endl;
"otice that the only difference #etween the three implementations of ;abulate&un*tionDalues is the type of the parameter to the function! The rest of the code is identical! This su&&ests a rather ele&ant solution usin& templates! 'nstead of providin& multiple different versions of ;abulate&un*tionDalues- each specialized for a particular type of function or functors- we'll write a sin&le template version of ;abulate&un*tionDalues parameterized over the type of the ar&ument! This is shown here=
1 A@1 1
"ow- we can pass any type of o#*ect to ;abulate&un*tionDalues that we want- provided that the ar&ument can #e called with a sin&le double as a parameter to produce a value! This hearkens #ack to our discussion of implicit interfaces in the chapter on templates! Usin& a template function- we can accept any compati#le type without worryin& a#out how e)actly it's implemented! 0hen writin& functions that re5uire a user1specified call#ack- you may want to consider parameterizin& the function over the type of the call#ack instead of usin& function pointers! The resultin& code will #e more fle)1 i#le and future &enerations of pro&rammers will #e much the #etter for your e)tra effort! Storing 2b?ects in STL mapsF -art II 'n the chapter on operator overloadin&- we demonstrated how to store custom o#*ects as keys in an ST ma$ #y overloadin& the operator! /owever- what if you want to store elements in a ma$ or set- #ut not usin& the de1 fault comparison operator> 8or e)ample- consider a set *har 2) of C strin&s! "ormally- the operator will compare two *har 2s #y seein& if one references memory with a lower address than the other! This isn't at all the #ehavior we want! 8irst- it would mean that the set would store its elements in a seemin&ly random ordersince the comparison is independent of the contents of the C strin&s! Second- if we tried to call find or *ount to determine mem#ership in the set- since the set compares the #ointers to the C strin&s- not the C strin&s themselves- find and *ount would return whether the &iven #ointer- not the strin& pointed at- was contained in the set! 0e need to tell the set that it should not use the operator to compare C strin&s- #ut we can't simply provide an alternative operator and e)pect the set to use it! 'nstead- we'll define a functor class whose o$erator () compares two C strin&s le)ico&raphically and returns whether one strin& compares less than the other! /ere's one possi#le implementation=
*lass 45tring4om$are { $ubli*3 bool operator!$ !const charR oneB const charR t6o$ const * return strcmp!oneB t6o$ . -; TT Use strcmp to do the comparison 4 #;
Then- to si&nal to the set that it should compare elements usin& 45tring4om$are instead of the default erator- we'll parameterize the set over #oth *har2 and 45tring4om$are- as shown here=
set *har2, ;String;ompare) m%5et;
op1
7ecause 45tring4om$are is a template ar&ument- C++ treats set *har 2) and set *har 2, 45tring, 4om$are) as two different types! This means that you can only iterate over a set *har 2, 45tring4om, $are) with a set *har 2, 4string4om$are)33iterator! .ou will pro#a#ly find t%$edef handy here to save some tedious typin&! This a#ove discussion concerned the ST set- #ut you can use a similar trick on the ST ma$! 8or e)ample- if we want to create a map whose keys are C strin&s- we could write
1 A@; 1
ma$ *har2, int, ;String;ompare) m%:a$;
STL Algorithms 1e7isited "ow that you're armed with the full power of C++ functors- let's revisit some of the ST al&orithms we've covered and discuss how to ma)imize their firepower! The very first al&orithm we covered was a**umulate- defined in the numeri*) header! 'f you'll recall- a*, *umulate sums up the elements in a ran&e and returns the result! 8or e)ample- &iven a ve*tor int)- the fol1 lowin& code returns the sum of all of the ve*tor's elements=
a**umulate(m%De*tor.begin(), m%De*tor.end(), 0);
The first two parameters should #e self1e)planatory- and the third parameter KzeroL represents the initial value of the sum! /owever- this view of a**umulate is limited- and to treat a**umulate as simply a way to sum container ele1 ments would #e an error! Father- accumulate is a general-#ur#ose "unction "or trans"orming a collection o" elements into a single !alue) There is a second version of the a**umulate al&orithm that takes a #inary function as a fourth parameter! This version of a**umulate is implemented like this=
tem$late t%$ename (n$ut(terator, t%$ename ;%$e, typename (inaryFn) inline ;%$e a**umulate((n$ut(terator start, (n$ut(terator sto$, ;%$e a**umulator, (inaryFn n) { 'hile(start 0= sto$) { accumulator , n!accumulatorB Rstart$; !!start; # return initial; #
This a**umulate iterates over the elements of a container- callin& the #inary function on the accumulator and the current element of the container and storin& the result #ack in the accumulator! 'n other words- a**umulate continuously updates the value of the accumulator #ased on its initial value and the values contained in the input ran&e! 8inally- a**umulate returns the accumulator! "ote that the version of a**umulate we encountered earlier is actually a special case of the a#ove version where the provided call#ack function computes the sum of its parameters! To see a**umulate in action- let's consider an e)ample! Fecall that the ST al&orithm lo'erBbound returns an iterator to the first element in a ran&e that compares &reater than or e5ual to some value! /oweverlo'erBbound re5uires the elements in the iterator ran&e to #e in sorted order- so if you have an unsorted ve*, tor- you cannot use lo'erBbound! et's write a function @nsorted6o'erOound that accepts a ran&e of iter1 ators and a lower #ound- then returns the value of the least element in the ran&e &reater than or e5ual to the lower #ound! 8or simplicity- let's assume we're workin& with a ve*tor int) so that we don't &et #o&&ed down in template synta)- thou&h this approach can easily #e &eneralized!
1 A@A 1
,lthou&h this function can #e implemented usin& loops- we can levera&e off of a**umulate to come up with a considera#ly more concise solution! Thus- we'll define a functor class to pass to a**umulate- then write @n, sorted6o'erOound as a wrapper call to a**umulate with the proper parameters! Consider the followin& functor=
*lass 6o'erOound/el$er { $ubli*3 e#plicit 1o6er(ound'elper!int lo6er$ : lo6est5alue!lo6er$ *4 int operator!$ !int bestSoFarB int current$ * i !current >, lo6est5alue SS current . bestSoFar$ return current; return bestSoFar; 4 $rivate3 const int lo6est5alue; #;
This functor's constructor accepts the value that we want to lower1#ound! 'ts o$erator () function accepts two ints- the first representin& the lowest known value &reater than lo'estDalue and the second the current value! 'f the value of the current element is &reater than or e5ual to the lower #ound and also less than the #est value so far- o$erator () returns the value of the current element! Htherwise- it simply returns the #est value we've found so far! Thus if we call this functor on every element in the ve*tor and keep track of the return value- we should end up with the lowest value in the ve*tor &reater than or e5ual to the lower #ound! 0e can now write the @nsorted6o'erOound function like this=
int @nsorted6o'erOound(*onst ve*tor int)? in$ut, int lo'erOound) { return accumulate!input.begin!$B input.end!$B numeric&limits.int>::ma#!$B 1o6er(ound'elper!lo6er(ound$$; #
Hur entire function is simply a wrapped call to a**umulate- passin& a specially1constructed 6o'erOound, /el$er o#*ect as a parameter! "ote that we've used the value numeri*Blimits int)33ma7() as the initial value for the accumulator! numeri*Blimits- defined in the limits) header- is a traits class that e)ports use1 ful information a#out the #ounds and #ehavior of numeric types- and its ma7 static mem#er function returns the ma)imum possi#le value for an element of the specified type! 0e use this value as the initial value for the accu1 mulator since any inte&er is less than it- so if the ran&e contains no elements &reater than the lower #ound we will &et numeri*Blimits int)33ma7() #ack as a sentinel! 'f you need to transform a ran&e of values into a sin&le result Kof any type you wishL- use a**umulate! To transform a ran&e of values into another ran&e of values- use transform! 0e discussed transform #riefly in the chapter on ST al&orithms in the conte)t of 4onvert;o@$$er4ase and 4onvert;o6o'er4ase- #ut such e)amples are *ust the tip of the ice#er&! transform is nothin& short of a miracle function- and it arises a whole host of circumstances!Q 0igherCorder -rogramming This discussion of functors was initially motivated #y countin& the num#er of short strings inside of an ST ve*tor! 0e demonstrated that #y usin& *ountBif with a custom functor as the final parameter- we were a#le to write code that counted the num#er of elements in a ve*tor string) whose len&th was less than a certain
Q Those of you familiar with functional pro&rammin& mi&ht reco&nize order functions Gap and Feduce!
a**umulate
and
transform
1 A@B 1
value! 7ut while this code solved the pro#lem efficiently- we ended up writin& so much code that any potential #enefits of the ST al&orithms were dwarfed #y the time spent writin& the functor! 8or reference- here was the code we used=
*lass 5horter;han { $ubli*3 e7$li*it 5horter;han(int ma76ength) 3 length(ma76ength) {# bool o$erator() (*onst string? str) *onst { return str.length() length; # $rivate3 int length; #; *onst int m%Dalue = Get(nteger(); *ountBif(m%De*tor.begin(), m%De*tor.end(), ShorterThan!my5alue$);
Consider the followin& code which also solves the pro#lem- #ut #y usin& a simple for loop=
*onst int m%Dalue = Get(nteger(); int total = 0; for(int i = 0; i m%De*tor.siUe(); !!i) if(m%De*tor[i].length() m%Dalue) !!total;
This code is considera#ly more reada#le than the functor version and is appro)imately a third as lon&! 7y al1 most any metric- this code would #e considered superior to the earlier version! 'f you'll recall- we were motivated to write this 5horter;han functor #ecause we were una#le to use *ountBif in con*unction with a traditional C++ function! 7ecause *ountBif accepts as a parameter a unary function- we could not write a C++ function that could accept #oth the current container element and the value to compare its len&th a&ainst! /owever- we did note that were *ountBif to accept a #inary function and e)tra client data- then we could have written a simple C++ function like this one=
bool 6ength(s6ess;han(*onst string? str, int threshold) { return str.length() threshold; #
,nd then passed it in- alon& with the cutoff len&th- to the *ountBif function! The fundamental pro#lem is that the ST *ountBif al&orithm re5uires a sin&le1parameter function- #ut the function we want to use re5uires two pieces of data! 0e want the ST al&orithms to use our two1parameter function 6ength(s6ess;han- #ut with the second parameter always havin& the same value! 0hat if somehow we could modify 6ength(s6ess;han #y 4lockin& in6 the second parameter> 'n other words- we'd like to take a function that looks like this=
1 A@? 1
(parameter)
int length
(parameter)
(parameter)
int length
"ow- if we call this special version of 6ength(s6ess;han with a sin&le parameter Kcall it strL- it would #e as thou&h we had called the initial version of 6ength(s6ess;han- passin& as parameters the value of str and the stored value ?! This then returns whether the len&th of the str strin& is less than ?! Dssentially- #y #indin& the second parameter of the two1parameter 6ength(s6ess;han function- we end up with a one1parameter function that descri#es e)actly the predicate function we want to provide to *ountBif! Thus- at a hi&h level- the code we want to #e a#le to write should look like this=
*ountBif(v.begin(), v.end(),
the "unction "ormed by loc*ing 3 as the second #arameter o" :ength;s:ess<han)= This sort of pro&rammin&- where functions can #e created and modified *ust like re&ular o#*ects- is known as higher-order #rogramming! 0hile #y default C++ does not support hi&her1order pro&rammin&- usin& functors and the ST functional pro&rammin& li#raries- in many cases it is possi#le to write hi&her1order code in C++! 'n the remainder of this chapter- we'll e)plore the ST functional pro&rammin& li#raries and see how to use hi&her1 order pro&rammin& to superchar&e ST al&orithms! Ada&table !unctions To provide hi&her1order pro&rammin& support- standard C++ provides the fun*tional) li#rary! fun*, tional) e)ports several useful functions that can transform and modify functions on1the1fly to yield new func1 tions more suita#le to the task at hand! /owever- #ecause of several lan&ua&e limitations- the fun*tional) li#rary can only modify specially constructed functions called 4adapta#le functions-6 functors Knot re&ular C++ functionsL that e)port information a#out their parameter and return types! 8ortunately- any one1 or two1paramet1 er function can easily #e converted into an e5uivalent adapta#le function! 8or e)ample- suppose you want to make an adapta#le function called :%&un*tion that takes a string #y reference1to1*onst as a parameter and returns a bool- as shown #elow=
1 A@6 1
*lass :%&un*tion { $ubli*3 bool o$erator() (*onst string? str) *onst { "2 &un*tion that mani$ulates a string 2" # #;
"ow- to make this function an adapta#le function- we need to specify some additional information a#out the parameter and return types of this functor's o$erator () function! To assist in this process- the functional li#1 rary defines a helper template class called unar%Bfun*tion- which is prototyped #elow=
template .typename ParameterTypeB typename CeturnType> class unary& unction;
The first template ar&ument represents the type of the parameter to the functionR the second- the function's return type! Unlike the other classes you have seen #efore- the unar%Bfun*tion class contains no data mem#ers and no mem#er functions! 'nstead- it performs some #ehind1the1scenes ma&ic with the t%$edef keyword to e)port the information e)pressed in the template types to the rest of the functional pro&rammin& li#rary! Since we want our a#ove functor to also e)port this information- we'll use a techni5ue called inheritance to import all of the inform1 ation from unar%Bfun*tion into our :%&un*tion functor! 7ecause :%&un*tion accepts as a parameter an o#*ect of type string and returns a varia#le of type bool- we will have :%&un*tion inherit from the type un, ar%Bfun*tion string, bool)! The synta) to accomplish this is shown #elow=
*lass :%&un*tion : public unary& unction.stringB bool> { $ubli*3 bool o$erator() (*onst string? str) *onst { "2 &un*tion that mani$ulates a string 2" # #;
0e'll e)plore inheritance in more detail later- #ut for now *ust think of it as a way for importin& information from class into another! "ote that althou&h the function accepts as its parameter a *onst string?- we chose to use a unar%Bfun*tion specialized for the type string! The reason is somewhat technical and has to do with how unar%Bfun*tion interacts with other functional li#rary components- so for now *ust remem#er that you should not specify reference1to1*onst types inside the unar%Bfun*tion template parametrization! The synta) for convertin& a #inary functor into an adapta#le #inary function works similarly to the a#ove code for unary functions! Suppose that we'd like to make an adapta#le #inary function that accepts a string and an int and returns a bool! 0e #e&in #y writin& the #asic functor code- as shown here=
*lass :%=ther&un*tion { $ubli*3 bool o$erator() (*onst string? str, int val) *onst { "2 9o something, return a bool. 2" # #;
Cha#ter 1?% Functors To convert this functor into an adapta#le function- we'll have it inherit from binar%Bfun*tion! unar%Bfun*tion- binar%Bfun*tion is a template class that's defined as
template .typename Param7TypeB typename Param8TypeB typename CesultType> class binary& unction;
1 A@@ 1 ike
0hile the a#ove approach for &eneratin& adapta#le functions is perfectly le&al- it's a #it clunky and we still have a hi&h ratio of #oilerplate code to actual lo&ic! 8ortunately- the ST functional li#rary provides the powerful #ut cryptically named $trBfun2 function that transforms a re&ular C++ function into an adapta#le function! $trBfun can convert #oth unary and #inary C++ functions into adapta#le functions with the correct parameter types- meanin& that you can skip the hassle of the a#ove code #y simply writin& normal functions and then usin& $trBfun to transform them into adapta#le functions! 8or e)ample- &iven the followin& C++ function=
bool 6ength(s6ess;han(string m%5tr, int threshold) { return m%5tr.length() threshold; #
'f we need to &et an adapta#le version of that function- we can write ptr& un!1engthIs1essThan$ in the spot where the adapta#le function is needed!
$trBfun is a useful #ut imperfect tool! Gost nota#ly- you cannot use $trBfun on functions that accept para1 meters as reference1to1*onst! $trBfun returns a unar%Bfun*tion o#*ect- and as mentioned a#ove- you can1 not specify reference1to1*onst as template ar&uments to unar%Bfun*tion! ,lso- #ecause of the way that the C++ compiler &enerates code for functors- code that uses $trBfun can #e a #it slower than code usin& functors!
8or situations where you'd like to convert a mem#er function into an adapta#le function- you can use the memBfun or memBfunBref functions! These functions convert mem#er functions into unary functions that ac1 cept as input a receiver o#*ect- then invoke that mem#er function on the receiver! The difference #etween memBfun and memBfunBref is how they accept their parameters ( memBfun accepts a #ointer to the receiver o#*ect- while memBfunBref accepts a re"erence to the receiver! 8or e)ample- &iven a ve*tor string)- the followin& code will print out the len&ths of all of the strin&s in the ve*tor=
transform(m%De*tor.begin(), m%De*tor.end(), ostreamBiterator int)(*out, .Tn.), mem& un&re !Sstring::length$);
et's dissect this call to transform- since there's a lot &oin& on! The first two parameters delineate the input ran&e- in this case the full contents of m%De*tor! The third parameter specifies where to put the output- and since here it's an ostreamBiterator the output will #e written to *out! The final parameter is memBfunBref(?string33length)- a function that accepts as input a string and then returns the value of the length mem#er function called on that string!
Q $trBfun is short for 4pointer function6- since you're providin& as a parameter a function pointer! 't should not #e con1 fused with "ick Carlante's e)cellent video 47inky's Cointer 8un!6
1 A@E 1
memBfunBref can also #e used to convert unary Kone1parameterL mem#er functions into adapta#le #inary func1
tions that take as a first parameter the o#*ect to apply the function to and as a second parameter the parameter to the function! 0hen we cover #inders in the ne)t section- you should &et a #etter feel for e)actly how useful this is! ,inding -arameters "ow that we've covered how the ST functional li#rary handles adapta#le functions- let's consider how we can use them in practice! ,t the #e&innin& of this chapter- we introduced the notion of #arameter binding- convertin& a two1parameter function into a one1parameter function #y lockin& in the value of one of its parameters! To allow you to #ind parameters to functions- the ST functional pro&rammin& li#rary e)ports two functions- bind!st and bind$ndwhich accept as parameters an adapta#le function and a value to #ind and return new functions that are e5ual to the old functions with the specified values #ound in place! 8or e)ample- &iven the followin& implementation of 6ength(s6ess;han=
bool 6ength(s6ess;han(string str, int threshold) { return str.length() threshold; #
0e could use the followin& synta) to construct a function that's 6ength(s6ess;han with the value five #ound to the second parameter=
bind8nd!ptr& un!1engthIs1essThan$B I$;
The line bindHnd($trBfun(6ength(s6ess;han), K) first uses $trBfun to &enerate an adapta#le version of the 6ength(s6ess;han function- then uses bindHnd to lock the parameter ? in place! The result is a new unary function that accepts a string parameter and returns if that strin&'s len&th is less than ?- the value we #ound to the second parameter! Since bindHnd is a function that accepts a function as a parameter and returns a function as a result- bindHnd is a function that is sometimes referred to as a higher-order "unction! 7ecause the result of the a#ove call to bindHnd is a unary function that determines if a strin& has len&th less than five- we can use the *ountBif al&orithm to count the num#er of values less than five #y usin& the follow1 in& code=
*ountBif(*ontainer.begin(), *ontainer.end(), bind8nd!ptr& un!1engthIs1essThan$B I$);
Compare this code to the functor1#ased approach illustrated at the start of this chapter! This version of the code is much- much shorter than the previous version! 'f you aren't #e&innin& to appreciate e)actly how much power and fle)i#ility the fun*tional) li#rary provides- skip ahead and take a look at the practice pro#lems! The bind8st function acts similarly to bindHnd- e)cept that Kas its name su&&estsL it #inds the first parameter of a function! Feturnin& to the a#ove e)ample- &iven a ve*tor int)- we could count the num#er of elements in that ve*tor smaller than the len&th of strin& 4C++U6 #y writin&
*ountBif(m%De*tor.begin(), m%De*tor.end(), bind7st!ptr& un!1engthIs1essThan$B ";33%"$);
'n the ST functional pro&rammin& li#rary- parameter #indin& is restricted only to #inary functions! Thus you cannot #ind a parameter in a three1parameter function to yield a new #inary function- nor can you #ind the para1
1 A@< 1
meter of a unary function to yield a zero1parameter K4nullary6L function! 8or these operations- you'll need to cre1 ate your own custom functors- as shown in the practice pro#lems at the end of this chapter! <egating 1esults Suppose that &iven a function 6ength(s6ess;han- we want to find the num#er of strin&s in a container that are not less than a certain len&th! 0hile we could simply write another function 6ength(sEot6ess;han- it would #e much more convenient if we could somehow tell C++ to take whatever value 6ength(s6ess;han returns and to use the opposite result! That is- &iven a function that looks like this=
YES
Input
1engthIs1essThan
NO
0e'd like to chan&e it into a function that looks like this=
NO
Input
1engthIs1essThan in2'rt'r
YES
This operation is negation ( constructin& a new function whose return value has the opposite value of the input function! There are two ST ne&ator functions ( not8 and notH ( that return the ne&ated result of a unary or #inary predicate function- respectively! Thus- the a#ove function that's a ne&ation of 6ength(s6ess;han could #e written as not8!ptr& un!1engthIs1essThan$$! Since notH returns an adapta#le function- we can then pass the result of this function to bindHnd to &enerate a unary function that returns whether a strin&'s len&th is at least a certain threshold value! 8or e)ample- here's code that returns the num#er of strings in a container with len&th at least ?=
*ountBif(*ontainer.begin(), *ontainer.end(), bindHnd(not8($trBfun(6ength(s6ess;han)), K));
0hile this line is dense- it ele&antly solves the pro#lem at hand #y com#inin& and modifyin& e)istin& code to create entirely different functions! Such is the #eauty and simplicity of hi&her1order pro&rammin& ( why rewrite code from scratch when you already have all the pieces individually assem#led>
et's suppose that you have a container of ints and you'd like to add 1A@ to each of them! Fecall that you can use the ST transform al&orithm to apply a function to each element in a container and then store the result! 7ecause we're addin& 1A@ to each element- we mi&ht consider writin& a function like this one=
int Add8FG(int $aram) { return $aram ! 8FG; #
0hile this code works correctly- this approach is not particularly ro#ust! 0hat if later on we needed to incre1 ment all elements in a container #y B;- or perhaps #y an ar#itrary value> Thus- we mi&ht want to consider repla1 cin& Add8FG #y a function like this one=
int Add;'o(nts(int one, int t'o) { return one ! t'o; #
,nd then usin& #inders to lock the second parameter in place! 8or e)ample- here's code that's e5uivalent to what we've written a#ove=
transform(*ontainer.begin(), *ontainer.end(), *ontainer.begin(), bind8nd!ptr& un!UddT6oInts$B 7DE$);
,t this point- our code is correct- #ut it can &et a #it annoyin& to have to write a function Add;'o(nts that simply adds two inte&ers! Goreover- if we then need code to increment all doubles in a container #y 1!A@- we would need to write another function Add;'o9oubles to avoid pro#lems from typecasts and truncations! 8ortu1 nately- #ecause this is a common use case- the ST functional li#rary provides a lar&e num#er of template adapt1 a#le function classes that simply apply the #asic C++ operators to two values! 8or e)ample- in the a#ove codewe can use the adapta#le function class plus<int> instead of our Add;'o(nts function- resultin& in code that looks like this=
transform(*ontainer.begin(), *ontainer.end(), *ontainer.begin(), bindHnd(plus.int>!$, 8FG));
"ote that we need to write $lus int)() instead of simply $lus int)- since we're usin& the temporary o#*ect synta) to construct a $lus int) o#*ect for bindHnd! 8or&ettin& the parentheses can cause a ma*or compiler error headache that can take a while to track down! ,lso notice that we don't need to use $trBfun here- since $lus int) is already an adapta#le function! 8or reference- here's a list of the common 4operator functions6 e)ported #y fun*tional)=
$lus less multi$lies lessBeVual divides greater minus greaterBeVual modulus notBeVualBto eVualBto
To see an e)ample that com#ines the techni5ues from the previous few sections- let's consider a function that ac1 cepts a ve*tor double) and converts each element in the ve*tor to its reciprocal Kone divided #y the valueL! 7ecause we want to convert each element with value 7 to the value 1I7- we can use a com#ination of #inders and
1 AE1 1
operator functions to solve this pro#lem #y #indin& the value 1!0 to the first parameter of the divides double) functor! The result is a unary function that accepts a parameter of type double and returns the element's reciprocal! The resultin& code looks like this=
transform(v.begin(), v.end(), v.begin(), bind7st!di)ides.double>!$B 7.-$);
This code is concise and ele&ant- solvin& the pro#lem in a small space and makin& e)plicit what operations are #ein& performed on the data! Im&lementing the . unctional> Library "ow what we've seen how the fun*tional) li#rary works from a client perspective- let's discuss how the li#1 rary is put toðer! 0hat's so special a#out adapta#le functions> /ow does $trBfun convert a re&ular func1 tion into an adapta#le one> /ow do functions like bindHnd and not8 work> et's #e&in #y lookin& at e)actly what an adapta#le function is! Fecall that adapta#le functions are functors that inherit from either unar%Bfun*tion or binar%Bfun*tion! "either of these template classes are particularly complicatedR here's the complete definition of unar%Bfun*tion=Q
tem$late t%$ename Arg;%$e, t%$ename Cet;%$e) *lass unar%Bfun*tion { $ubli*3 typede UrgType argument&type; typede CetType result&type; #;
This class contains no data mem#ers and no mem#er functions! 'nstead- it e)ports two t%$edefs ( one renam1 in& Arg;%$e to argumentBt%$e and one renamin& Cet;%$e to resultBt%$e! 0hen you create an adapta#le function that inherits from unar%Bfun*tion- your class ac5uires these t%$edefs! 8or e)ample- if we write the followin& adapta#le function=
*lass (s<ositive3 public unary& unction.doubleB bool> { $ubli*3 bool o$erator() (double value) *onst { return value ) 0.0; # #;
The statement $ubli* unar%Bfun*tion double, double) imports two t%$edefs into (s<ositive= ar, gumentBt%$e and returnBt%$e- e5ual to double and bool- respectively! Fi&ht now it mi&ht not #e apparent how these types are useful- #ut as we #e&in implementin& the other pieces of the fun*tional) li#rary it will #ecome more apparent! Im&lementing not7 To #e&in our #acksta&e tour of the fun*tional) li#rary- let's see how to implement the not8 function! Fecall that not8 accepts as a parameter a unary adapta#le predicate function- then returns a new adapta#le function that yields opposite values as the ori&inal function! 8or e)ample- not8((s<ositive()) would return a function that returns whether a value is not positive!
Q Technically speakin& unar%Bfun*tion and binar%Bfun*tion are stru*ts- #ut this is irrelevant here!
1 AE; 1
'mplementin& not8 re5uires two steps! 8irst- we'll create a template functor class parameterized over the type of the adapta#le function to ne&ate! This functor's constructor will take as a parameter an adapta#le function of the proper type and store it for later use! 0e'll then implement its o$erator() function such that it calls the stored function and returns the ne&ation of the result! 3raphically- this is shown here=
N'gation F-n&tor
In,-t Stor'd 3-n&tion In2'rt'r O-t,-t
Hnce we have desi&ned this functor- we'll have not8 accept an adapta#le function- wrap it in our ne&atin& func1 tor- then return the resultin& o#*ect to the caller! This means that the return value of not8 is an adapta#le unary predicate function that returns the opposite value of its parameter- which is e)actly what we want! et's #e&in #y writin& the template functor class- which we'll call unar%Bnegate Kthis is the name of the func1 tor class &enerated #y the fun*tional) li#rary's not8 functionL! 0e know that this functor should #e para1 meterized over the type of the adapta#le function it ne&ates- so we can #e&in #y writin& the followin&=
template .typename UnaryPredicate> class unary&negate { $ubli*3 e#plicit unary&negate!const UnaryPredicateS pred$ : p!pred$ *4 "2 ... 2" $rivate3 UnaryPredicate p; #;
/ere- the constructor accepts an o#*ect of type @nar%<redi*ate- then stores it in the data mem#er $! "ow- let's implement the o$erator() function- which- as you'll recall- should take in a parameter- feed it into the stored function $- then return the inverse result! The code for this function looks like this=
tem$late t%$ename @nar%<redi*ate) *lass unar%Bnegate { $ubli*3 e7$li*it unar%Bnegate(*onst @nar%<redi*ate? $red) 3 $($red) {# bool operator!$ !const TR 6hat goes hereW RTS param$ const * return %p!param$; 4 $rivate3 @nar%<redi*ate $; #;
1 AEA 1
0e've almost finished writin& our unar%Bnegate class- #ut we have a sli&ht pro#lem ( what is the type of the parameter to o$erator()> This is where adapta#le functions come in! 7ecause @nar%<redi*ate is adapt1 a#le- it must e)port a type called argumentBt%$e correspondin& to the type of its ar&ument! 0e can thus define our o$erator() function to accept a parameter of type t%$ename @nar%<redi*ate33argume ntBt%$e to &uarantee that it has the same parameter type as the @nar%<redi*ate class!Q The updated code for unar%Bnegate looks like this=
tem$late t%$ename @nar%<redi*ate) *lass unar%Bnegate { $ubli*3 e7$li*it unar%Bnegate(*onst @nar%<redi*ate? $red) 3 $($red) {# bool o$erator() (*onst typename UnaryPredicate::argument&type? $aram) *onst { return 0$($aram); # $rivate3 @nar%<redi*ate $; #;
That's 5uite a mouthful- #ut it's e)actly the solution we're lookin& for! There's one step left to finalize this functor class- and that's to make the functor into an adapta#le function #y havin& it inherit from the proper unar%Bfun*tion! Since the functor's ar&ument type is t%$ename @n, ar%<redi*ate33argumentBt%$e and its return type is bool- we'll inherit from unar%Bfun*tion t%$e, name @nar%<redi*ate33 argumentBt%$e, bool)! The final code for unar%Bnegate is shown here=
tem$late t%$ename @nar%<redi*ate) *lass unar%Bnegate3 public unary& unction.typename UnaryPredicate::argument&typeB bool> { $ubli*3 e7$li*it unar%Bnegate(*onst @nar%<redi*ate? $red) 3 $($red) {# bool o$erator() (*onst t%$ename @nar%<redi*ate33argumentBt%$e? $aram) *onst { return 0$($aram); # $rivate3 @nar%<redi*ate $; #;
0e've now finished writin& our functor class to perform the ne&ation- and all that's left to do is write not8! not8 is much simpler than unar%Bnegate- since it simply has to take in a parameter and wrap it in a unar%Bnegate functor! This is shown here=
template .typename UnaryPredicate> unary&negate.UnaryPredicate> not7!const UnaryPredicateS pred$ * return unary&negate.UnaryPredicate>!pred$; 4
1 AEB 1
.ou mi&ht #e wonderin& why there are two steps involved in writin& not8! ,fter all- once we have the functor that performs ne&ation- why do we need to write an additional function to create it> The answer is sim#licity! 0e don't need not8- #ut havin& it availa#le reduces comple)ity! 8or e)ample- usin& the (s<ositive adapta#le function from a#ove- let's suppose that we want to write code to find the first nonpositive element in a ve*tor! Usin& the findBif al&orithm and not8- we'd write this as follows=
ve*tor double)33iterator itr = findBif(v.begin(), v.end(), not7!IsPositi)e!$$);
'f instead of usin& not8 we were to e)plicitly create a unar%Bnegate o#*ect- the code would look like this=
ve*tor double)33iterator itr = findBif(v.begin(), v.end(), unary&negate.IsPositi)e>!IsPositi)e!$$);
That's 5uite a mouthful! 0hen callin& the template function not8- the compiler automatically infers the type of the ar&ument and constructs an appropriately parameterized unar%Bnegate o#*ect! 'f we directly use unar%Bnegate- C++ will not perform type inference and we'll have to spell out the template ar&uments ourselves! The pattern illustrated here ( havin& a template class and a template function to create it ( is common in li#rary code #ecause it lets li#rary clients use comple) classes without ever havin& to know how they're imple1 mented #ehind1the1scenes! Im&lementing ptr& un "ow that we've seen how not8 works- let's see if we can construct the $trBfun function! ,t a hi&h level $trBfun and not8 work the same way ( they each accept a parameter- construct a special functor class #ased on the parameter- then return it to the caller! The difference #etween not8 and $trBfun- however- is that there are two different versions of $trBfun ( one for unary functions and one for #inary functions! The two versions work almost identically and we'll see how to implement them #oth- #ut for simplicity we'll #e&in with the unary case! To convert a raw C++ unary function into an adapta#le unary function- we need to wrap it in a functor that inher1 its from the proper unar%Bfun*tion #ase class! 0e'll make this functor's o$erator() function simply call the stored function and return its value! To #e consistent with the namin& convention of the fun*tional) li#1 rary- we'll call the functor $ointerBtoBunar%Bfun*tion and will parameterize it over the ar&ument and re1 turn types of the function! This is shown here=
tem$late t%$ename Arg;%$e, t%$ename Cet;%$e) *lass $ointerBtoBunar%Bfun*tion3 public unary& unction.UrgTypeB CetType> { $ubli*3 e#plicit pointer&to&unary& unction!UrgType n!CetType$$ : unction! n$ *4 CetType operator!$ !const UrgTypeS param$ const * return unction!param$; 4 $rivate3 UrgType !R unction$!CetType$; #;
There isn't that much code here- #ut it's fairly dense! "otice that we inherit from unar%Bfun*tion Arg;%$e, Cet;%$e) so that the resultin& functor is adapta#le! ,lso note that the ar&ument and return types of o$erat, or() are considera#ly easier to determine than in the unar%Bnegate case #ecause they're specified as template ar&uments!
1 AE? 1
"ow- how can we implement $trBfun to return a correctly1constructed $ointerBtoBunar%Bfun*tion> Simple ( we *ust write a template function parameterized over ar&ument and return types- accept a function pointer of the appropriate type- then wrap it in a $ointerBtoBunar%Bfun*tion o#*ect! This is shown here=
tem$late t%$ename Arg;%$e, t%$ename Cet;%$e) $ointerBtoBunar%Bfun*tion Arg;%$e, Cet;%$e) $trBfun(CetType unction!UrgType$) { return pointer&to&unary& unction.UrgTypeB CetType>! unction$; #
This code is fairly dense- #ut &ets the *o# done! The implementation of $trBfun for #inary functions is similar to the implementation for unary functions! 0e'll create a template functor called $ointerBtoBbinar%Bfun*tion parameterized over its ar&ument and return types- then provide an implementation of $trBfun that constructs and returns an o#*ect of this type! This is shown here=
tem$late t%$ename Arg8, t%$ename ArgH, t%$ename Cet) *lass $ointerBtoBbinar%Bfun*tion3 public binary& unction.Urg7B Urg8B Cet> { $ubli*3 e7$li*it $ointerBtoBbinar%Bfun*tion(Cet fn(Arg8, ArgH)) 3 fun*tion(fn) {# Cet operator!$ !const Urg7S arg7B const Urg8S arg8$ const * return unction!arg7B arg8$; 4 $rivate3 Cet (2fun*tion)(Arg8, ArgH); #; tem$late t%$ename Arg8, t%$ename ArgH, t%$ename Cet) $ointerBtoBbinar%Bfun*tion Arg8, ArgH, Cet) $trBfun(Cet unction!Urg7B Urg8$) { return pointer&to&binary& unction.Urg7B Urg8B Cet>! unction$; #
"ote that we now have two versions of $trBfun ( one that takes in a unary function and one that takes in a #in1 ary function! This isn't a pro#lem- since the two template functions have different si&natures! Im&lementing bind7st To wrap up our tour of the fun*tional) li#rary- let's see how to implement bind8st! 'f you'll recallbind8st takes in a #inary adapta#le function and a value- then returns a new unary function e5ual to the input function with the first parameter locked in place! 0e'll follow the pattern of not8 and $trBfun #y writin& a template functor class called binder8st that actually does the #indin&- then havin& bind8st construct and re1 turn an o#*ect of the proper type! 7efore proceedin& with our implementation of binder8st- we need to take a 5uick detour into the inner work1 in&s of the binar%Bfun*tion class! ike unar%Bfun*tion- binar%Bfun*tion e)ports t%$edefs so that other parts of the fun*tional) li#rary can recover the ar&ument and return types of adapta#le functions! /owever- since a #inary function has two ar&uments- the names of the e)ported types are sli&htly different! binar%Bfun*tion provides the followin& three t%$edefs=
1 AE6 1
0e will need to reference each of these type names when writin& bind8st! "ow- how do we implement the binder8st functor> /ere is one possi#le implementation! The binder8st constructor will accept and store an adapta#le #inary function and the value for its first ar&ument! binder8st then provides an implementation of o$erator() that takes a sin&le parameter- then invokes the stored function passin& in the function parameter and the saved value! This is shown here=
)inding F-n&tor
Stor'd F-n&tion
Stor'd 4a*-' Param Param !
O-t,-t
In,-t
et's #e&in implementin& binder8st! The functor has to #e a template- since we'll #e storin& an ar#itrary ad1 apta#le function and value! /owever- we only need to parameterize the functor over the type of the #inary ad1 apta#le function- since we can determine the type of the first ar&ument from the adapta#le function's firstBar, gumentBt%$e! 0e'll thus #e&in with the followin& implementation=
template .typename (inaryFunction> class binder7st { "2 ... 2" #;
"ow- let's implement the constructor! 't should take in two parameters ( one representin& the #inary function and the other the value to lock into place! The first will have type Oinar%&un*tionR the second- t%$ename Oinar%&un*tion33firstBargumentBt%$e! This is shown here=
tem$late t%$ename Oinar%&un*tion) *lass binder8st { $ubli*3 binder7st!const (inaryFunctionS nB const typename (inaryFunction:: irst&argument&typeS arg$ : unction! n$B irst!arg$ *4 "2 ... 2" $rivate3 (inaryFunction unction; typename (inaryFunction:: irst&argument&type #;
irst;
ChewU That's 5uite a mouthful- #ut is the reality of much li#rary template code! ook at the declaration of the first data mem#er! Thou&h it may seem stran&e- this is the correct way to declare a data mem#er whose type is a type nested inside a template ar&ument!
1 AE@ 1
0e now have the constructor written and all that's left to take care of is o$erator()! Conceptually- this func1 tion isn't very difficult- and if we i&nore the parameter and return types have the followin& implementation=
tem$late t%$ename Oinar%&un*tion) *lass binder8st { $ubli*3 binder8st(*onst Oinar%&un*tion? fn, *onst t%$ename Oinar%&un*tion33firstBargumentBt%$e? arg) 3 fun*tion(fn), first(arg) {# TR ret RT operator!$ !const TR arg RTS param$ const * return unction! irstB param$; 4 $rivate3 Oinar%&un*tion fun*tion; t%$ename Oinar%&un*tion33firstBargumentBt%$e first;
#;
0hat are the ar&ument and return types for this function> 0ell- the function returns whatever o#*ect is produced #y the stored function- which has type t%$ename Oinar%&un*tion33resultBt%$e! The function accepts a value for use as the second parameter to the stored function- so it must have type t%$ename Oinar%&un*, tion33 se*ondBargumentBt%$e! This results in the followin& code=
tem$late t%$ename Oinar%&un*tion) *lass binder8st { $ubli*3 binder8st(*onst Oinar%&un*tion? fn, *onst t%$ename Oinar%&un*tion33firstBargumentBt%$e? arg) 3 fun*tion(fn), first(arg) {# typename (inaryFunction::result&type o$erator() (*onst typename (inaryFunction::second&argument&type? $aram) *onst { return fun*tion(first, $aram); # $rivate3 Oinar%&un*tion fun*tion; t%$ename Oinar%&un*tion33firstBargumentBt%$e first;
#;
0e're almost finished- and all that's left for binder8st is to make it adapta#le! Usin& the lo&ic from a#ovewe'll have it inherit from the proper instantiation of unar%Bfun*tion- as shown here=
1 AEE 1
tem$late t%$ename Oinar%&un*tion) *lass binder8st3 public unary& unction.typename (inaryFunction::second&argument&typeB typename (inaryFunction::result&type> { $ubli*3 binder8st(*onst Oinar%&un*tion? fn, *onst t%$ename Oinar%&un*tion33firstBargumentBt%$e? arg) 3 fun*tion(fn), first(arg) {# t%$ename Oinar%&un*tion33resultBt%$e o$erator() (*onst t%$ename Oinar%&un*tion33se*ondBargumentBt%$e? $aram) *onst { return fun*tion(first, $aram); # $rivate3 Oinar%&un*tion fun*tion; t%$ename Oinar%&un*tion33firstBargumentBt%$e first;
#; That's it for the binder8st class! ,s you can see- the code is dense and does a lot of ma&ic with t%$ename and nested types! 0ithout adapta#le functions- code like this would not #e possi#le! To finish up our discussion- let's implement bind8st! This function isn't particularly tricky- thou&h we do need to do a #it of work to e)tract the type of the value to lock in place=
tem$late t%$ename Oinar%&un*tion) binder8st Oinar%&un*tion) bind8st!const (inaryFunctionS nB const typename (inaryFunction:: irst&argument&typeS arg$ { return binder7st.(inaryFunction>! nB arg$; #
0e now have a complete workin& implementation of bind8st! 'f you actually open up the fun*tional) header and peek around inside- the code you'll find will pro#a#ly #ear a stron& resem#lance to what we've writ1 ten here! Limitations of the !unctional Library 0hile the ST functional li#rary is useful in a wide num#er of cases- the li#rary is unfortunately 5uite limited! fun*tional) only provides support for adapta#le unary and #inary functions- #ut commonly you'll encounter situations where you will need to #ind and ne&ate functions with more than two parameters! 'n these cases- one of your only options is to construct functor classes that accept the e)tra parameters in their constructors! Simil1 arly- there is no support for function composition- so we could not create a function that computes ; 7 + 1 #y call1 in& the appropriate com#ination of the $lus and multi$lies functors! /owever- the ne)t version of C++nicknamed 4C++0)-6 promises to have more support for functional pro&rammin& of this sort! 8or e)ample- it will provide a &eneral function called bind that lets you #ind as many values as you'd like to a function of ar#it1 rary arity! Keep your eyes peeled for the ne)t release of C++ ( it will #e far more functional than the current versionU
1 AE< 1
,lthou&h this is a C++ te)t- if you're interested in functional pro&rammin&- you mi&ht want to consider learnin& other pro&rammin& lan&ua&es like Scheme or /askell! 8unctional pro&rammin& is an entirely different way of thinkin& a#out computation- and if you're interested you should definitely consider e)pandin& your horizons with other lan&ua&es! The ever1popular class CS;B; KCro&rammin& an&ua&esL is a &reat place to look! -ractice -roblems 0e've covered a lot of pro&rammin& techni5ues in this chapter and there are no shorta&e of applications for the material! /ere are some pro#lems to &et you thinkin& a#out how functors and adapta#le functions can influence your pro&rammin& style= 1! The ST al&orithm forBea*h accepts as parameters a ran&e of iterators and a unary function- then calls the function on each ar&ument! Unusually- the return value of forBea*h is the unary function passed in as a parameter! 0hy mi&ht this #e> M ;! Usin& the fact that forBea*h returns the unary function passed as a parameter- write a function :%A*, *umulate that accepts as parameters a ran&e of ve*tor int)33iterators and an initial value- then returns the sum of all of the values in the ran&e- startin& at the specified value! 2o not use any loops ( instead- use forBea*h and a custom functor class that performs the addition! A! 0rite a function Advan*edOiased5ort that accepts as parameters a ve*tor string) and a string 4winner6 value- then sorts the ran&e- e)cept that all strin&s e5ual to the winner are at the front of the ve*tor! 2o not use any loops! <+int% 2se the ,TL sort algorithm and "unctor that stores the EwinnerF #arameter)= M B! Godify the a#ove implementation of Advan*edOiased5ort so that it works over an ar#itrary ran&e of iterators over strin&s- not *ust a ve*tor string)! Then modify it once more so that the iterators can iterate over any type of value! ?! The ST generate al&orithm is defined as void generate(&or'ard(terator start, &or, 'ard(terator end, Eullar%&un*tion fn) and iterates over the specified ran&e storin& the return value of the zero1parameter function fn as it &oes! 8or e)ample- callin& generate(v.begin(), v.end(), rand) would fill the ran&e Ov.begin() to v.end()L with random values! 0rite a func1 tion &illAs*ending that accepts an iterator ran&e- then sets the first element in the ran&e to zero- the second to one- etc! 2o not use any loops! 6! 0rite a function E7$unge6etter that accepts four parameters ( two iterators delineatin& an input ran&e of strings- one iterator delineatin& the start of an output ran&e- and a character ( then copies the strin&s in the input ran&e that do not contain the specified character into the output ran&e! The function should then return an iterator one past the last location written! 2o not use loops! <+int% 2se the remove_copy_if algorithm and a custom "unctor=! @! The standard de!iation of a set of data is a measure of how much the data varies from its avera&e value! 2ata with a small standard deviation tends to cluster around a point- while data with lar&e standard devi1 ation will #e more spread out! The formula for the standard deviation of a set of data a71- 7;- !!!- 7nb is
1 7 i 7 ; n i =1
/ere- 7 is the avera&e of the data points! To &ive a feelin& for this formula- &iven the data points 1- ;- A- the avera&e of the data points is ;- so the standard deviation is &iven #y
1 A<0 1
E! <! 10!
1 1 1 1 ; 7 i 7 ; = 7 i ; ;= 1 ; ; ; ; ; A; ; = 1 0 1= n i =1 A i=1 A A A
11!
1;!
0rite a function 5tandard9eviation that accepts an input ran&e of iterators over doubles Kor values implicitly converti#le to doublesL and returns its standard deviation! 2o not use any loops ( instead use the a**umulate function to compute the avera&e- then use a**umulate once more to compute the sum! <+int% To get the number o" elements in the range, you can use the distance "unction= L 0rite a function 4learAll5trings that accepts as input a ran&e of iterators over C strin&s that sets each strin& to #e the empty strin&! 'f you harness the fun*tional) li#rary correctly here- the function #ody will #e only a sin&le line of code! M Fewrite the 4learAll5trings function so that it works on a ran&e of C++ strings! ,s with the a#ove e)ercise- the function #ody can #e written in a sin&le line of code! M The FHT1;E cipher is a weak encryption cipher that works #y addin& 1;E to the value of each character in a strin& to produce a &ar#led strin&! Since *har can only hold ;?6 different values- two successive applications of FHT1;E will produce the ori&inal strin&! 0rite a function A$$l%C=;8HI that accepts a string and returns the string's FHT1;E cipher e5uivalent! 0rite a template function 4a$AtDalue that accepts a ran&e of iterators and a value #y reference1to1 *onst and replaces all elements in the ran&e that compare &reater than the parameter with a copy of the parameter! <+int% use the replace_if algorithm= M Hne piece of functionality missin& from the fun*tional) li#rary is the a#ility to #ind the first para1 meter of a unary function to form a nullary function! 'n this practice pro#lem- we'll implement a func1 tion called Oind=nl% that transforms a unary adapta#le function into a nullary function! a! 0rite a template functor class Oinder=nl% parameterized whose constructor accepts an adapta#le function and a value to #ind and whose o$erator() function calls the stored function passin& in the stored value as a parameter! .our class should have this interface=
tem$late t%$ename @nar%&un*tion) *lass Oinder=nl% { $ubli*3 (inder<nly!const UnaryFunctionS nB const typename UnaryFunction::argument&typeS )alue$; CetType operator!$ !$ const; #; #! 0rite a template function Oind=nl% that accepts the same parameters as the Oinder=nl% con1 structor and returns a Oinder=nl% of the proper type! The si&nature for this function should #e
template .typename UnaryFunction> (inder<nly.UnaryFunction> (ind<nly!const UnaryFunction S nB const typename UnaryFunction::argument&typeS )alue$; 1A! ,nother operation not supported #y the fun*tional) li#rary is "unction com#osition! 8or e)ample-
&iven two functions f and g- the composition g h f is a function such that g h fK)L Y gKfK)LL! 'n this e)1 ample- we'll write a function 4om$ose that lets us compose two unary functions of compati#le types! a! 0rite a template functor @nar%4om$ose parameterized over two adapta#le function types whose constructor accepts and stores two unary adapta#le functions and whose o$erator() accepts a sin&le parameter and returns the composition of the two functions applied to that ar&ument! Gake sure that @nar%4om$ose is an adapta#le unary function! #! 0rite a wrapper function 4om$ose that takes in the same parameters as @nar%4om$ose and returns a properly1constructed @nar%4om$ose o#*ect! c! D)plain how to implement not8 usin& 4om$ose and logi*alBnot- a unary adapta#le function e)1 ported #y fun*tional) that returns the lo&ical inverse of its ar&ument!
Forty years ago, goto-laden code was considered #er"ectly good #ractice) 6ow we stri!e to write structured control "lows) Twenty years ago, globally accessible data was considered #er"ectly good #ractice) 6ow we stri!e to enca#sulate data) Ten years ago, writing "unctions without thin*ing about the im#act o" e7ce#tions was considered good #ractice) 6ow we stri!e to write e7ce#tion-sa"e code) Time goes on) &e li!e) &e learn) ( Scott Geyers- author of E""ecti!e C++ and one of the leadin& e)perts on C++! OGey0?P 'n an ideal world- network connections would never fail- files would always e)ist and #e properly formattedusers would never type in malformed input- and computers would never run out of memory! Fealisticallythou&h- all of the a#ove can and will occur and your pro&rams will have to #e a#le to respond to them &racefully! 'n these scenarios- the normal function1call1and1return mechanism is not ro#ust enou&h to si&nal and report er1 rors and you will have to rely on e7ce#tion handling- a C++ lan&ua&e feature that redirects pro&ram control in case of emer&encies! D)ception handlin& is a comple) topic and will have far1reachin& effects on your C++ code! This chapter intro1 duces the motivation underlyin& e)ception handlin&- #asic e)ception1handlin& synta)- and some advanced tech1 ni5ues that can keep your code operatin& smoothly in an e)ception1filled environment! A Sim&le -roblem Up to this point- all of the pro&rams you've written have proceeded linearly ( they #e&in inside a special function called main- then proceed throu&h a chain of function calls and returns until KhopefullyL hittin& the end of main! 0hile this is perfectly accepta#le- it rests on the fact that each function- &iven its parameters- can perform a meanin&ful task and return a meanin&ful value! /owever- in some cases this simply isn't possi#le! Suppose- for e)ample- that we'd like to write our own version of the CS1067IJ 5tring;o(nteger functionwhich converts a string representation of an num#er into an int e5uivalent! Hne possi#le KpartialL imple1 mentation of 5tring;o(nteger mi&ht look like this=Q
int 5tring;o(nteger(*onst string ?in$ut) { stringstream *onverter(in$ut); int result; "" ;r% reading an int, fail if 'eAre unable to do so. *onverter )) result; if(*onverter.fail()) TT =hat should 6e do hereW *har leftover; "" 5ee if an%thingAs left over. *onverter )) leftover; if(0*onverter.fail()) return result; else TT =hat should 6e do hereW # Q This is #ased off of the Get(nteger function we covered in the chapter on streams! 'nstead of loopin& and repromptin& the user for input at each step- however- it simply reports errors on failure! (f so, fail.
1 A<; 1
'f the parameter in$ut is a string with a valid inte&er representation- then this function simply needs to per1 form the conversion! 7ut what should our function do if the parameter doesn't represent an inte&er> Hne pos1 si#le option- and the one used #y the CS1067IJ implementation of 5tring;o(nteger- is to call a function like Error that prints an error and terminates the pro&ram! This response seems a #it drastic and is a decidedly su#1 optimal solution for several reasons! 8irst- callin& Error doesn't &ive the pro&ram a chance to recover from the pro#lem! 5tring;o(nteger is a simple utility function- not a critical infrastructure component- and if it fails chances are that there's an ele&ant way to deal with the pro#lem! 8or e)ample- if we're usin& 5tring;o(n, teger to convert user input in a te)t #o) into an inte&er for further processin&- it makes far more sense to re1 prompt the user than to terminate the pro&ram! Second- in a very lar&e or complicated software system- it seems silly to terminate the pro&ram over a simple strin& error! 8or e)ample- if this 5tring;o(nteger function were used in an email client to convert a strin& representation of a time to an inte&er format Kparsin& the hours and minutes separatelyL- it would #e disastrous if the pro&ram crashed whenever receivin& malformed emails! 'n es1 sence- while usin& a function like Error will prevent the pro&ram from continuin& with &ar#a&e values- it is simply too drastic a move to use in serious code! This approach su&&ests a second option- one common in pure C ( sentinel !alues! The idea is to have functions return special values meanin& 4this value indicates that the function failed to e)ecute correctly!6 'n our case- we mi&ht want to have 5tring;o(nteger return 11 to indicate an error- for e)ample! Compared with the 4drop everythin&6 approach of Error this may seem like a &ood option M it reports the error and &ives the callin& func1 tion a chance to respond! /owever- there are several ma*or pro#lems with this method! 8irst- in many cases it is not possi#le to set aside a value to indicate failure! 8or e)ample- suppose that we choose to reserve 11 as an er1 ror code for 5tring;o(nteger! 'n this case- we'd make all of our calls to 5tring;o(nteger as
if(5tring;o(nteger(m%<aram) == ,8) { "2 ... handle error ... 2" #
7ut what happens if the input to 5tring;o(nteger is the strin& .,8.> 'n this case- whether or not the 5tring;o(nteger function completes successfully- it will still return 11 and our code mi&ht confuse it with an error case! ,nother serious pro#lem with this approach is that if each function that mi&ht possi#ly return an error has to re1 serve sentinel values for errors- we mi&ht accidentally check the return value of one function a&ainst the error code of another function! 'ma&ine if there were several constants floatin& around named ECC=CF 5;A;@5BEC, C=CF (EDA6(9BCE5@6;- etc!- and whenever you called a function you needed to check the return value a&ainst the correct one of these choices! 'f you chose incorrectly- even with the #est of intentions your error1checkin& would #e invalid! .et another shortcomin& of this approach is that in some cases it will #e impossi#le to reserve a value for use as a sentinel! 8or e)ample- suppose that a function returns a ve*tor double)! 0hat special ve*tor double) should we choose to use as a sentinel> /owever- the most serious pro#lem with the a#ove approach is that you as a pro&rammer can i&nore the return value without encounterin& any warnin&s! Dven if 5tring;o(nteger returns a sentinel value indicatin& an er1 ror- there are no compile1time or runtime warnin&s if you choose not to check for a return value! 'n the case of 5tring;o(nteger this may not #e that much of a pro#lem ( after all- holdin& a sentinel value instead of a meanin&ful value will not immediately crash the pro&ram ( #ut this can lead to pro#lems down the line that can snow#all into fully1fled&ed crashes! 0orse- since the crash will pro#a#ly #e caused #y errors from far earlier in the code- these sorts of pro#lems can #e ni&htmarish to de#u&! Surprisin&ly- e)perience shows that many pro1 &rammers ( either out of ne&li&ence or laziness ( for&et to check return values for error codes and snow#all ef1 fects are rather common! 0e seem to have reached an unsolva#le pro#lem! 0e'd like an error1handlin& system that- like Error- prevents the pro&ram from continuin& normally when an error occurs! ,t the same time- however- we'd like the ele&ance
1 A<A 1
of sentinel values so that we can appropriately process an error! /ow can we com#ine the stren&ths of #oth of these approaches into a sin&le system> $%ce&tion 0andling The reason the a#ove e)ample is such a pro#lem is that the normal C++ function1call1and1return system simply isn't ro#ust enou&h to communicate errors #ack to the callin& function! To resolve this pro#lem- C++ provides lan&ua&e support for an error1messa&in& system called e7ce#tion handling that completely #ypasses function1 call1and1return! 'f an error occurs inside a function- rather than returnin& a value- you can report the pro#lem to the e)ception handlin& system to *ump to the proper error1handlin& code! The C++ e)ception handlin& system is #roken into three parts ( tr% #locks- *at*h #locks- and thro' state1 ments! tr% #locks are simply re&ions of code desi&nated as areas that runtime errors mi&ht occur! To declare a tr% #lock- you simply write the keyword try- then surround the appropriate code in curly #races! 8or e)amplethe followin& code shows off a tr% #lock=
try { # *out .(Am in a tr% blo*+0. endl;
'nside of a tr% #lock- code e)ecutes as normal and *umps to the code directly followin& the tr% #lock once fin1 ished! /owever- at some point inside a tr% #lock your pro&ram mi&ht run into a situation from which it cannot normally recover ( for e)ample- a call to 5tring;o(nteger with an invalid ar&ument! 0hen this occurs- you can report the error #y usin& the thro- keyword to 4throw6 the e)ception into the nearest matchin& *at*h clause! ike return- thro' accepts a sin&le parameter that indicates an o#*ect to throw so that when handlin& the e)ception your code has access to e)tra information a#out the error! 8or e)ample- here are three statements that each throw o#*ects of different types=
thro6 -; thro6 ne6 )ector.double>; thro6 D.7J7IK; "" ;hro' an int "" ;hro' a ve*tor double) 2 "" ;hro' a double
0hen you throw an e)ception- it can #e cau&ht #y a *at*h clause specialized to catch that error! *at*h clauses are defined like this=
catch!ParameterType param$ { "2 Error,handling *ode 2" #
/ere- <arameter;%$e represents the type of varia#le this *at*h clause is capa#le of catchin&! *at*h #locks must directly follow tr% #locks- and it's ille&al to declare one without the other! Since catch clauses are special1 ized for a sin&le type- it's perfectly le&al to have cascadin& catch clauses- each desi&ned to pick up a different type of e)ception! 8or e)ample- here's code that catches e)ceptions of type int- ve*tor int)- and string=
1 A<B 1
tr% {
"" 9o something # catch!int myInt$ { "" (f the *ode thro's an int, e7e*ution *ontinues here. # catch!const )ector.int>S my5ector$ { "" =ther'ise, if the *ode thro's a ve*tor int), e7e*ution resumes here. # catch!const stringS myString$ { "" 5ame for string #
"ow- if the code inside the tr% #lock throws an e)ception- control will pass to the correct *at*h #lock! .ou can visualize e)ception handlin& as a room of people and a #all! The code inside the tr% #lock #e&ins with the #all and continues talkin& as lon& as possi#le! 'f an error occurs- the tr% #lock throws the #all to the appropriate *at*h handler- which #e&ins e)ecutin&! et's return to our earlier e)ample with 5tring;o(nteger! 0e want to si&nal an error in case the user enters an invalid parameter- and to do so we'd like to use e)ception handlin&! The 5uestion- thou&h- is what type of o#1 *ect we should throw! 0hile we can choose whatever type of o#*ect we'd like- C++ provides a header filestde7*e$t)- that defines several classes that let us specify what error tri&&ered the e)ception! Hne of theseinvalidBargument- is ideal for the situation! invalidBargument accepts in its constructor a string para1 meter containin& a messa&e representin& what type of error occurred- and has a mem#er function called 'hat that returns what the error was!Q 0e can thus rewrite the code for 5tring;o(nteger as
int 5tring;o(nteger(*onst string? in$ut) { stringstream *onverter(in$ut); int result; "" ;r% reading an int, fail if 'eAre unable to do so. *onverter )) result; if(*onverter.fail()) thro6 in)alid&argument!";annot parse " 3 input 3 " as an integer."$; *har leftover; "" 5ee if an%thingAs left over. (f so, fail. *onverter )) leftover; if(0*onverter.fail()) return result; else thro6 in)alid&argument!string!"Une#pected character: "$ 3 le to)er$;
"otice that while the function itself does not contain a tr%I*at*h pair- it nonetheless has a thro' statement! 'f this statement is e)ecuted- then C++ will step #ackwards throu&h all callin& functions until it finds an appropri1 ate *at*h statement! 'f it doesn't find one- then the pro&ram will halt with a runtime error! "ow- we can write code usin& 5tring;o(nteger that looks like this=
'hat
is a poor choice of a name for a mem#er function! Clease make sure to use more descriptive names in your codeU
1 A<? 1
/ere- if 5tring;o(nteger encounters an error and throws an e)ception- control will *ump out of the tr% #lock into the *at*h clause specialized to catch o#*ects of type invalidBargument! Htherwise- code contin1 ues as normal in the tr% #lock- then skips over the *at*h clause to print 4.ayU 0e're done!6 There are several thin&s to note here! 8irst- if 5tring;o(nteger throws an e)ception- control immediately #reaks out of the tr% #lock and *umps to the *at*h clause! Unlike the pro#lems we had with our earlier ap1 proach to error handlin&- here- if there is a pro#lem in the tr% #lock- we're &uaranteed that the rest of the code in the tr% #lock will not e)ecute- preventin& runtime errors stemmin& from malformed o#*ects! Second- if there is an e)ception and control resumes in the *at*h clause- once the *at*h #lock finishes runnin&- control does not resume #ack inside the tr% #lock! 'nstead- control resumes directly followin& the tr%I*at*h pair- so the pro1 &ram a#ove will print out 4.ayU 0e're done!6 once the *at*h #lock finishes e)ecutin&! 0hile this mi&ht seem unusual- remem#er that the reason for e)ception handlin& in the first place is to halt code e)ecution in spots where no meanin&ful operation can #e defined! Thus if control leaves a tr% #lock- chances are that the rest of the code in the tr% could not complete without errors- so C++ does not provide a mechanism for resumin& pro1 &ram control! Third- note that we cau&ht the invalidBargument e)ception #y reference K*onst invalidBargument? instead of invalidBargumentL! ,s with parameter1passin&- e)ception1catchin& can take values either #y value or #y reference- and #y acceptin& the parameter #y reference you can avoid makin& an unnecessary copy of the thrown o#*ect! A /ord on Sco&e D)ception handlin& is an essential part of the C++ pro&rammin& lan&ua&e #ecause it provides a system for re1 coverin& from serious errors! ,s its name implies- e)ception handlin& should #e used only for e7ce#tional cir1 cumstances ( errors out of the ordinary that necessitate a ma*or chan&e in the flow of control! 0hile you can use e)ception handlin& as a fancy form of function call and return- it is hi&hly recommended that you avoid doin& so! Throwin& an e)ception is much slower than returnin& a value #ecause of the e)tra #ookkeepin& re5uired- so #e sure that you're only usin& e)ception handlin& for serious pro&ram errors! ,lso- the e)ception handlin& system will only respond when manually tri&&ered! Unless a code snippet e)pli1 citly thro's a value- a *at*h #lock cannot respond to it! This means that you cannot use e)ception handlin& to prevent your pro&ram from crashin& from se&mentation faults or other pointer1#ased errors- since pointer errors result in operatin&1system level process termination- not C++1level e)ception handlin&!Q -rogramming with $%ce&tion 0andling 0hile e)ception handlin& is a ro#ust and ele&ant system- it has several sweepin& implications for C++ code! Gost nota#ly- when usin& e)ception handlin&- unless you are a#solutely certain that the classes and functions you use never throw e)ceptions- you must treat your code as thou&h it mi&ht throw an e)ception at any point! 'n other words- you can never assume that an entire code #lock will #e completed on its own- and should #e pre1
Q 'f you use Gicrosoft's Tisual Studio development environment- you mi&ht notice that various errors like null1pointer dereferences and stack overflows result in errors that mention 4unhandled e)ception6 in their description! This is a Gi1 crosoft1specific feature and is different from C++'s e)ception1handlin& system!
1 A<6 1
pared to handle cases where control #reaks out of your functions at inopportune times! 8or e)ample- consider the followin& function=
void 5im$le&un*tion() { *har2 m%45tring = ne' *har[8HI]; 9o5omething(m%45tring); delete [] m%45tring; #
/ere- we allocate space for a C strin&- pass it to a function- then deallocate the memory! 0hile this code seems totally safe- when you introduce e)ceptions into the mi)- this code can #e very dan&erous! 0hat happens- for e)ample- if 9o5omething throws an e)ception> 'n this case- control would *ump to the nearest *at*h #lock and the line delete [] m%45tring would never e)ecute! ,s a result- our pro&ram will leak 1;E #ytes of memory! 'f this pro&ram runs over a sufficiently lon& period of time- eventually we will run out of memory and our pro&ram will crash! There are three main ways that we can avoid these pro#lems! 8irst- it's completely accepta#le to *ust avoid e)1 ception1handlin& all toðer! This approach mi&ht seem like a cop1out- #ut it is a completely valid option that many C++ developers choose! Several ma*or software pro*ects written in C++ do not use e)ception handlin&partially #ecause of the e)tra difficulties encountered when usin& e)ceptions! /owever- this approach results in code that runs into the same pro#lems discussed earlier in this chapter with 5tring;o(nteger ( functions can only communicate errors throu&h return values and pro&rammers must #e e)tra vi&ilant to avoid i&norin& return values! The second approach to writin& e)ception1safe code uses a techni5ue called 4catch1and1rethrow!6 et's return to the a#ove code e)ample with a dynamically1allocated character #uffer! 0e'd like to &uarantee that the C strin& we've allocated &ets deallocated- #ut as our code is currently written- it's difficult to do so #ecause the 9o5omething function mi&ht throw an e)ception and interrupt our code flow! 'f there is an e)ception- what if we were a#le to somehow intercept that e)ception- clean up the #uffer- and then propa&ate the e)ception outside of the 5im$le&un*tion function> 8rom an outside perspective- it would look as if the e)ception had come from inside the 9o5omething function- #ut in reality it would have taken a 5uick stop inside 5im$le&un*tion #efore proceedin& outwards! The reason this method works is that it is legal to throw an e7ce#tion "rom inside a catch bloc*! ,lthou&h *at*h #locks are usually reserved for error handlin&- there is nothin& preventin& us from throwin& the e)ception we catch! 8or e)ample- this code is completely le&al=
tr% { tr% { 9o5omething(); # *at*h(*onst invalidBargument? error) { *out .(nner blo*+3 Error3 . error.'hat() thro6 error; "" <ro$agate the error out'ard #
endl;
error.'hat()
endl;
1 A<@ 1
/ere- if the 9o5omething function throws an e)ception- it will first #e cau&ht #y the innermost tr% #lockwhich prints it to the screen! This *at*h handler then throws error a&ain- and this time it is cau&ht #y the out1 ermost *at*h #lock! 0ith this techni5ue- we can almost rewrite our 5im$le&un*tion function to look somethin& like this=
void 5im$le&un*tion() { *har2 m%45tring = ne' *har[8HI]; "2 ;r% to 9o5omething. (f it fails, *at*h the e7*e$tion and rethro' it. 2" try { 9o5omething(m%45tring); # catch (TR =hat to catchW RT) { delete 9: my;String; thro6 TR =hat to thro6W RT; # "2 Eote that if there is no e7*e$tion, 'e still need to *lean things u$. 2" delete [] m%45tring;
There's a #it of a pro#lem here ( what sort of e)ceptions should we catch> Suppose that we know every sort of e)ception 9o5omething mi&ht throw! 0ould it #e a &ood idea to write a *at*h #lock for each one of these types> ,t first this may seem like a &ood idea- #ut it can actually cause more pro#lems than it solves! 8irst- in each of the *at*h #locks- we'd need to write the same delete [] statement! 'f we were to make chan&es to the 5im$le&un*tion function that necessitated more cleanup code- we'd need to make pro&ressively more chan&es to the 5im$le&un*tion *at*h cascade- increasin& the potential for errors! ,lso- if we for&et to catch a specific type of error- or if 9o5omething later chan&es to throw more types of errors- then we mi&ht miss an opportunity to catch the thrown e)ception and will leak resources! Clus- if we don't know what sorts of e)cep1 tions 9o5omething mi&ht throw- this entire approach will not work! The pro#lem is that in this case- we want to tell C++ to catch anything that's thrown as an e)ception! 0e don't care a#out what the type of the e)ception is- and need to intercept the e)ception simply to ensure that our re1 source &ets cleaned up! 8ortunately- C++ provides a mechanism specifically for this purpose! To catch an e)1 ception of any type- you can use the special synta) catch(...)- which catches any e)ception! Thus we'll have the *at*h clause inside 9o5omething #e a *at*h(...) clause- so that we can catch any type of e)ception that 9o5omething mi&ht throw! 7ut this causes another pro#lem= we'd like to rethrow the e)ception- #ut since we've used a *at*h(...) clause- we don't have a name for the specific e)ception that's #een cau&ht! 8ortu1 nately- C++ has a special use of the thro' statement that lets you throw the current e)ception that's #ein& pro1 cessed! The synta) is
thro6;
That is- a lone thro' statement with no parameters! 7e careful when usin& thro';- however- since if you're not inside of a *at*h #lock the pro&ram will crashU The final version of 5im$le&un*tion thus looks like this=
1 A<E 1
void 5im$le&un*tion() { *har2 m%45tring = ne' *har[8HI];
"2 ;r% to 9o5omething. (f it fails, *at*h the e7*e$tion and rethro' it. 2" tr% { 9o5omething(m%45tring); # catch !...$ { delete [] m%45tring; thro6; # "2 Eote that if there is no e7*e$tion, 'e still need to *lean things u$. 2" delete [] m%45tring;
,s you can tell- the 4catch1and1rethrow6 approach to e)ception handlin& results in code that can #e rather com1 plicated! 0hile in some circumstances catch1and1rethrow is the #est option- in many cases there's a much #etter alternative that results in concise- reada#le- and thorou&hly e)ception1safe code ( o#*ect memory mana&ement! 2b?ect #emory #anagement and 1AII C++'s memory model is #est descri#ed as 4dan&erously efficient!6 Unlike other lan&ua&es like $ava- C++ does not have a &ar#a&e collector and conse5uently you must manually allocate and deallocate memory! ,t first- this mi&ht seem like a simple task ( *ust delete anythin& you allocate with ne'- and make sure not to delete somethin& twice! /owever- it can #e 5uite difficult to keep track of all of the memory you've allocated in a pro1 &ram! ,fter all- you pro#a#ly won't notice any symptoms of memory leaks unless you run your pro&rams for hours on end- and in all likelihood will have to use a special tool to check memory usa&e! .ou can also run into trou#le where two o#*ects each point to a shared o#*ect! 'f one of the o#*ects isn't careful and accidentally de, letes the memory while the other one is still accessin& it- you can &et some particularly nasty runtime errors where seemin&ly valid data has #een corrupted! The situation &ets all the more complicated when you introduce e)ception1handlin& into the mi)- where the code to delete allocated memory mi&ht not #e reached #ecause of an e)ception! 'n some cases havin& a hi&h de&ree of control over memory mana&ement can #e 5uite a #oon to your pro&ram1 min&- #ut much of the time it's simply a hassle! 0hat if we could somehow &et C++ to mana&e our memory for us> 0hile #uildin& a fully1functional &ar#a&e collection system in C++ would #e *ust short of impossi#le- usin& only #asic C++ concepts it's possi#le to construct an e)cellent appro)imation of automatic memory mana&e1 ment! The trick is to #uild smart #ointers- o#*ects that ac5uire a resource when created and that clean up the re1 source when destroyed! That is- when the o#*ects are constructed- they wrap a newly1allocated pointer inside an o#*ect shell that cleans up the mess when the o#*ect &oes out of scope! Com#ined with features like operator overloadin&- it's possi#le to create slick smart pointers that look almost e)actly like true C++ pointers- #ut that know when to free unused memory! The C++ header file memor%) e)ports the autoB$tr type- a smart pointer that accepts in its constructor a pointer to dynamically1allocated memory and whose constructor calls delete on the resource!Q autoB$tr is a template class whose template parameter indicates what type of o#*ect the autoB$tr will 4point6 at! 8or e)1 ample- an autoB$tr string) is a smart pointer that points to a string! 7e careful ( if you write autoB$tr string 2)- you'll end up with an autoB$tr that points to a string 2- which is similar to a string 22! Throu&h the ma&ic of operator overloadin&- you can use the re&ular dereference and arrow operat1
Q "ote that autoB$tr calls delete- not delete []- so you cannot store dynamically1allocated arrays in want the functionality of an array with automatic memory mana&ement- use a ve*tor!
autoB$tr!
'f you
1 A<< 1
ors on an autoB$tr as thou&h it were a re&ular pointer! 8or e)ample- here's some code that dynamically alloc1 ates a ve*tor int)- stores it in an autoB$tr- and then adds an element into the ve*tor=
"2 /ave the autoB$tr $oint to a ne'l%,allo*ated ve*tor int). ;he *onstru*tor 2 is e7$li*it, so 'e must use $arentheses. 2" auto&ptr.)ector.int> > managed5ector!ne6 )ector.int>$; managed5ector->$ushBba*+(8FG); "" Add 8FG to the end of the ve*tor. (Rmanaged5ector)[0] = JH; "" 5et element 0 b% dereferen*ing the $ointer.
0hile in many aspects autoB$tr acts like a re&ular pointer with automatic deallocation- autoB$tr is funda1 mentally different from re&ular pointers in assi&nment and initialization! Unlike o#*ects you've encountered up to this point- assi&nin& or initializin& an autoB$tr to hold the contents of another destructively modifies the source autoB$tr! Consider the followin& code snippet=
autoB$tr int) one(ne' int); autoB$tr int) t'o; t'o = one;
,fter the final line e)ecutes- t'o will hold the resource ori&inally owned #y one- and one will #e empty! 2ur1 in& the assi&nment- one relin5uished ownership of the resource and cleared out its state! Conse5uently- if you use one from this point forward- you'll run into trou#le #ecause it's not actually holdin& a pointer to anythin&! 0hile this is hi&hly counterintuitive- it has several advanta&es! 8irst- it ensures that there can #e at most one autoB$tr to a resource- which means that you don't have to worry a#out the contents of an autoB$tr #ein& cleaned up out from underneath you #y another autoB$tr to that resource! Second- it means that it's safe to re1 turn autoB$trs from functions without the resource &ettin& cleaned up! 0hen returnin& an autoB$tr from a function- the ori&inal copy of the autoB$tr will transfer ownership to the new autoB$tr durin& return1value initialization- and the resource will #e transferred safely! Q 8inally- #ecause each autoB$tr can assume that it has sole ownership of the resource- autoB$tr can #e implemented e)tremely efficiently and has almost zero overhead! ,s a conse5uence of the 4autoB$tr assi&nment is transference6 policy- you must #e careful when passin& an autoB$tr #y value to a function! Since the parameter will #e initialized to the ori&inal o#*ect- it will empty the ori&inal autoB$tr! Similarly- you should not store autoB$trs in ST containers- since when the containers reallocate or #alance themselves #ehind the scenes they mi&ht assi&n autoB$trs around in a way that will tri&1 &er the o#*ect destructors! 8or reference- here's a list of the mem#er functions of the autoB$tr template class=
Q 8or those of you interested in pro&rammin& lan&ua&e desi&n- C++ uses what's known as co#y semantics for most of its operations- where assi&nin& o#*ects to one another creates copies of the ori&inal o#*ects! autoB$tr seems stran&e #e1 cause it uses mo!e semantics- where assi&nin& autoB$trs to one another transfers ownership of some resource! Gove semantics are not easily e)pressed in C++ and the code to correctly implement autoB$tr is surprisin&ly comple) and re1 5uires an intricate understandin& of the C++ lan&ua&e! The ne)t revision of C++- C++0)- will add several new features to the lan&ua&e to formalize and simply move semantics and will replace autoB$tr with uniVueB$tr- which formalizes the move semantics!
1 B00 1
e7$li*it autoB$tr (;%$e2 resour*e)
Constructs a new autoB$tr wrappin& the specified pointer- which must #e from dynamically1allocated memory!
autoB$tr(autoB$tr? other) autoB$tr int) one(ne' int); autoB$tr int) t'o = one;
Constructs a new autoB$tr that ac5uires resource ownership from the autoB$tr used in the initialization! ,fterwards- the old autoB$tr will not encapsulate any dynamically1allocated memory!
;? o$erator 2() *onst 2m%Auto<tr = 8FG;
2ereferences the stored pointer and returns a reference to the memory it's pointin& at!
;2 o$erator,) () *onst m%5tringAuto<tr,)a$$end(.4!!0.);
Felin5uishes control of the stored resource and returns it so it can #e stored in another location! The autoB$tr will then contain a E@66 point1 er and will not mana&e the memory any more!
void reset(;2 $tr = E@66) m%<tr.reset(); m%<tr.reset(ne' int);
Feleases any stored resources and optionally stores a new resource inside the autoB$tr!
;2 get() *onst 5ome&un*tion(m%<tr.get()); "" Cetrieve stored resour*e
Feturns the stored pointer! Useful for passin& the mana&ed re1 source to other functions! Hf course- dynamically1allocated memory isn't the only C++ resource that can #enefit from o#*ect memory man1 a&ement! 8or e)ample- when workin& with HS1specific li#raries like Gicrosoft's 0inA; li#rary- you will com1 monly have to manually mana&e handles to system resources! 'n spots like these- writin& wrapper classes that act like autoB$tr #ut that do cleanup usin& methods other than a plain delete can #e 5uite #eneficial! 'n factthe system of havin& o#*ects mana&e resources throu&h their constructors and destructors is commonly referred to as resource acAuisition is initiali:ation- or simply F,''! $%ce&tions and Smart -ointers Up to this point- smart pointers mi&ht seem like a curiosity- or perhaps a useful construct in a limited num#er of circumstances! /owever- when you introduce e)ception handlin& to the mi)- smart pointers will #e invalua#le! 'n fact- in professional code where e)ceptions can #e thrown at almost any point- smart pointers are almost as u#i5uitous as re&ular C++ pointers! et's suppose you're &iven the followin& linked list cell struct=
stru*t node; { int data; node; 2ne7t; #;
1 B01 1
This function allocates a new node; cell- then tells it to hold on to the value returned #y 5ome4om$li*ated, &un*tion! 'f we i&nore e)ception handlin&- this code is totally fine- provided of course that the callin& function correctly holds on to the node; 2 pointer we return! /owever- when we add e)ception handlin& to the mi)- this function is a recipe for disaster! 0hat happens if 5ome4om$li*ated&un*tion throws an e)ception> Since GetEe'4ell doesn't have an associated tr% #lock- the pro&ram will a#ort GetEe'4ell and search for the nearest *at*h clause! Hnce the *at*h finishes e)ecutin&- we have a pro#lem ( we allocated a node; o#*ect#ut we didn't clean it up! 0orse- since GetEe'4ell is no lon&er runnin&- we've lost track of the node; entirelyand the memory is orphaned! Dnter autoB$tr to save the day! Suppose we chan&e the declaration node;2 ne'4ell to autoB$tr node;) ne'4ell! "ow- if 5ome4om$li*ated&un*tion throws an e)ception- we won't leak any memory since when the autoB$tr &oes out of scope- it will reclaim the memory for us! 0onderfulU Hf course- we also need to chan&e the last line from return ne'4ell to return ne'4ell.release()- since we promised to return a node; 2- not an autoB$tr node;)! The new code is printed #elow=
node;2 GetEe'4ell() { auto&ptr.nodeT> ne6;ell!ne6 nodeT$; ne'4ell,)ne7t = E@66; ne'4ell,)data = 5ome4om$li*ated&un*tion(); return ne6;ell.release!$; "" ;ell the autoB$tr to sto$ managing memor%. #
This function is now wonderfully e)ception1safe thanks to autoB$tr! Dven if we prematurely e)it the function from an e)ception in 5ome4om$li*ated&un*tion- the autoB$tr destructor will ensure that our resources are cleaned up! /owever- we can make this code even safer #y usin& the autoB$tr in yet another spot! 0hat hap1 pens if we call GetEe'4ell #ut don't store the return value anywhere> 8or e)ample- suppose we have a func1 tion like this=
void 5ill%&un*tion() { GetEe'4ell(); "" =h dear, there goes the return value. #
0hen we wrote GetEe'4ell- we tacitly assumed that the callin& function would hold on to the return value and clean the memory up at some later point! /owever- it's totally le&al to write code like 5ill%&un*tion that calls GetEe'4ell and entirely discards the return value! This leads to memory leaks- the very pro#lem we were tryin& to solve earlier! 8ortunately- throu&h some creative use of autoB$tr- we can eliminate this pro#lem! Consider this modified version of GetEe'4ell=
1 B0; 1
auto&ptr.nodeT> GetEe'4ell() { autoB$tr node;) ne'4ell(ne' node;); ne'4ell,)ne7t = E@66; ne'4ell,)data = 5ome4om$li*ated&un*tion(); return ne'4ell; "" 5ee belo' #
/ere- the function returns an autoB$tr- which means that the returned value is itself mana&ed! "ow- if we call 5ill%&un*tion- even thou&h we didn't &ra# the return value of GetEe'4ell- #ecause GetEe'4ell returns an autoB$tr- the memory will still &et cleaned up! #ore to $%&lore D)ception1handlin& and F,'' are comple) topics that have impressive ramifications for the way that your write C++ code! /owever- we simply don't have time to cover every facet of e)ception handlin&! 'n case you're inter1 ested in e)plorin& more advanced topics in e)ception handlin& and F,''- consider lookin& into the followin&= 1! The Standard $%ce&tion Classes= 'n this chapter we discussed invalidBargument- one of the many e)ception classes availa#le in the C++ standard li#rary! /owever- there are several more e)ception classes that form an ela#orate hierarchy! Consider readin& into some of the other classes ( some of them even show up in the ST U ;! $%ce&tion S&ecifications! 7ecause functions can throw e)ceptions at any time- it can #e difficult to de1 termine which pieces of code can and cannot throw e)ceptions! 8ortunately- C++ has a feature called an e7ce#tion s#eci"ication which indicates what sorts of e)ceptions a function is allowed to throw! 0hen an e)ception leaves a function with an e)ception specification- the pro&ram will a#ort unless the type of the e)ception is one of the types mentioned in the specification! A! !unction try ,locks! There is a variant of a re&ular try #lock that lets you put the entire contents of a function into a tryIcatch handler pair! /owever- it is a relatively new feature in C++ and is not supported #y several popular compilers! Check a reference for more information! B! ne6 and $%ce&tions! 'f your pro&ram runs out of availa#le memory- the ne' operator will indicate a failure #y throwin& an e)ception of type badBallo*! 0hen desi&nin& custom container classes- it mi&ht #e worth checkin& a&ainst this case and actin& accordin&ly! ?! The ,oost Smart -ointers= 0hile autoB$tr is useful in a wide variety of circumstances- in many as1 pects it is limited! Hnly one autoB$tr can point to a resource at a time- and autoB$trs cannot #e stored inside of ST containers! The 7oost C++ li#raries conse5uently provide a hu&e num#er of smart pointers- many of which employ considera#ly more complicated resource1mana&ement systems than autoB$tr! Since many of these smart pointers are likely to #e included in the ne)t revision of the C++ standard- you should #e sure to read into them! 7*arne Stroustrup Kthe inventor of C++L wrote an e)cellent introduction to e)ception safety- focusin& mostly on implementations of the C++ Standard i#rary! 'f you want to read into e)ception1safe code- you can read it on1 line at http=IIwww!research!att!comIi#sIArdVsafe!pdf! ,dditionally- there is a most e)cellent reference on autoB$tr availa#le at http=IIwww!&otw!caIpu#licationsIusin&VautoVptrVeffectively!htm that is a &reat resource on the su#*ect!
1 B0A 1
1! 0hat happens if you put a *at*h(...) handler at the top of a *at*h cascade> M ;! D)plain why the autoB$tr constructor is marked e7$li*it! <+int% 0i!e an e7am#le o" an error you can ma*e i" the constructor is not mar*ed explicit=) A! The 5im$le&un*tion function from earlier in this chapter ran into difficulty with e)ception1safety #e1 cause it relied on a manually1mana&ed C strin&! D)plain why this would not #e a pro#lem if it instead used a C++ string! B! Consider the followin& C++ function=
void :ani$ulate5ta*+(sta*+ string)? m%5ta*+) { if(m%5ta*+.em$t%()) thro' invalidBargument(.Em$t% sta*+0.); string to$Elem = m%5ta*+.to$(); m%5ta*+.$o$(); "2 ;his might thro' an e7*e$tion0 2" 9o5omething(m%5ta*+); m%5ta*+.$ush(to$Elem); #
This function accepts as input a C++ sta*+ string)- pops off the top element- calls the 9o5omething function- then pushes the element #ack on top! Crovided that the 9o5omething function doesn't throw an e)ception- this code will &uarantee that the top element of the sta*+ does not chan&e #efore and after the function e)ecutes! Suppose- however- that we wanted to a#solutely &uarantee that the top element of the stack never chan&es- even if the function throws an e)ception! Usin& the catch1 and1rethrow strate&y- e)plain how to make this the case! M ?! 0rite a class called Automati*5ta*+:anager whose constructor accepts a sta*+ string) and pops off the top element Kif one e)istsL and whose destructor pushes the element #ack onto the sta*+! Usin& this class- rewrite the code in Cro#lem B so that it's e)ception safe! /ow does this version of the code compare to the approach usin& catch1and1rethrow> M 6! 0rite a template class Ensure4alled parameterized over an ar#itrary nullary function type Ki!e! zero1 parameter functionL whose constructor accepts a nullary function and whose destructor calls this func1 tion! This is a &eneralization of Automati*5ta*+:anager class you *ust wrote! Should Ensure, 4alled have copy semantics- or should it #e uncopya#le> 0hy>
inear e5uations and matrices are at the heart of many techni5ues in computer science! Garkov processeswhich underlie 3oo&le's Ca&eFank al&orithm- can #e descri#ed and manipulated usin& matrices- and modern A2 &raphics use matrices e)tensively to transform points in space into pi)els on a screen! 'n this e)tended e)ample- we'll e)plore systems of linear e5uations and write a pro&ram to solve them usin& 0auss-Mordan elimination! 0e'll also see how to use this techni5ue to efficiently invert ar#itrary s5uare matrices! 'n the process- we'll &et the chance to play around with the ST fun*tional) li#rary- our home&rown grid class- and even a #it of e)ception handlin&! Systems of Linear $Iuations , linear eAuation is a sum or difference of terms where every term is either a scalar or the product of a varia#le and a scalar! 8or e)ample- the formula for a line in two1dimensional space- y Y m7 + #- is a linear e5uation with varia#les 7 and y and constants m and #R this is the reason for the terminolo&y 4linear e5uation!6 The formula 7; + y; Y r; is nonlinear #ecause 7; and y; are varia#les raised to the second power- and the formula 7y Y k is non1 linear #ecause 7y is a product of two varia#les! , solution to a linear e5uation is a set of values for each of the varia#les in the e5uation that make the e5uation hold! 8or e)ample- &iven the linear e5uation A 7 + ;y + : Y 0one solution is 7 Y (1- y Y ;- : Y (1 since AK(1L + ;K;L + K(1L Y (A + B ( 1 Y 0! 0hen the varia#les in the system refer to coordinate a)es- solutions are sometimes written as points in space! Thus one solution to the a#ove e5uation is K(1- ;- (1L and another is K0- 0- 0L! , system o" linear eAuations is a collection of linear e5uations over the same varia#les! , solution to a system of linear e5uations is a set of values for each of the varia#les that is a solution to each of the e5uations in the sys1 tem! 8or e)ample- consider the followin& system of e5uations= y Y A7 + B y Y @7 ( 1; The point K;- 10L is a solution to the first e5uation in this system #ut is not a solution to the second- so it is not a solution to the system! Similarly- K0- 0L is not a solution to the system #ecause it is not a solution to either of the e5uations! /owever- KB- 16L is a solution to the system- since it solves #oth the first and the second e5uation! The solution set to a system of e5uations is the set of all solutions to that system! 'n the a#ove e)ample- the solution set only contains one element= KB- 16L! Some systems of e5uations have no solution at all- such as this one= yY7+A yY7+B 'n this case the solution set is the empty set- `! Hther systems of e5uations have infinitely many solutions! 8or e)ample- any point on the 7 a)is is a solution to these e5uations= 7 + y + ;: N 0 ( ;y ( Bz Y 0
There are infinitely many com#inations of values we could assi&n to varia#les in a linear e5uation- so how can efficiently compute which com#inations are solutions> 8ortunately- there are many techni5ues for solvin& sys1 tems of linear e5uations- most of which work #y manipulatin& the e5uations to derive a minimal set of con1 straints on valid solutions! 7efore descri#in& and implementin& one particular techni5ue called 0auss-Mordan elimination- let us first e)amine how we can manipulate systems of linear e5uations! To motivate this e)amplelet's suppose that we have the followin& system of linear e5uations= A7 + y ( @: Y 11 ) + y ( Az Y ? There are many ways that we can modify this system of e5uations without chan&in& the solution set! 8or in1 stance- we can multiply #oth sides of one of the e5uations #y a nonzero constant without affectin& whether a par1 ticular point is a solution! ,s an e)ample- the a#ove system is e5uivalent to the system &iven #elow since we have simply multiplied #oth sides of the #ottom e5uation #y (A= A7 + y ( @: Y 11 M"x M "y + )z K M1* Similarly- we can add one e5uation to another without chan&in& the solution set! Thus A7 + y ( @: Y 11 (A7 ( Ay + <: Y (1? 's e5uivalent to M y + z K M' (A7 ( Ay + <: Y (1? 7ecause we have added the second e5uation to the first! Usin& these two rules- we can &reatly simplify a system until we reach a state where the solution can #e derived easily! 'n our particular e)ample- we can simplify as follows= ( ;y ( ;: !Y (B (A7 ( Ay ( <: !Y (1? ( ;y + ;: !Y (B + y ( A: !Y ? ( y + : !Y (; + y ( A: !Y A ( y + : !Y (; ( ;: !Y A y ( : !Y ( ;: !Y ; A KGultiplyin& the second e5uation #y 11IAL KGultiplyin& the first e5uation #y jL K,ddin& the first and second e5uationsL KGultiplyin& the first e5uation #y (1L
7 7 7 7
,t this point we cannot eliminate the 7 from the second e5uation or y from the first and thus cannot proceed any further! 0e could eliminate : from one of the e5uations- #ut only if we reintroduce 7 or y to an e5uation from which it has #een eliminated! 'f we now solve for 7 and y- we have the followin&=
1 B0@ 1
"ow- consider any point K7- y- :L that is a solution to the system of linear e5uations! 7y the a#ove e5uations- we have that K7- y- :L Y KA + ;:- ; + :- :L Y KA- ;- 0L + K;:- :- :L Y KA- ;- 0L + :K;- 1- 1L! This first term is the point KA- ;- 0L in three1dimensional space and the second the vector K;- 1- 1L scaled #y some value :! ,s we vary :- we can o#tain more and more solutions to this system of e5uations! 'n fact- the set of solutions to this system is &iv1 en #y a KA- ;- 0L + :K;- 1- 1L k : is a real num#er b! #atri% 1e&resentations ,ny linear e5uation containin& varia#les 70- 71- 7;- !!!- 7n can #e rearran&ed to an e5uivalent linear e5uation of the form a070 + a171 + a;7; + !!! + an7n Y c , system of m linear e5uations over varia#les 70- 71- 7;- !!!- 7n can thus #e written as a00)0 + a0171 + a0;7; + !!! + a0n7n Y c0 a10)0 + a1171 + a1;7; + !!! + a1n7n Y c1 !!! am0)0 + am171 + am;7; + !!! + amn7n Y cm 'f we try solvin& a lar&e system of e5uations usin& the a#ove techni5ues- it can #e tedious to write out every e5uation at each step! Gathematicians and computer scientists are inherently lazy- and rather than writin& out these lar&e systems instead use matrices- two1dimensional &rids of num#ers correspondin& to the coefficients and constants in a system of e5uations! 8or e)ample- for the a#ove system of e5uations- we would write out the followin& matri)=
a 00 a 01 a 10 a 11 a m$ a m/
a0n c 0 a1n c 1 a mn c m
This is technically called an augmented matri7 #ecause of the coefficients in the final column! 0e will refer to this column as the augmented column! To &ive a concrete e)ample of a matri) representation- the system of e5uations defined #y ) + ;y + Az Y B () + y Y (A B) + By ( ?z Y 1 would #e represented as the au&mented matri)
1 ; A B 1 1 0 A B B ? 1
1 B0E 1
7ecause a matri) is simply a more condensed notation for a system of linear e5uations- we can solve the linear system represented in matri) form usin& the same set of transformations we could apply directly to e5uations! 8or e)ample- we can multiply any row in a matri) #y a nonzero constant- as shown here=
1 ; A B ' G J 1 1 0 A 1 1 0 A B B ? 1 B B ? 1
0e can similarly add one row to another- as shown here Kaddin& the second row to the firstL
; B 6 E 1 * G * 1 1 0 A 1 1 0 A B B ? 1 B B ? 1
8inally- we can swap any two rows! This corresponds to writin& the e5uations in a different order than #efore!
1 ? 6 ? 1 1 + " 1 1 0 A 1 * G * B B ? 1 B B ? 1
1educed 1ow $chelon !orm 7efore introducin& 3auss1$ordan elimination- we need to cover two more pieces of terminolo&y ( #i!ots and reduced row echelon "orm! ,n element in a matri) is called a #i!ot element if it is the first nonzero element in its row and the only nonzero value in its column! 7y convention- values in the au&mented column of the matri) are not considered pivot elements! To &ive a concrete e)ample- consider the followin& matri)=
A 0 ; E0 0 ? 0 ?< 0 0 0 A0
The three in the first column is a pivot since it is the first nonzero element in its row and is the only nonzero ele1 ment in its column! Similarly- the first five in the second row is a pivot! The two in the first row is not a pivot #ecause it is not the first element in its row- and the three in the #ottom row is not a pivot #ecause it is not the only nonzero element in its column! ,n important point to note is that there cannot #e any more pivots in a matri) than there are rows or columns! Thus a B)A matri) can only have three pivots- while a @); matri) can only have two! This is #ecause no row or column can contain more than one pivot- since pivots must come first in their row and must #e the only nonzero entry in a column! , matri) is in reduced row echelon "orm KrrefL if the followin& two properties hold= The first nonzero element of each row is a pivot with value 1! The first nonzero element of each row is to the ri&ht of the first nonzero element of the row a#ove it!
1 B0< 1
1 A 0 01 0 0 1 0A 0 0 0 1;
This matri)- however- is not #ecause the pivot on the second row is to the left of the pivot on the first row=
0 0 1 01 1 A 0 0A 0 0 0 1;
Swappin& the first and second rows of this matri) will convert it to rref- however! Similarly- the matri) #elow is not in rref #ecause the second row does not #e&in with a pivot=
1 1 1 01 0 0 1 0A 0 0 0 1;
'f we su#tract the second row from the first- thou&h- we &et this matri)=
1 1 + + 0 0 1 0 A 0 0 0 1 ;
0hich is in rref! 8inally- the followin& matri) is not in rref #ecause the pivot on the last row is not e5ual to 1=
1 ; 0 00 0 0 1 00 0 0 0 A6
1 ; 0 00 0 0 1 00 0 0 0 1
0e will not prove it here- #ut every matri) can #e reduced to a uni5ue matri) in rref usin& the three manipula1 tions listed a#ove! The procedure for performin& this conversion is 3auss1$ordan elimination- discussed later! 0hat's so special a#out reduced row echelon form> 'ntuitively- the rref version of a matri) is the most simpli1 fied version of the matri) possi#le! 0e cannot learn any more a#out the system #y performin& additional manip1 ulations- and the relations #etween varia#les are as simple as possi#le! This means that we can easily e)tract the solutions Kif anyL to the system of e5uations from the matri) without much effort! Fecall that there are three pos1 si#le outcomes to a system of e5uations ( there is either no solution- one solution- or in"initely many solutions! These correspond to rref matri) forms as follows=
1 B10 1
Cha#ter 1G% E7tended E7am#le% 0auss-Mordan Elimination 6o solution! Then the rref form of the matri) will have a row where the #ody of the matri) consists solely of zeros with a nonzero coefficient! 'f we convert the matri) #ack into a system of e5uations- this corresponds to the e5uation 0 Y k for some nonzero k- which is clearly impossi#le! 8or e)ample- this rref matri) corresponds to a system with no solutions=
1 0 ; @ 1 0 1 0 0 ; 0 0 0 0 1
5ne solution! Then every every column in the rref matri) contains a pivot! These correspond to e5ua1 tions of the form 70 Y c0- 71 Y c1- !!!- 7n Y cn! The solution to the system can then #e read off from the final column! 8or e)ample=
1 0 0 0 0 1 0 0 0 0 1 0 1 ; 1 0
"ote that if the coefficient in the #ottom row of this matri) were nonzero- the system would have no solution! In"initely many solutions! Then some column does not contain a pivot! This corresponds to a varia#le whose value is either unconstrained or defined in terms of some other varia#le in the system! 8or e)1 ample=
1 0 0 01 0 1 0 0; 0 0 1 01
:aussC;ordan $limination 3auss1$ordan elimination is a modified form of 3aussian eliminationQ that converts a matri) into its reduced row echelon form e5uivalent! The techni5ue hin&es on the fact that the first nonzero element of each row must #e a pivot- which must have value 1 and #e the only nonzero value in its column! The al&orithm walks across the columns of the matri)- tryin& to clear the column out so that the only remainin& element is a pivot! ,fter clear1 in& all of the columns or creatin& the ma)imum possi#le num#er of pivots- the al&orithm terminates! 2urin& its e)ecution- 3auss1$ordan elimination partitions matrices into two sections ( an upper section consistin& of rows that #e&in with a pivot and a lower section that has not yet #een analyzed! To see how 3auss1$ordan elimination works- we'll use the al&orithm to convert the followin& matri) to rref=
0 ? 10 1 @ ; 6 1; 10 ;0 ; A 6 11 ;?
0e #e&in #y tryin& to put a pivot into the first column of the matri)! This re5uires us to find a row with a nonzero value in the first column! et's choose the second row! 8or an element to #e a pivot- it has to have value 1- so we'll normalize the second row #y dividin& it #y two! This yields
Q 3aussian Dlimination converts a matri) to row echelon "orm rather than reduced row echelon form!
1 B11 1
0 ? 10 1 @ 1 " G * 1+ ; A 6 11 ;?
"ow- for the this 1 to #e a pivot- every other element in the column must #e zero! Since the final row of this matri) contains a two in this column- we will need to use our matri) operations to clear this value! 0e can ac1 complish this #y normalizin& the row so that the row has a 1 in the current column- then su#tractin& the second row out from the third! Since the second row has a 1 in the first column- this will result with the first column of the third row containin& a zero! To see how this works- let's #e&in #y dividin& the third row #y two- as seen here=
0 ? 1 A 1 "/
10 1 @ 6 ? 10 " 11 / * /
"ow that the row has a one in the first position- we su#tract the second row from the third! This puts a zero into the first column- makin& our 1 a pivot=
0 ? 1 A + "/
10 1 @ 6 ? 10 " 1 / * /
The first column now contains a pivot- so we're one step closer to havin& the matri) in rref! Fecall that the definition of rref re5uires the pivot in each row to #e further to the ri&ht than the pivot in the row a#ove it! To maintain this property- we'll thus move this row up to the top #y swappin& it with the first row! This results in the followin& matri)=
1 " G * 1+ 0 ? 10 1 @ 0 A / ; A 1 / ; ? / ;
,s mentioned earlier- 3auss1$ordan elimination partitions the matri) into an upper &roup where the pivots are in place and a lower &roup that has not yet #een analyzed! These &roups are shown a#ove- where the #old rows have #een processed and the un#olded rows have not! "ow that we've finished the first column- let's move on to the second! 0e want to place a pivot into this columnso we need to find a row that can contain this pivot! Since each row can contain at most one pivot- this means that we'll only look at the #ottom two rows! 7oth the second and third rows have a nonzero value in the second column and could #e chosen as the row to contain the pivot- so we'll ar#itrarily choose the second row to contain the pivot! ,s with the first row- we'll normalize this row #y dividin& #y five- as shown here=
1 A 6 ? 10 0 1 1 / * ( / * 0 A / ; A 1 / ; ? / ;
"ow- we need to clear out the rest of the column! The top row has a three in this position that we need to elimin1 ate! 0e #e&in #y dividin& the first row #y three- as shown here=
1 B1; 1
1 /" + + 0 1 ; 0 A / ; A
J / 1* (1 / 1* 1 / ? @ / ? 1/ ; ? / ;
0e've cleared out the element in the second position- #ut in doin& so made the first row no lon&er #e&in with a 1! Thus we'll multiply the top row #y A so that its first entry is a 1! This is shown here=
1 + + J / * (1 / * 0 1 ; 1 / ? @ / ? 0 A / ; A 1 / ; ? / ;
0e've restored the first row and now need to repeat this process on the #ottom row! 0e multiply every element in the row #y 1;IA to normalize it=
1 0 0 0 1 ; 0 1
;E / ? @1 / ? 1 / ? @ / ? 1 / " * / "
1 0 0 ;E / ? @1 / ? 0 1 ; 1 / ? @ / ? 0 + + / 1* ' / 1*
The second column now contains a pivot! The matri) #elow a&ain shows what rows have #een processed- with rows in #old correspondin& to those that have #een considered and un#olded rows those that have not!
1 + + J / * (1 / * + 1 1 / * ( / * 0 0 0 ; / 1? B / 1?
et's now try to put a pivot into the third column! Since the first two rows already contain pivots- they cannot store any more pivots- so if we are &oin& to have a pivot in the third column the pivot will have to #e in the last row! /owever- the last row has a zero in the second position- meanin& that we can't convert it into a pivot! 0e're therefore done with the third column and can move on to the last column! ,&ain- we want to put a pivot into this column- and a&ain the first two rows are used up! 0e thus try to convert the third row so that it contains a pivot in this column! 0e normalize the row #y multiplyin& #y 11?I; to yield
1 0 0 ;E / ? @1 / ? 0 1 ; 1 / ? @ / ? 0 0 0 1
1 B1A 1
*/ J + + 1 (1 / J 0 1 ; 1 / ? @ / ? 0 0 0 1 ;
*/ J + + + 1* / J 0 1 ; 1 / ? @ / ? 0 0 0 1 ;
1 + + + " 0 1 ; 1 / ? @ / ? 0 0 0 1 ;
Fepeatin& this process a&ain on the second row- we normalize the second row #y dividin& #y 11I?=
1 0 0 0A + * 1+ 1 ( 0 0 0 1;
1 0 0 0A + * 1+ + * 0 0 0 1;
,nd normalizin&=
1 0 0 0 A + 1 + 1 0 0 0 1 ;
This matri) is now in reduced row echelon form and the al&orithm terminates! 0e've successfully reduced this matri) to rref! /ere's a 5uick summary of how the al&orithm works= Startin& in the first column and continuin& ri&htward= ook for a row that doesn't already contain a pivot with a nonzero value in this column! "ormalize that row so that the value in the current column is one! 8or each other row with a nonzero value in this column= "ormalize the row so that the value in the current column is one! Su#tract out the row containin& the new pivot! "ormalize the row to restore any pivots it contains! Gove the current row a#ove all other rows that don't already contain pivots!
Usin& the C++ techni5ues we've covered so far- we can write an ele&ant implementation of the 3auss1$ordan elimination al&orithm! 8or our implementation we'll represent a matri) as a grid double)- usin& the imple1 mentation of grid we wrote several chapters a&o! To refresh your memory- the grid interface is as follows=
template .typename 2lemType> class grid { $ubli*3 "2 4onstru*tors *reate a grid of the s$e*ified siUe. 2" grid!$; grid!int numCo6sB int num;ols$; "2 CesiUing fun*tions. 2" )oid clear!$; )oid resiLe!int 6idthB int height$; "2 5iUe Vueries. 2" int numCo6s!$ const; int num;ols!$ const; bool empty!$ const; int siLe!$ const; "2 Element a**ess. 2" 2lemTypeS getUt!int ro6B int col$; const 2lemTypeS getUt!int ro6B int col$ const; "2 (terator definitions. 2" typede typename )ector.2lemType>::iterator iterator; typede typename )ector.2lemType>::const&iterator const&iterator; "2 4ontainer iteration. 2" iterator begin!$; iterator end!$; const&iterator begin!$ const; const&iterator end!$ const; "2 Co' iteration. 2" iterator ro6&begin!int ro6$; iterator ro6&end!int ro6$; const&iterator ro6&begin!int ro6$ const; const&iterator ro6&end!int ro6$ const; "2 <ro7% ob>e*ts for o$erator[] 2" class 0utableCe erence * TR ... RT 4; class ImmutableCe erence * TR ... RT 4; "2 Element sele*tion fun*tions. 2" 0utableCe erence operator9:!int ro6$; ImmutableCe erence operator9:!int ro6$ const; "2 Celational bool operator bool operator bool operator bool operator bool operator bool operator #; o$erators. 2" . !const gridS ., !const gridS ,, !const gridS %, !const gridS >, !const gridS > !const gridS other$ other$ other$ other$ other$ other$ const; const; const; const; const; const;
1 B1? 1
8or our purposes- we'll assume that the last column of the grid double) represents the au&mented column! 0e'll implement 3auss1$ordan Dlimination as a function called Gauss\ordanEliminate- as shown here=
)oid FaussOordan2liminate!grid.double>S matri#$ { "2 ... 2" #
The al&orithm works #y iteratin& across the columns of the matri) placin& pivots into the proper position! 0e can thus start our implementation off as follows=
void Gauss\ordanEliminate(grid double)? matri7) { or!int col , -; col . matri#.num;ols!$ - 7; 33col$ * "2 ... 2" 4 #
"otice that we iterate not up to matri7.num4ols() #ut rather to matri7.num4ols() , 8! This is deli#er1 ate= the last column of the matri) is the au&mented column and is not technically part of the matri) #ody! Fecall that the num#er of pivots in a matri) cannot e)ceed the num#er of rows or columns! 0e will thus keep track of not only the current column- #ut also how many pivots we have placed! 0e will #reak out of the loop either when we've finished processin& the final column or when we've placed the ma)imum num#er of pivots- as shown here=
void Gauss\ordanEliminate(grid double)? matri7) { const int ma#Pi)ots , min!matri#.numCo6s!$B matri#.num;ols!$ - 7$; int pi)otsPlaced , -; for(int *ol = 0; *ol matri7.num4ols() , 8 SS pi)otsPlaced . ma#Pi)ots; !!*ol) { "2 ... 2" # #
Throu&hout this implementation- we'll maintain the invariant that the first $ivots<la*ed rows of the matri) have already #een processed and #e&in with pivots! "ow- we need to place a pivot in the current column of the matri)! 0e can't place the pivot into a row that already contains one- and since the first $ivots<la*ed rows already contain pivots we need to look in rows num#ered #etween $ivots<la*ed and matri7.numCo's() for a nonzero value to convert to a pivot! 8or simplicity- we'll decompose out the code for findin& a row satisfyin& this property to a helper function called &ind<ivotCo'- as shown here=
int &ind<ivotCo'(*onst grid double)? matri7, int *ol, int $ivots<la*ed) { or!int ro6 , pi)otsPlaced; ro6 . matri#.numCo6s!$; 33ro6$ i !matri#9ro6:9col: %, -.-$ return ro6; return -7; #
This function returns the inde) of a non1pivot row containin& a nonzero value in the *olCth column- or (1 if no row could #e found! 0e can use this function in Gauss\ordanEliminate as follows=
1 B16 1
void Gauss\ordanEliminate(grid double)? matri7) { *onst int ma7<ivots = min(matri7.numCo's(), matri7.num4ols() , 8); int $ivots<la*ed = 0; for(int *ol = 0; *ol matri7.num4ols() , 8 ?? $ivots<la*ed ma7<ivots; !!*ol) { const int ro6 , FindPi)otCo6!matri#B colB pi)otsPlaced$; i !ro6 ,, -7$ continue; "2 ... 2" # #
0e now have the row that will house the new pivot- and now it's time to normalize the row so that the element at position Kro'- *olL is 1! 0hile we could do this usin& a simple for loop- this is a &reat chance to show off our skills with the ST al&orithms and the fun*tional) li#rary! Fecall the transform al&orithm- which has the followin& si&nature=
tem$late t%$ename (n$ut(terator, t%$ename =ut$ut(terator, t%$ename @nar%&un*tion) =ut$ut(terator trans orm!InputIterator beginB InputIterator endB <utputIterator outB UnaryFunction n$; transform applies the function fn to each element in the ran&e Obegin- endL and stores the resultin& values in the ran&e #e&innin& with out! ,s we discussed in the chapter on al&orithms- the output iterator out can point #ack to the input ran&e- in which case transform will update all of the values in the ran&e #y applyin& fn to them! 'f we can construct a function that normalizes every element in the ran&e- then we can use transform to update the entire row! 8ortunately- we do have such a function thanks to the ST fun*tional) li#rary! 0e
want to normalize the row #y dividin& every element in the row #y the element that will #e the pivot! Since the pivot is stored in matri7[ro'][*ol]- we can create a function that divides #y the pivot #y callin&
bind8nd!di)ides.double>!$B matri#9ro6:9col:$ divides double) is an adapta#le #inary functor e)ported #y fun*tional) that takes two doubles and re1 turns their 5uotient! 7indin& the second parameter with the value of matri7[ro'][*ol] thus constructs a un1 ary function that divides its ar&ument #y matri7[ro'][*ol]!
Usin& this #it of ST power- we can normalize the row in a sin&le line of code as follows=
void Gauss\ordanEliminate(grid double)? matri7) { *onst int ma7<ivots = min(matri7.numCo's(), matri7.num4ols() , 8); int $ivots<la*ed = 0; for(int *ol = 0; *ol matri7.num4ols() , 8 ?? $ivots<la*ed ma7<ivots; !!*ol) { *onst int ro' = &ind<ivotCo'(matri7, *ol, $ivots<la*ed); if(ro' == ,8) *ontinue; trans orm!matri#.ro6&begin!ro6$B matri#.ro6&end!ro6$B matri#.ro6&begin!ro6$B "" =ver'rite ro' bind8nd!di)ides.double>!$B matri#9ro6:9col:$; # # "2 ... 2"
1 B1@ 1
Fecall that the ro'Bbegin and ro'Bend functions on the grid return iterators that define a sin&le row of the grid! "ow that this has #een taken care of- let's move on to the ne)t step ( clearin& out the rest of the column! 0e do this #y iteratin& over each other row- checkin& if the row has a nonzero value in the current column! 'f sowe normalize the row so that the element in the pivot column is one- su#tract out the current row- then renormal1 ize the row to restore any pivots! 0e'll #e&in #y lookin& for rows that need to #e cleared=
void Gauss\ordanEliminate(grid double)? matri7) { *onst int ma7<ivots = min(matri7.numCo's(), matri7.num4ols() , 8); int $ivots<la*ed = 0; for(int *ol = 0; *ol matri7.num4ols() , 8 ?? $ivots<la*ed ma7<ivots; !!*ol) { *onst int ro' = &ind<ivotCo'(matri7, *ol, $ivots<la*ed); if(ro' == ,8) *ontinue; transform(matri7.ro'Bbegin(ro'), matri7.ro'Bend(ro'), matri7.ro'Bbegin(ro'), "" =ver'rite ro' bindHnd(divides double)(), matri7[ro'][*ol]); or!ne#tCo6 , -; ne#tCo6 . matri#.numCo6s!$; 33ne#tCo6$ * "2 5+i$ the *urrent ro' and ro's that have Uero in this *olumn. 2" i !ne#tCo6 ,, ro6 >> matri#9ne#tCo6:9col: %, -$ continue; "2 ... 2"
4 # #
"ow that we've found a row to clear- we need to normalize the row #y dividin& every value in the row #y the value in the current column! 0e've already fi&ured out how to do this usin& transform and bindHnd- so to avoid code duplication we'll decompose out the lo&ic to divide the row #y a value into a helper function called 9ivideCo'! This is shown here=
void 9ivideCo'(grid double)? matri7, int ro', double b%) { trans orm!matri#.ro6&begin!ro6$B matri#.ro6&end!ro6$B matri#.ro6&begin!ro6$B bind8nd!di)ides.double>!$B by$; #
,nd can then update the e)istin& code to use this function=
1 B1E 1
void Gauss\ordanEliminate(grid double)? matri7) { *onst int ma7<ivots = min(matri7.numCo's(), matri7.num4ols() , 8); int $ivots<la*ed = 0; for(int *ol = 0; *ol matri7.num4ols() , 8 ?? $ivots<la*ed ma7<ivots; !!*ol) { *onst int ro' = &ind<ivotCo'(matri7, *ol, $ivots<la*ed); if(ro' == ,8) *ontinue; Di)ideCo6!matri#B ro6B matri#9ro6:9col:$; for(ne7tCo' = 0; ne7tCo' matri7.numCo's(); !!ne7tCo') { "2 5+i$ the *urrent ro' and ro's that have Uero in this *olumn. 2" if(ne7tCo' == ro' NN matri7[ne7tCo'][*ol] 0= 0) *ontinue; Di)ideCo6!matri#B ne#tCo6B matri#9ne#tCo6:9col:$; # # # "2 ... 2"
"ow- we need to su#tract out the row containin& the new pivot from this row! ,&ain- we can do this usin& hand1 written loops- #ut is there some way to harness the ST to solve the pro#lem> The answer is yes- thanks to a second version of transform. Up to this point- the transform we've used transforms a sin&le ran&e with a unary function! /owever- another form of transform e)ists that accepts as input two ran&es and a binary func1 tion- then produces a stream of values #y applyin& the #inary function pairwise to the values in ran&e! 8or e)1 ample- &iven two iterator ran&es delineatin& the ran&es a0- 1- ;- A- Bb and a?- 6- @- E- <b- usin& this second ver1 sion of transform and the multi$lies int) function o#*ect would produce the values a0- 6- 1B- ;B- A6b! The prototype for the two1ran&e version of transform is
tem$late t%$ename (n(tr8, t%$ename (n(trH, t%$ename =ut(tr, t%$ename Oinar%&un*tion) <utItr trans orm!InItr7 start7B InItr7 end7B InItr8 start8B <utItr outB (inaryFunction n$;
"otice that the first two ar&uments define a full ran&e whereas the third ar&ument only specifies the start of the second ran&e! This version of transform assumes that the second iterator ran&e has at least as many elements as the first ran&e- which is valid for our current application! To su#tract the two rows- we can use this version of transform and the minus double) function o#*ect to su#tract the values in the new pivot row from the values in the current row! This is shown here=
1 B1< 1
void Gauss\ordanEliminate(grid double)? matri7) { *onst int ma7<ivots = min(matri7.numCo's(), matri7.num4ols() , 8); int $ivots<la*ed = 0; for(int *ol = 0; *ol matri7.num4ols() , 8 ?? $ivots<la*ed ma7<ivots; !!*ol) { *onst int ro' = &ind<ivotCo'(matri7, *ol, $ivots<la*ed); if(ro' == ,8) *ontinue; 9ivideCo'(matri7, ro', matri7[ro'][*ol]); for(ne7tCo' = 0; ne7tCo' matri7.numCo's(); !!ne7tCo') { "2 5+i$ the *urrent ro' and ro's that have Uero in this *olumn. 2" if(ne7tCo' == ro' NN matri7[ne7tCo'][*ol] 0= 0) *ontinue; 9ivideCo'(matri7, ne7tCo', matri7[ne7tCo'][*ol]); trans orm!matri#.ro6&begin!ne#tCo6$B matri#.ro6&end!ne#tCo6$B matri#.ro6&begin!ro6$B TT Subtract )alues rom ne6 pi)ot ro6 matri#.ro6&begin!ne#tCo6$B TT <)er6rite e#isting )alues minus.double>!$$; "2 ... 2" # # #
0e're almost done cleanin& up this row and there's only one more step ( normalizin& the row to restore any pivots! 7ecause the al&orithm moves from left to ri&ht- any pivots in the row we *ust modified must #e to the left of the current column! 0e thus need to check to see if there are any nonzero values to the left of the current column that mi&ht #e pivots! ,s you mi&ht have &uessed- this can easily #e done usin& the ST - thanks to the fun*tional) li#rary! 'n particular- we can use the notBeVualBto double) function o#*ect in con*unction with bindHnd to construct a unary function that returns true for any nonzero value- then can pass this function into the ST findBif al&orithm to o#tain an iterator to the first nonzero element in the row! 'f we find a valuewe'll normalize the row- and otherwise *ust leave the row as1is! This is shown here=
1 B;0 1
void Gauss\ordanEliminate(grid double)? matri7) { *onst int ma7<ivots = min(matri7.numCo's(), matri7.num4ols() , 8); int $ivots<la*ed = 0; for(int *ol = 0; *ol matri7.num4ols() , 8 ?? $ivots<la*ed ma7<ivots; !!*ol) { *onst int ro' = &ind<ivotCo'(matri7, *ol, $ivots<la*ed); if(ro' == ,8) *ontinue; 9ivideCo'(matri7, ro', matri7[ro'][*ol]); for(ne7tCo' = 0; ne7tCo' matri7.numCo's(); !!ne7tCo') { "2 5+i$ the *urrent ro' and ro's that have Uero in this *olumn. 2" if(ne7tCo' == ro' NN matri7[ne7tCo'][*ol] 0= 0) *ontinue; 9ivideCo'(matri7, ne7tCo', matri7[ne7tCo'][*ol]); transform(matri7.ro'Bbegin(ne7tCo'), matri7.ro'Bend(ne7tCo'), matri7.ro'Bbegin(ro'), "" 5ubtra*t values from ne' $ivot ro' matri7.ro'Bbegin(ne7tCo'), "" =ver'rite e7isting values minus double)()); grid.double>::iterator nonLero , ind&i !matri#.ro6&begin!ne#tCo6$B matri#.ro6&begin!ne#tCo6$ 3 colB bind8nd!not&eGual&to.double>!$B -.-$$; i !nonLero %, matri#.ro6&begin!ne#tCo6$ 3 col$ Di)ideCo6!matri#B ne#tCo6B RnonLero$; # # # "2 ... 2"
and
matri7.ro'Bbegin(ne7tCo') ! *ol
These are iterators definin& all elements in the row to the left of the current column! Since findBif returns the end of the iterator ran&e as a sentinel if no elements in the ran&e pass the predicate- we check if the returned iter1 ator is e5ual to matri7.ro'Bbegin(ne7tCo') ! *ol #efore normalizin& the row a&ain! ,t this point we've cleaned up the row and after this inner for loop terminates we will have made the element at position Kro'- *olL a pivot! The last step is to move this row as hi&h up in the matri) as possi#le so that we don't end up considerin& the row a&ain in the future! 0e thus want to swap this row with the first row that does1 n't already contain a pivot! 7ut we know which row that is ( it's the row at the position specified #y $ivots, <la*ed! Usin& the s'a$Branges al&orithm- which e)chan&es the contents of two iterator ran&es- this can #e done in one line! 0e'll also increment $ivots<la*ed- since we've *ust made another pivot! This yields the fi1 nal version of Gauss\ordanEliminate- as shown here=
1 B;1 1
void Gauss\ordanEliminate(grid double)? matri7) { *onst int ma7<ivots = min(matri7.numCo's(), matri7.num4ols() , 8); int $ivots<la*ed = 0; for(int *ol = 0; *ol matri7.num4ols() , 8 ?? $ivots<la*ed ma7<ivots; !!*ol) { *onst int ro' = &ind<ivotCo'(matri7, *ol, $ivots<la*ed); if(ro' == ,8) *ontinue; 9ivideCo'(matri7, ro', matri7[ro'][*ol]); for(ne7tCo' = 0; ne7tCo' matri7.numCo's(); !!ne7tCo') { "2 5+i$ the *urrent ro' and ro's that have Uero in this *olumn. 2" if(ne7tCo' == ro' NN matri7[ne7tCo'][*ol] 0= 0) *ontinue; 9ivideCo'(matri7, ne7tCo', matri7[ne7tCo'][*ol]); transform(matri7.ro'Bbegin(ne7tCo'), matri7.ro'Bend(ne7tCo'), matri7.ro'Bbegin(ro'), "" 5ubtra*t values from ne' $ivot ro' matri7.ro'Bbegin(ne7tCo'), "" =ver'rite e7isting values minus double)()); grid double)33iterator nonUero = findBif(matri7.ro'Bbegin(ne7tCo'), matri7.ro'Bbegin(ne7tCo') ! *ol, bindHnd(notBeVualBto double)(), 0.0)); if(nonUero 0= matri7.ro'Bbegin(ne7tCo') ! *ol) 9ivideCo'(matri7, ne7tCo', 2nonUero); # s6ap&ranges!matri#.ro6&begin!ro6$B matri#.ro6&end!ro6$B matri#.ro6&begin!pi)otsPlaced$$; 33pi)otsPlaced; # #
This function is dense- #ut correctly implements 3auss1$ordan elimination! 0e can now solve linear systems of e5uationsU Com&uting In7erses 7efore concludin& our treatment of 3auss1$ordan elimination- we'll consider one practical application= invertin& a matri)! 8or this discussion- we will only consider s5uare matrices! 0hen multiplyin& real num#ers- the num#er 1 is the identity in that the product of any num#er and 1 is that ori1 &inal num#er! That is- 1X) Y )X1 Y )! Similarly- the in!erse of a num#er Kdenoted )11L is the uni5ue num#er such that the product of the ori&inal num#er and the inverse is the identity= )X)11 Y )11X) Y 1! 0hen multiplyin& matrices-Q the identity matri7 Kdenoted IL plays the same roleR &iven a matri) ,- the identity matri) is the matri) such that ,' Y ', Y ,! Similarly- The in!erse of a matri) is a matri) that- when multiplied with the ori&inal mat1 ri)- yields the identity= ,,11 Y ,11, Y '! "ot all num#ers have an inverse ( in particular- there is no num#er which multiplied #y zero yields 1! Similarly- not all matrices have an inverse! , matri) with no inverse is said to #e singular! 8or s5uare matrices- matri) multiplication is only defined when the two matrices have the same size! Thereforethere are multiple identity matrices- one for each size of s5uare matri)! The identity matri) is a matri) that is
Q 0e did not cover matri) multiplication here- thou&h it's well1defined for matrices if the dimensions are correct! Consult a linear al&e#ra te)t#ook for more information on matri) multiplication!
1 B;; 1
zero everywhere e)cept for on the main dia&onal Kfrom the upper1left corner to the lower1ri&ht cornerL- where it is all ones! Thus the A)A identity matri) is
1 0 0 0 1 0 0 0 1
0hen workin& with real num#ers it is easy to compute the inverse ( *ust take the reciprocal of the num#er! 'n1 vertin& a matri) is considera#ly trickier #ecause matri) multiplication is more complicated! 8ortunately- we can use a modified version of 3auss1$ordan elimination to compute the inverse of a matri)! The trick is to au&ment the initial matri) with the identity matri)- then reduce the matri) to rref! 'f the matri) is nonsin&ular- the result of the row reduction should #e the identity matri) au&mented with the inverse of the ori&inal matri)! 8or e)1 ample- if we want to invert the followin& matri)=
A 1 B 1 ? < ; 6 ?
0e would first au&ment it with the identity matri)- as shown here=
A 1 B1 0 0 1 ? <0 1 0 ; 6 ?0 0 1
Then convert the matri) to rref usin& the 3auss1$ordan Dlimination al&orithm to yield
The matri) is now the identity matri) au&mented with the matri) inverse- so the inverse matri) is
Crovin& this techni5ue correct is #eyond the scope of a mere C++ pro&rammin& te)t- so we will take its correct1 ness for &ranted! Usin& our current implementation of 3auss1$ordan Dlimination- we cannot compute inverse matrices #ecause we have hardcoded the assumption that the input matri) has e)actly one au&mented column! This is easily remedied with an additional parameter to the function specifyin& how many columns in the matri) are part of the au&men1 ted matri)- as shown here=
1 B;A 1
int $ivots<la*ed = 0; for(int *ol = 0; col . num0atri#;olumns ?? $ivots<la*ed ma7<ivots; !!*ol) { *onst int ro' = &ind<ivotCo'(matri7, *ol, $ivots<la*ed); if(ro' == ,8) *ontinue; 9ivideCo'(matri7, ro', matri7[ro'][*ol]); for(ne7tCo' = 0; ne7tCo' matri7.numCo's(); !!ne7tCo') { "2 5+i$ the *urrent ro' and ro's that have Uero in this *olumn. 2" if(ne7tCo' == ro' NN matri7[ne7tCo'][*ol] 0= 0) *ontinue; 9ivideCo'(matri7, ne7tCo', matri7[ne7tCo'][*ol]); transform(matri7.ro'Bbegin(ne7tCo'), matri7.ro'Bend(ne7tCo'), matri7.ro'Bbegin(ro'), "" 5ubtra*t values from ne' $ivot ro' matri7.ro'Bbegin(ne7tCo'), "" =ver'rite e7isting values minus double)()); grid double)33iterator nonUero = findBif(matri7.ro'Bbegin(ne7tCo'), matri7.ro'Bbegin(ne7tCo') ! *ol, bindHnd(notBeVualBto double)(), 0.0)); if(nonUero 0= matri7.ro'Bbegin(ne7tCo') ! *ol) 9ivideCo'(matri7, ne7tCo', 2nonUero); # s'a$Branges(matri7.ro'Bbegin(ro'), matri7.ro'Bend(ro'), matri7.ro'Bbegin($ivots<la*ed)); !!$ivots<la*ed; # #
0e can now #e&in writin& a function (nvert:atri7 which accepts a grid double) as a parameter and up1 dates it to hold its inverse! Hur first step is to ensure that the matri) is s5uare- which we can do here=
void (nvert:atri7(grid double)? matri7) { i !matri#.numCo6s!$ %, matri#.num;ols!$$ thro6 in)alid&argument!";annot in)ert a non-sGuare matri#."$; "2 ... 2" #
"e)t- we need to construct a matri) that has the input matri) as its #ody and that is au&mented with the identity matri)! Unfortunately- we cannot simply use the grid's resiUe mem#er function since this discards all internal data! 'nstead- we'll construct a second grid of the proper size and e)plicitly initialize it to hold the ri&ht values! 0e'll use the *o$% al&orithm to do the actual copyin&- plus a simple for loop to initialize the au&mented part of the matri) to the identity! This is shown here=
1 B;B 1
void (nvert:atri7(grid double)? matri7) { if(matri7.numCo's() 0= matri7.num4ols()) thro' invalidBargument(.4annot invert a non,sVuare matri7..); grid.double> scratch=or+!matri#.numCo6s!$B matri#.num;ols!$ R 8$; TR ;opy e#isting data. RT or!int ro6 , -; ro6 . matri#.numCo6s!$; 33ro6$ copy!matri#.ro6&begin!ro6$B matri#.ro6&end!ro6$B scratch=or+.ro6&begin!ro6$$; TR =rite the identity matri#. RT or!int ro6 , -; ro6 . matri#.numCo6s!$; 33ro6$ scratch=or+9ro6:9ro6 3 matri#.num;ols!$: , 7.-; # "2 ... 2"
"ow that we've #uilt our matri)- we can hand it off to the Gauss\ordanEliminate function to &et row1re1 duced- as shown here=
void (nvert:atri7(grid double)? matri7) { if(matri7.numCo's() 0= matri7.num4ols()) thro' invalidBargument(.4annot invert a non,sVuare matri7..); grid double) s*rat*h1or+(matri7.numCo's(), matri7.num4ols() 2 H); "2 4o$% e7isting data. 2" for(int ro' = 0; ro' matri7.numCo's(); !!ro') *o$%(matri7.ro'Bbegin(ro'), matri7.ro'Bend(ro'), s*rat*h1or+.ro'Bbegin(ro')); "2 1rite the identit% matri7. 2" for(int ro' = 0; ro' matri7.numCo's(); !!ro') s*rat*h1or+[ro'][ro' ! matri7.num4ols()] = 8.0; FaussOordan2liminate!scratch=or+B matri#.num;ols!$$; # "2 ... 2"
,t this point- one of two cases holds! 'f the input matri) was inverti#le- the reduced row echelon form of the matri) should #e the identity matri) and the au&mented portion of the matri) should #e the matri) inverse! Hth1 erwise- the reduced row echelon form of the matri) will not #e the identity and we should report an error! 0e can check if the matri) is the identity matri) #y iteratin& over every element and checkin& if there are nonzero values off the main dia&onal or values other than one on the dia&onal! This is shown here=
1 B;? 1
'f the matri) is inverti#le- then we need to copy the au&mented portion of the matri) #ack into the parameter! 0e'll a&ain use *o$% to help with this one! Since we want to copy the second half of each row #ack into the source matri)- we'll start iteratin& at ro'Bbegin(...) ! matri7.num4ols()- which is the first element of the au&mented matri) in each row! The final code for (nvert:atri7 is shown here=
1 B;6 1
void (nvert:atri7(grid double)? matri7) { if(matri7.numCo's() 0= matri7.num4ols()) thro' invalidBargument(.4annot invert a non,sVuare matri7..); grid double) s*rat*h1or+(matri7.numCo's(), matri7.num4ols() 2 H); "2 4o$% e7isting data. 2" for(int ro' = 0; ro' matri7.numCo's(); !!ro') *o$%(matri7.ro'Bbegin(ro'), matri7.ro'Bend(ro'), s*rat*h1or+.ro'Bbegin(ro')); "2 1rite the identit% matri7. 2" for(int ro' = 0; ro' matri7.numCo's(); !!ro') s*rat*h1or+[ro'][ro' ! matri7.num4ols()] = 8.0; Gauss\ordanEliminate(s*rat*h1or+, matri7.num4ols()); for(int ro' = 0; ro' matri7.numCo's(); !!ro') for(int *ol = 0; *ol matri7.num4ols(); !!*ol) if((ro' == *ol ?? s*rat*h1or+[ro'][*ol] 0= 8.0) NN (ro' 0= *ol ?? s*rat*h1or+[ro'][*ol] 0= 0.0)) thro' invalidBargument(.5ingular matri7..); or!int ro6 , -; ro6 . matri#.numCo6s!$; 33ro6$ copy!scratch=or+.ro6&begin!ro6$ 3 matri#.num;ols!$B scratch=or+.ro6&end!ro6$B matri#.ro6&begin!ro6$$; #
#ore to $%&lore 0hewU 0e've *ust finished a whirlwind tour of linear al&e#ra and matri) al&orithms and &ot a chance to see e)1 actly how much milea&e we can &et out of the ST ! 7ut we've #arely scratched the surface of matri) processin& and numerical computin& and there are far more advanced techni5ues out there! 'f you're interested in e)plorin& what else you can do with this code- consider lookin& into the followin&= 1! Tem&lates! The code we've written has assumed that the grid contains doubles- #ut there's nothin& fundamentally wron& with usin& a grid float) or even a grid *om$le7 double) ) Kthe *om$le7 template type- defined in *om$le7)- represents a comple) num#er and is one of the oldest headers in the standard li#raryL! Templatize the code we've written so that it works on grids of any type! 'f you're up for a real challen&e- implement a CationalEumber class with full operator overloadin& support and use it in con*unction with Gauss\ordanEliminate to &et mathematically precise values for the in1 verse of a matri)! ;! :aussian $limination with ,acksol7ing! 'f you are only interested in solvin& linear systems of e5ua1 tions and don't necessarily want to &et the fully row1reduced form of a matri)- you can use an alternative techni5ue called 0aussian elimination with bac*sol!ing! This approach is much faster than 3auss1 $ordan elimination and is used more fre5uently in practice! 't also is not particularly difficult to imple1 ment and with a #it of work can &ive you impressive performance &ains! A! <umerical Stability! Computations involvin& doubles or floats are inherently suspect #ecause the #inary representation of these types is su#*ect to roundin& errors and other inaccuracies! Fesearch how to improve the numerical sta#ility of this al&orithm so that you can use it to o#tain increasin&ly concise values!
1 B;@ 1
int $ivots<la*ed = 0; for(int *ol = 0; *ol num:atri74olumns ?? $ivots<la*ed ma7<ivots; !!*ol) { *onst int ro' = &ind<ivotCo'(matri7, *ol, $ivots<la*ed); if(ro' == ,8) *ontinue; 9ivideCo'(matri7, ro', matri7[ro'][*ol]); for(ne7tCo' = 0; ne7tCo' matri7.numCo's(); !!ne7tCo') { "2 5+i$ the *urrent ro' and ro's that have Uero in this *olumn. 2" if(ne7tCo' == ro' NN matri7[ne7tCo'][*ol] 0= 0) *ontinue; 9ivideCo'(matri7, ne7tCo', matri7[ne7tCo'][*ol]); transform(matri7.ro'Bbegin(ne7tCo'), matri7.ro'Bend(ne7tCo'), matri7.ro'Bbegin(ro'), "" 5ubtra*t values from ne' $ivot ro' matri7.ro'Bbegin(ne7tCo'), "" =ver'rite e7isting values minus double)()); grid double)33iterator nonUero = findBif(matri7.ro'Bbegin(ne7tCo'), matri7.ro'Bbegin(ne7tCo') ! *ol, bindHnd(notBeVualBto double)(), 0.0)); if(nonUero 0= matri7.ro'Bbegin(ne7tCo') ! *ol) 9ivideCo'(matri7, ne7tCo', 2nonUero); # s'a$Branges(matri7.ro'Bbegin(ro'), matri7.ro'Bend(ro'), matri7.ro'Bbegin($ivots<la*ed)); !!$ivots<la*ed; # #
1 B;E 1
void (nvert:atri7(grid double)? matri7) { if(matri7.numCo's() 0= matri7.num4ols()) thro' invalidBargument(.4annot invert a non,sVuare matri7..); grid double) s*rat*h1or+(matri7.numCo's(), matri7.num4ols() 2 H); "2 4o$% e7isting data. 2" for(int ro' = 0; ro' matri7.numCo's(); !!ro') *o$%(matri7.ro'Bbegin(ro'), matri7.ro'Bend(ro'), s*rat*h1or+.ro'Bbegin(ro')); "2 1rite the identit% matri7. 2" for(int ro' = 0; ro' matri7.numCo's(); !!ro') s*rat*h1or+[ro'][ro' ! matri7.num4ols()] = 8.0; Gauss\ordanEliminate(s*rat*h1or+, matri7.num4ols()); for(int ro' = 0; ro' matri7.numCo's(); !!ro') for(int *ol = 0; *ol matri7.num4ols(); !!*ol) if((ro' == *ol ?? s*rat*h1or+[ro'][*ol] 0= 8.0) NN (ro' 0= *ol ?? s*rat*h1or+[ro'][*ol] 0= 0.0)) thro' invalidBargument(.5ingular matri7..); for(int ro' = 0; ro' matri7.numCo's(); !!ro') *o$%(s*rat*h1or+.ro'Bbegin(ro') ! matri7.num4ols(), s*rat*h1or+.ro'Bend(ro'), matri7.ro'Bbegin(ro')); #
't's impossi#le to learn C++ or any other o#*ect1oriented lan&ua&e without encounterin& inheritance- a mechan1 ism that lets different classes share implementation and interface desi&n! /owever- inheritance has evolved &reatly since it was first introduced to C++- and conse5uently C++ supports several different inheritance schemes! This chapter introduces and motivates inheritance- then discusses how inheritance interacts with other lan&ua&e features! Inheritance of Im&lementation and Interface C++ started off as a lan&ua&e called 4C with Classes-6 so named #ecause it was essentially the C pro&rammin& lan&ua&e with support for classes and o#*ect1oriented pro&rammin&! C++ is the more modern incarnation of C with Classes- so most K#ut not allL of the features of C with Classes also appear in C++! The inheritance introduced in C with Classes allows you to define new classes in terms of older ones! 8or e)1 ample- suppose you are usin& a third1party li#rary that e)ports a <rinter class- as shown #elow=
*lass <rinter { $ubli*3 "2 4onstru*tor, destru*tor, et*. 2" void set&ont(*onst string? fontEame, int siUe); void set4olor(*onst string? *olor); void $rint9o*ument(*onst string? do*ument); $rivate3 "2 (m$lementation details 2" #;
This <rinter class e)ports several formattin& functions- plus $rint9o*ument- which accepts a strin& of the document to print! et's assume that $rint9o*ument is implemented synchronously ( that is- $rint9o*u, ment will not return until the document has finished printin&! 'n some cases this #ehavior is fine- #ut in others it's simply not accepta#le! 8or e)ample- suppose you're writin& data#ase software for a lar&e li#rary and want to &ive users the option to print out call num#ers! Chances are that people usin& your software will print call num1 #ers for multiple #ooks and will #e irritated if they have to sit and wait for their documents to finish printin& #e1 fore continuin& their search! To address this pro#lem- you decide to add a new feature to the printer that lets the users en5ueue several documents and print them in a sin&le #atch *o#! That way- users searchin& for #ooks can en5ueue call num#ers without lon& pauses- then print them all in one operation! /owever- you don't want to force users to 5ueue up documents and then do a #atch print *o# at the end ( after all- may#e they're *ust lookin& for one #ook ( so you want to retain all of the ori&inal features of the <rinter class! /ow can you ele&antly solve this pro#lem in software> et's consider the a#ove pro#lem from a pro&rammin& perspective! The important points are=
0e are provided the <rinter class from an e)ternal source- so we cannot modify the <rinter inter1 face! 0e want to preserve all of the e)istin& functionality from the <rinter class! 0e want to e)tend the <rinter class to include e)tra functionality!
1 BA0 1
This is an ideal spot to use inheritance- a means for definin& a new class in terms of an older one! 0e have an e)istin& class that contains most of our needed functionality- #ut we'd like to add some e)tra features! et's define a Oat*h<rinter class that supports two new functions- enVueue9o*ument and $rintAll9o*u, ments- on top of all of the re&ular <rinter functionality! 'n C++- we write this as
*lass Oat*h<rinter: public Printer "" (nherit from <rinter { $ubli*3 void enVueue9o*ument(*onst string? do*ument); void $rintAll9o*uments(); $rivate3 Vueue string) do*uments; "" 9o*ument Vueue #;
/ere- the class declaration *lass Oat*h<rinter3 $ubli* <rinter indicates that the new class Oat*h, <rinter inherits the functionality of the <rinter class! ,lthou&h we haven't e)plicitly provided the $rint, 9o*ument or set&ont functions- since those functions are defined in <rinter- they are also part of Oat*h, <rinter! 8or e)ample=
Oat*h<rinter m%<rinter; m%<rinter.set;olor!"Ced"$; m%<rinter.printDocument!"This is a document%"$; m%<rinter.enGueueDocument!"Print this one later."$; m%<rinter.printUllDocuments!$; "" "" "" "" (nherited from <rinter 5ame 9efined in Oat*h<rinter 5ame
0hile the Oat*h<rinter can do everythin& that a <rinter can do- the converse is not true ( a <rinter can1 not call enVueue9o*ument or $rintAll9o*uments- since we did not modify the <rinter class interface! 'n the a#ove setup- <rinter is called a base class of Oat*h<rinter- which is a deri!ed class! 'n C++ *ar&onthe relationship #etween a derived class and its #ase class is the is-a relationship! That is- a Oat*h<rinter is-a <rinter #ecause everythin& the <rinter can do- the Oat*h<rinter can do as well! The converse is not truethou&h- since a <rinter is not necessarily a Oat*h<rinter! 7ecause Oat*h<rinter is-a <rinter- anywhere that a <rinter is e)pected we can instead provide a Oat*h, <rinter! 8or e)ample- suppose we have a function that accepts a <rinter o#*ect- perhaps to confi&ure its font renderin&- as shown here=
void (nitialiUe<rinter(<rinter? $);
,lthou&h (nitialiUe<rinter e)pects an ar&ument of type <rinter?- we can instead provide it a Oat*h, <rinter! This operations is well1defined and perfectly safe #ecause the Oat*h<rinter contains all of the functionality of a re&ular <rinter! 'f we temporarily for&et a#out all of the e)tra functionality provided #y the Oat*h<rinter class- we still have a &ood old1fashioned <rinter! 0hen workin& with inheritance- you can think of the types of ar&uments to functions as specifyin& the minimum re5uirements for the parameter! , func1 tion acceptin& a <rinter? or a *onst <rinter? can take in a o#*ect of any type- provided of course that it ul1 timately derives from <rinter!
1 BA1 1
"ote that it is completely le&al to have several classes inherit from a sin&le #ase class! Thus- if we wanted to de1 velop another printer that supported &raphics printin& in addition to te)t- we could write the followin& class definition=
*lass Gra$hi*s<rinter3 public Printer { $ubli*3 "2 4onstru*tor, destru*tor, et*. 2" void $rint<i*ture(*onst <i*ture? $i*ture); "" &or some <i*ture *lass $rivate3 "2 (m$lementation details 2" #;
"ow- Gra$hi*s<rinter can do everythin& a re&ular <rinter can do- #ut can also print <i*ture o#*ects! ,&ain- Gra$hi*s<rinter is-a <rinter- #ut not vice1versa! Similarly- Gra$hi*s<rinter is not a Oat*h, <rinter! ,lthou&h they are #oth derived classes of <rinter- they have nothin& else in common! 't sometimes help to visualize the inheritance relations #etween classes as a tree! 0e adopt the convention that if one class derives from another- the first class is represented as a child of the second! 0e also la#el all ed&es in the tree with arrows pointin& from derived classes to #ase classes! 8or e)ample- <rinter- Oat*h<rinter- and Gra$hi*s<rinter are all related as follows=
Printer
(atchPrinter
FraphicsPrinter
1untime Costs of ,asic Inheritance The inheritance scheme outlined a#ove incurs no runtime penalties! Cro&rams usin& this type of inheritance will #e *ust as fast as pro&rams not usin& inheritance! 'n memory- a derived class is simply a #ase class o#*ect with its e)tra data mem#ers tacked on the end! 8or e)1 ample- suppose you have the followin& classes=
*lass Oase4lass { $rivate3 int baseP, baseR; #; *lass 9erived4lass: public (ase;lass { $rivate3 int derP, derR; #;
1 BA; 1
Address 8000 800J 800I 808H baseP baseR derP derR
"otice that the first ei&ht #ytes of this o#*ect are precisely the data mem#ers of a Oase4lass o#*ect! This is in part the reason that you can treat instances of a derived class as instances of a #ase class! This in1memory rep1 resentation of inheritance is e)tremely efficient and is one of the reasons that C++ was so popular in its infancy! Gost competin& o#*ect1oriented lan&ua&es represented o#*ects with considera#ly more complicated structures that re5uired comple) pointer lookups- so inheritance in those lan&ua&es incurred a steep runtime penalty! C++on the other hand- supported this simple form of inheritance with zero overhead! Inheritance of Interface The inheritance pattern outlined a#ove uses inheritance to add e7tensions to e)istin& classes! This is un1 dou#tedly useful- #ut does not arise fre5uently in practice! , different version of inheritance- called inheritance o" inter"ace- is e)traordinarily useful in modern pro&rammin&! et's return to the <rinter class! <rinter e)ports a $rint9o*ument mem#er function that accepts a string parameter- then sends the strin& to the printer! Hne of our other derived classes- Gra$hi*s<rinterhas a $rint<i*ture mem#er function that accepts some sort of <i*ture o#*ect- then sends the picture to the printer! 0hat if we want to print a document containin& a mi) of te)t and pictures ( for e)ample- this course reader> 0e'd then need to introduce yet another su#class of <rinter- perhaps a :i7ed;e7t<rinter- that sup1 ports a $rint:i7ed;e7t mem#er function that prints a com#ination of te)t and ima&es! 0hile we could con1 tinue to use the style of inheritance introduced a#ove- it will 5uickly spiral out of control for several reasons! 8irst- each printer can only print out one type of document! That is- a :i7ed;e7t<rinter cannot print out pic1 tures- nor a Gra$hi*s<rinter a mi)ed1te)t document! 0e could eliminate this pro#lem #y writin& a sin&le :i7ed;e7tAndGra$hi*s<rinter class- #ut this too has its pro#lems if we then introduce another type of o#1 *ect to print Ksay- a hi&h1resolution photoL that re5uired its own special printin& code! This leads to the second pro#lem- a lack of e)tensi#ility! 8or any new type of o#*ect we want to print- we need to introduce another mem#er function or class capa#le of handlin& that o#*ect! 'n our case this is inconvenient and does not scale well! 0e need to pick another plan of attack! The pro#lem is that everythin& that mi&ht &et sent to the printer re5uires sli&htly different lo&ic to print out! Te)t documents need to apply fonts and formattin& transformations- &raphics documents need to convert from some application1specific format into somethin& reada#le #y the printer- and mi)ed1te)t documents need to arran&e their te)t and ima&es into an appropriate layout #efore processin& each piece individually! 7ut each type of doc1 ument has one piece of functionality in common! Gost printers are pro&rammed to accept as input a &rid of dots representin& the document to print! That is- whether you're printin& te)t or a three1dimensional pie chart- the in1 put to the printer is a &rid of pi)els representin& the dots makin& up the ima&e! , te)t document mi&ht end up producin& different pi)els than a hi&h1resolution photo- #ut they #oth end up as pi)els at some point! Crovided that we can transform an o#*ect in memory into a mess of pi)els- we can send it to the printer! Consider the followin& class definition for a Generi*Oat*h<rinter o#*ect=
1 BAA 1
This Generi*Oat*h<rinter o#*ect e)ports an enVueue9o*ument function that stores an ar#itrary document in a print 5ueue that can then #e printed #y callin& $rintAll9o*uments! There is one ma*or 5uestion- thou&h= what should the parameter type #e> 0e can print any type of document we can think of- provided that we can convert it into a &rid of pi)els! 0e mi&ht #e tempted to accept a &rid of pi)els as a parameter! This- howeverhas several draw#acks! 8irst- pi)el &rids take up a hu&e amount of memory! Color printers usually store color information as 5uadruples of the cyan- ma&enta- yellow- and #lack KC.GKL color components- so a sin&le pi)el is usually a four1#yte value! 'f you have a ;00 2C' printer and want to print to an E!? ) 11` pa&e- you'd need to store around @?k#! That's a lot of memory- and if you wanted to en5ueue a lar&e num#er of documents this ap1 proach mi&ht strain or e)haust system resources! Clus- we don't actually need the pi)els until we #e&in printin&and even then we only need pi)el information for one document at a time! Second- what if later in desi&n we realize that we need e)tra information a#out the print *o#> 8or e)ample- suppose we want to implement a print1 in& priority system where more ur&ent documents print #efore less important ones! 'n this case- we'd need to add an e)tra parameter to enVueue9o*ument representin& that priority and all e)istin& code usin& enVueue9o*u, ment would stop workin&! 8inally- this approach e)poses too much of the inner workin&s of Generi*Oat*h, <rinter to the client! 7y treatin& documents as masses of pi)els instead of documents- the Generi*Oat*h, <rinter violates some of the fundamental rules of data a#straction! et's review these pro#lems= The a#ove approach is needlessly memory1intensive #y caterin& to the lowest common denominator of all possi#le printa#le documents! The approach limits later e)tensions #y fi)in& the parameter as an infle)i#le pi)el array! The Generi*Oat*h<rinter should work on documents- not pi)els!
's there a lan&ua&e feature that would let us solve all of these pro#lems> The answer is yes! 0hat if we simply create an o#*ect that looks like this=
*lass 9o*ument { $ubli*3 "2 4onstru*tor, destru*tor, et*. 2" grid $i7el;) *onvert;o<i7elArra%() *onst; "" &or some stru*t $i7el; int get<riorit%() *onst; $rivate3 "2 (m$lementation details 2" #;
This 9o*ument class e)ports two functions ( *onvert;o<i7elArra%()- which converts the document from its current format into a grid $i7el;) of the pi)els in the ima&e- and get<riorit%()- which returns the rel1 ative priority of the document!
1 BAB 1
0e can now have the enVueue9o*ument function from Generi*Oat*h<rinter accept a 9o*ument as a parameter! That way- when the Generi*Oat*h<rinter needs to &et an array of pi)els- it can simply call *on, vert;o<i7elArra% on any stored 9o*ument! Similarly- if the Generi*Oat*h<rinter decides to imple1 ment a priority system- it can use the information provided #y the 9o*ument's get<riorit%() function! Goreover- if later on durin& implementation we realize that Generi*Oat*h<rinter needs access to additional information- we can simply add e)tra mem#er functions to the 9o*ument class! 0hile this still re5uires us to rewrite code to add these mem#er functions- the actual calls to enVueue9o*ument will still work correctly- and the only people who need to modify any code is the 9o*ument class implementer- not the 9o*ument class cli1 ents! 0hile this solution mi&ht seem ele&ant- it still has a ma*or pro#lem ( how can we write a 9o*ument class that encompasses all of the possi#le documents we can try to print> The answer is simple= we can't! Usin& the lan1 &ua&e features we've covered so far- it simply isn't possi#le to solve this pro#lem! Consider for a minute what form our pro#lem looks like! 0e need to provide a 9o*ument o#*ect that represents a printa#le document- #ut we cannot write a sin&le um#rella class representin& every conceiva#le document! 'n1 stead of creatin& a sin&le 9o*ument class- what if we could create se!eral di""erent 9o*ument classes- each of which provided a workin& implementation of the *onvert;o<i7elArra% and get9o*umentEame functions> That is- we mi&ht have a ;e7t9o*ument class that stores a string and whose *onvert;o<i7elArra% con1 verts the strin& into a &rid of pi)els representin& that strin&! 0e could also have a Gra$hi*s9o*ument o#*ect with mem#er functions like add4ir*le or add(mage whose *onvert;o<i7elArra% function &enerates a &raphical representation of the stored ima&e! 'n other words- we want to make the 9o*ument class represent an inter"ace rather than an im#lementation! 9o*ument should simply outline what mem#er functions are common to other classes like Gra$hi*s9o*ument or ;e7t9o*ument without specifyin& how those functions should work! 'n C++ code- this means that we will rewrite the 9o*ument class to look like this=
*lass 9o*ument { $ubli*3 "2 4onstru*tor, destru*tor, et*. 2" )irtual int2 *onvert;o<i7elArra%() *onst , -; )irtual string get9o*umentEame() *onst , -; $rivate3 "2 (m$lementation details 2" #;
'f you'll notice- we ta&&ed #oth of the mem#er functions with the virtual keyword- and put an = 0 after each function declaration! 0hat does this stran&e synta) mean> The = 0 synta) is an odd #it of C++ synta) that says 4this function does not actually e)ist!6 'n other words- we've prototyped a function that we have no intention of ever writin&! 0hy would we ever want to do this> The reason is simple! 7ecause we've prototyped the func1 tion- other pieces of C++ code know what the parameter and return types are for the *onvert;o<i7elArra% and get9o*umentEame functions! /owever- since there is no meanin&ful implementation for either of these functions- we add the = 0 to tell C++ not to e)pect one! To understand the virtual keyword- consider this ;e7t9o*ument class outlined #elow=
1 BA? 1
This ;e7t9o*ument class inherits from 9o*ument- and althou&h 9o*ument has a declaration of the *on, vert;o<i7elArra% and get<riorit% functions- ;e7t9o*ument has specified that it too contains these functions! They are marked virtual- as in the 9o*ument class- #ut unlike 9o*ument's versions of these func1 tions the ;e7t9o*ument functions do not have the = 0 notation after them! This indicates that the functions ac1 tually e)ist and do have implementations! 0e won't cover how these functions are implemented since it's irrel1 evant to our discussion- #ut #ecause they have actual implementations code like this is perfectly le&al=
;e7t9o*ument m%9o*ument; grid $i7el;) arra% = myDocument.con)ertToPi#elUrray!$;
0e've covered the = 0 notation- #ut what does the virtual keyword mean> To understand how virtual works- consider the followin& code snippet=
;e7t9o*ument2 m%9o*ument = ne6 Te#tDocument; grid $i7el;) arra% = m%9o*ument,)*onvert;o<i7elArra%();
This code should not #e at all surprisin& ( we've *ust rewritten the a#ove code usin& a pointer to a ;e7t9o*u, ment rather than a stack1#ased ;e7t9o*ument! /owever- consider this code snippet #elow=
DocumentR m%9o*ument = ne' ;e7t9o*ument; "" Eote3 $ointer is a 9o*ument 2 grid $i7el;) arra% = myDocument->con)ertToPi#elUrray!$;
This code looks similar to the a#ove code #ut represents a fundamentally different operation! 'n the first line- we allocate a new ;e7t9o*ument o#*ect- #ut store it in a pointer of type 9o*ument 2! 'nitially- this mi&ht seem nonsensical ( pointers of type 9o*ument 2 should only #e a#le to point to o#*ects of type 9o*ument! /owever- #ecause ;e7t9o*ument is a derived class of 9o*ument- <ext0ocument is-a 0ocument! The is-a relation applies literally here ( since ;e7t9o*ument is-a 9o*ument- we can point to o#*ects of type ;e7t9o*, ument usin& pointers of type 9o*ument 2! Dven if we can point to o#*ects of type ;e7t9o*ument with o#*ects of type 9o*ument 2- why is the line m%9o*ument,)*onvert;o<i7elArra%() le&al> ,s mentioned earlier- the 9o*ument class definition says that 9o*ument does not have an implementation of *onvert;o<i7elArra%- so it seems like this code should not compile! This is where the virtual keyword comes in! Since we marked *onvert;o<i7elArra% vir, tual- when we call the *onvert;o<i7elArra% function throu&h a 9o*ument 2 o#*ect- C++ will call the function named *onvert;o<i7elArra% for the class that's actually being #ointed at- not the *on, vert;o<i7elArra% function defined for o#*ects of the type of the pointer! 'n this case- since our 9o*ument 2 is actually pointin& at a ;e7t9o*ument- the call to *onvert;o<i7elArra% will call the ;e7t9o*ument's version of *onvert;o<i7elArra%!
1 BA6 1
The a#ove approach to the pro#lem is known as #olymor#hism! 0e define a #ase class Kin this case 9o*umentL that e)ports several functions marked virtual! 'n our pro&ram- we pass around pointers to o#*ects of this #ase class- which may in fact #e pointin& to a #ase class o#*ect or to some derived class! 0henever we make mem#er function calls to the virtual functions of the #ase class- C++ fi&ures out at runtime what type of o#*ect is #ein& pointed at and calls its implementation of the virtual function! et's return to our Generi*Oat*h<rinter class- which now in its final form looks somethin& like this=
*lass Generi*Oat*h<rinter { $ubli*3 "2 4onstru*tor, destru*tor, et*. 2" void enVueue9o*ument(DocumentR do*); void $rintAll9o*uments(); $rivate3 Gueue.Document R> documents; #;
Hur enVueue9o*ument function now accepts a 9o*ument 2- and its private data mem#ers include an ST Vueue of 9o*ument 2s! 0e can now implement enVueue9o*ument and $rintAll9o*uments usin& code like this=
)oid Feneric(atchPrinter::enGueueDocument!DocumentR doc$ * documents.push!doc$; "" Ce*all 5;6 Vueue uses $ush instead of enVueue 4 )oid Feneric(atchPrinter::printUllDocuments!$ * TR Print all enGueued documents RT 6hile!%documents.empty!$$ * DocumentR ne#tDocument , documents. ront!$; documents.pop!$; "" Ce*all 5;6 Vueue reVuires e7$li*it $o$ o$eration sendToPrinter!ne#tDocument->con)ertToPi#elUrray!$$; delete ne#tDocument; "" Assume it 'as allo*ated 'ith ne' 4 4
The enVueue9o*ument function accepts a 9o*ument 2 and en5ueues it in the document 5ueue- and the $rintAll9o*uments function continuously de5ueues documents- converts them to pi)el arrays- then sends them to the printer! 7ut while this a#ove code mi&ht seem simple- it's actually workin& some wonders #ehind the scenes! "otice that when we call ne7t9o*ument,)*onvert;o<i7elArra%()- the o#*ect pointed at #y ne7t9o*ument could #e of any ty#e derived from 9o*ument! That is- the a#ove code will work whether we've en5ueued ;e7t9o*uments- Gra$hi*s9o*uments- or even :i7ed;e7t9o*uments! Goreover- the Gener, i*Oat*h<rinter class does not even need to know of the e)istence of these types of documentsR as lon& as Generi*Oat*h<rinter knows the &eneric 9o*ument interface- C++ can determine which functions to call! This is the main stren&th of inheritance ( we can write code that works with o#*ects of ar#itrary types #y identi1 fyin& the common functionality across those types and writin& code solely in terms of these operations!
Cha#ter 1>% Introduction to Inheritance Hirtual !unctionsF -ure Hirtual !unctionsF and Abstract Classes 'n the a#ove e)ample with the 9o*ument class- we defined 9o*ument as
*lass 9o*ument { $ubli*3 "2 4onstru*tor, destru*tor, et*. 2" virtual grid $i7el;) *onvert;o<i7elArra%() *onst = 0; virtual string get9o*umentEame() *onst = 0; $rivate3 "2 (m$lementation details 2" #;
1 BA@ 1
/ere- all of the 9o*ument mem#er functions are marked virtual and have the = 0 synta) to indicate that the functions are not actually defined! 8unctions marked virtual with = 0 are called #ure !irtual "unctions and represent functions that e)ist solely to define how other pieces of C++ code should interact with derived classes!Q Classes that contain pure virtual functions are called abstract classes! 7ecause a#stract classes contain code for which there is no implementation- it is ille&al to directly instantiate a#stract classes! 'n the case of our document e)ample- this means that #oth of the followin& are ille&al=
9o*ument m%9o*ument; "" Error0 9o*ument2 m%9o*ument = ne' 9o*ument; "" Error0
Hf course- it's still le&al to declare 9o*ument 2 varia#les- since those are pointers to a#stract classes rather than a#stract classes themselves! , derived class whose #ase class is a#stract may or may not implement all of the pure virtual functions defined in the #ase class! 'f the derived class does implement each function- then the derived class is non1a#stract Kun1 less- of course- it introduces its own pure virtual functionsL! Htherwise- if there is at least one pure virtual func1 tion declared in the #ase class and not defined in the derived class- the derived class itself will #e an a#stract class! There is no re5uirement that functions marked virtual #e pure virtual functions! That is- you can provide virtual functions that have implementations! 8or e)ample- consider the followin& class representin& a roller1 #lader=
*lass CollerOlader { $ubli*3 "2 4onstru*tor, destru*tor, et*. 2" )irtual )oid slo6Do6n!$; "" Dirtual, not $ure virtual $rivate3 "2 (m$lementation details 2" #;
Q Those of you familiar with inheritance in other lan&ua&es like $ava mi&ht wonder why C++ uses the awkward = 0 syn1 ta) instead of a clearer keyword like abstra*t or $ure! The reason was mostly political! 7*arne Stroustrup introduced pure virtual functions to the C++ lan&ua&e several weeks #efore the planned release of the ne)t set of revisions to C++! ,ddin& a new keyword would have delayed the ne)t lan&ua&e release- so to ensure that C++ had support for pure virtual functions- he chose the = 0 synta)!
1 BAE 1
/ere- slo'9o'n is implemented as a virtual function that is not pure virtual! 'n the implementation of slo', 9o'n- you do not repeat the virtual keyword- and for all intents and purposes treat slo'9o'n as a re&ular C++ function! "ow- suppose we write a (ne7$erien*edCollerOlader class- as shown here=
*lass (ne7$erien*edCollerOlader: public Coller(lader { $ubli*3 "2 4onstru*tor, destru*tor, et*. 2" )irtual )oid slo6Do6n!$; $rivate3 "2 (m$lementation details 2" #; void (ne7$erien*edCollerOlader33slo'9o'n() { fall9o'n(); #
This (ne7$erien*edCollerOlader class provides its own implementation of slo'9o'n that calls some fall9o'n function!Q "ow- consider the followin& code snippet=
CollerOlader2 blader = ne' CollerOlader; blader->slo6Do6n!$; CollerOlader2 bladerH = ne' (ne7$erien*edCollerOlader; blader8->slo6Do6n!$;
'n #oth cases- we call the slo'9o'n function throu&h a pointer of type CollerOlader 2- so C++ will call the version of slo'9o'n for the class that's actually pointed at! 'n the first case- this will call the CollerOlader's version of slo'9o'n- which calls a$$l%Ora+es! 'n the second- since bladerH points to an (ne7$eri, en*edCollerOlader- the slo'9o'n call will call (ne7$erien*edCollerOlader's slo'9o'n functionwhich then calls fall9o'n! 'n &eneral- when callin& a virtual function- C++ will invoke the version of the func1 tion that corresponds to the most derived implementation availa#le in the o#*ect #ein& pointed at! 7ecause the (ne7$erien*edCollerOlader implementation of slo'9o'n replaces the #ase class version- (ne7$eri, en*edCollerOlader's implementation slo'9o'n is said to o!erride CollerOlader's! 0hen inheritin& from non1a#stract classes that contain virtual functions- there is no re5uirement to provide your own implementation of the virtual functions! 8or e)ample- a 5tuntCollerOlader mi&ht #e a#le to do tricks a re&ular CollerOlader can't- #ut still slows down the same way! 'n code we could write this as
*lass 5tuntCollerOlader: public Coller(lader { $ubli*3 "2 Eote3 no mention of slo6Do6n 2" void ba*+fli$(); void tri$leA7el(); #;
1 BA< 1
'f we then were to write code that used 5tuntCollerOlader- callin& slo'9o'n would invoke CollerOlader's version of slo'9o'n since it is the most derived implementation of slo'9o'n availa#le to 5tuntCollerOlader! 8or e)ample=
CollerOlader2 blader = ne' 5tuntCollerOlader; blader->slo6Do6n!$; "" 4alls CollerOlader33slo'9o'n
Similarly- if we were to create a class ;erribl%(ne7$erien*edCollerOlader that e)ports a $ani* func1 tion #ut no slo'9o'n function- as shown here=
*lass ;erribl%(ne7$erien*edCollerOlader: public Ine#periencedColler(lader { $ubli*3 "2 Eote3 no referen*e to slo6Do6n 2" void $ani*(); #;
Then the followin& code will invoke (ne7$erien*edCollerOlader33slo'9o'n- causin& the roller #lader to fall down=
CollerOlader2 blader = ne' ;erribl%(ne7$erien*edCollerOlader; blader->slo6Do6n!$;
'n this last e)ample we wrote a class that derived from a class which itself was a derived class! This is perfectly le&al and arises commonly in pro&rammin& practice! A /ord of /arning Consider the followin& two classes=
*lass EotDirtual { $ubli*3 )oid notU5irtualFunction!$; #; *lass EotDirtual9erived3 public /ot5irtual { $ubli*3 )oid notU5irtualFunction!$; #;
/ere- the #ase class EotDirtual e)ports a function called notADirtual&un*tion and its derived classEotDirtual9erived- also provides a notADirtual&un*tion function! ,lthou&h these functions have the same name- since notADirtual&un*tion is not marked virtual- the derived class version does not replace the #ase class version! Consider this code snippet=
EotDirtual2 nv = ne' EotDirtual9erived; n)->notU5irtualFunction!$;
/ere- since EotDirtual9erived is1a EotDirtual- the a#ove code will compile! /owever- since notADir, tual&un*tion is Kas its name su&&estsL not a virtual function- the a#ove code will call the EotDirtual ver1 sion of notADirtual&un*tion- not EotDirtual9erived's notADirtual&un*tion!
1 BB0 1
'f you want to let derived classes override functions in a #ase class- you must mark the #ase class's function virtual! Htherwise- C++ won't treat the function call virtually and will always call the version of the function associated with the type of the pointer! 8or e)ample=
EotDirtual2 nv = ne' EotDirtual9erived; n)->notU5irtualFunction!$; "" 4alls /ot5irtual33notADirtual&un*tion() EotDirtual9erived 2nvH = ne' EotDirtual9erived; n)8->notU5irtualFunction!$; "" 4alls /ot5irtualDeri)ed33notADirtual&un*tion();
'n &eneral- it is considered #ad pro&rammin& practice to have a derived class implement a mem#er function with the same name as a non1virtual function in its #ase class! 2oin& so leads to the sorts of odd #ehavior shown a#ove and is an easy source of errors! The protected Access S&ecifier et's return to the 9o*ument class from earlier in the chapter! Suppose that while desi&nin& some of the 9o*u, ment su#classes- we note that every sin&le su#class ends up havin& a 'idth and height field! To minimize code duplication- we decide to move the 'idth and height fields from the derived classes into the 9o*ument #ase class! Since we don't want 9o*ument class clients directly accessin& these fields- we decide to mark them $rivate- as shown here=
*lass 9o*ument { $ubli*3 "2 4onstru*tor, destru*tor, et*. 2" virtual grid $i7el;) *onvert;o<i7elArra%() *onst = 0; virtual string get9o*umentEame() *onst = 0; $rivate3 int 6idthB height; "" 1arning3 slight $roblem here #;
/owever- #y movin& 'idth and height into the 9o*ument #ase class- we've accidentally introduced a pro#1 lem into our code! Since 'idth and height are $rivate- even thou&h ;e7t9o*ument and the other su#1 classes inherit from 9o*ument- the su#classes will not #e a#le to access the 'idth and height fields! 0e want the 'idth and height fields to #e accessi#le only to the derived classes- #ut not to the outside world! Usin& only the C++ we've covered up to this point- this is impossi#le! /owever- there is a third access specifier #eyond $ubli* and $rivate called protected that does e)actly what we want! 2ata mem#ers and functions marked $rote*ted- like $rivate data mem#ers- cannot #e accessed #y class clients! /owever- unlike $rivate vari1 a#les- $rote*ted functions and data mem#ers are accessi#le #y derived classes!
$rote*ted is a useful access specifier that in certain circumstances can make your code 5uite ele&ant! /owever- you should #e very careful when &rantin& derived classes $rote*ted access to data mem#ers! ike $ubli* data mem#ers- usin& $rote*ted data mem#ers locks your classes into a sin&le implementation and can make code chan&es down the line difficult to impossi#le! Gake sure that markin& a data mem#er $rote*ted is truly the ri&ht choice #efore proceedin&! /owever- markin& member "unctions $rote*ted is a common pro1
&rammin& techni5ue that lets you e)port mem#er functions only usa#le #y derived classes! 0e will see an e)1 ample of $rote*ted mem#er functions later in this chapter!
Cha#ter 1>% Introduction to Inheritance Hirtual 9estructors ,n important topic we have i&nored so far is !irtual destructors! Consider the followin& two classes=
*lass Oase4lass { $ubli*3 Oase4lass(); ^Oase4lass(); #; *lass 9erived4lass: public (ase;lass { $ubli*3 9erived4lass(); ^9erived4lass(); $rivate3 *har2 m%5tring; #; 9erived4lass339erived4lass() { myString , ne6 char978A:; "" Allo*ate some memor% # 9erived4lass33^9erived4lass() { delete 9: myString; "" 9eallo*ate the memor% #
1 BB1 1
/ere- we have a trivial constructor and destructor for Oase4lass! 9erived4lass- on the other hand- has a constructor and destructor that allocate and deallocate a #lock of memory! 0hat happens if we write the follow1 in& code>
Oase4lass2 m%4lass = ne' 9erived4lass; delete my;lass;
'ntuitively- you'd think that since m%4lass points to a 9erived4lass o#*ect- the 9erived4lass destructor would invoke and clean up the dynamically1allocated memory! Unfortunately- this is not the case! The m%4, lass pointer is statically1typed as a Oase4lass 2 #ut points to an o#*ect of type 9erived4lass- so delete m%4lass results in unde"ined beha!ior! The reason for this is that we didn't let C++ know that it should check to see if the o#*ect pointed at #y a Oase4lass 2 is really a 9erived4lass when callin& the destructor! Un1 defined #ehavior is never a &ood thin&- so to fi) this we mark the Oase4lass destructor virtual! Unlike the other virtual functions we've encountered- thou&h- derived class destructors do not replace the #ase class de1 structors! 'nstead- when invokin& a destructor virtually- C++ will first call the derived class destructor- then the #ase class destructor! 0e thus chan&e the two class declarations to look like this=
*lass Oase4lass { $ubli*3 Oase4lass(); )irtual ^Oase4lass(); #;
1 BB; 1
*lass 9erived4lass3 $ubli* Oase4lass { $ubli*3 9erived4lass(); ^9erived4lass(); $rivate3 *har 2m%5tring; #;
There is one more point to address here- the #ure !irtual destructor! 7ecause virtual destructors do not act like re&ular virtual functions- even if you mark a destructor pure virtual- you must still provide an implementation! Thus- if we rewrote Oase4lass to look like
*lass Oase4lass { $ubli*3 Oase4lass(); )irtual ^Oase4lass() = 0; #;
0e'd then need to write a trivial implementation for the Oase4lass destructor- as shown here=
Oase4lass33^Oase4lass() { "" 9o nothing #
This is an unfortunate lan&ua&e 5uirk- #ut you should #e aware of it since this will almost certainly come up in the future! 1untime Costs of Hirtual !unctions Tirtual functions are incredi#ly useful and syntactically concise- #ut e)actly how efficient are they> ,fter all- a virtual function call invokes one of many possi#le functions- and somehow the compiler has to determine which version of the function to call! There could #e an ar#itrarily lar&e num#er of derived classes overridin& the par1 ticular virtual function- so a nalve s'it*h statement that checks the type of the o#*ect would #e prohi#itively e)pensive! 8ortunately- most C++ compilers use a particularly clever implementation of virtual functions thatwhile slower than re&ular function calls- are much faster than what you may have initially e)pected! Consider the followin& classes=
*lass Oase4lass { $ubli*3 virtual ^Oase4lass() {# "" <ol%mor$hi* *lasses need virtual destru*tors virtual void do5omething(); $rivate3 int baseP, baseR; #; *lass 9erived4lass3 $ubli* Oase4lass { $ubli*3 virtual void do5omething(); "" =verride Oase4lass version $rivate3 int derP, derR; #;
Cha#ter 1>% Introduction to Inheritance Then the in1memory representation of a 9erived4lass o#*ect looks like this=
Address 8000 )tableR 800J 800I 808H 808M baseP baseR derP derR .- Deri)ed;lass1specific mem#ers .- (ase;lass mem#ers
1 BBA 1
,s #efore- we see the Oase4lass mem#ers followed #y the 9erived4lass mem#ers- #ut there is now another piece of data in this o#*ect= the )tableR- or !table-#ointer! This vta#le1pointer is a #ointer to the !irtual "unction table! 0henever you create a class containin& one or more virtual functions- C++ will create a ta#le con1 tainin& information a#out that class that includes metadata a#out the class alon& with a list of function pointers for each of the virtual functions in that class! 8or e)ample- here's a dia&ram of a Oase4lass o#*ect- a 9e, rived4lass o#*ect- and their respective virtual function ta#les= )tableR baseP baseR (ase;lass Ob5'&t )tableR baseP baseR derP derR Deri)ed;lass Ob5'&t The virtual function ta#le for Oase4lass #e&ins with metadata a#out the Oase4lass type- then has two func1 tion pointers ( one for the Oase4lass destructor and one for Oase4lass's implementation of do5omething! The 9erived4lass virtual function ta#le similarly contains information a#out 9erived4lass- as well as function pointers for the destructor and do5omething mem#er functions! 'f you'll notice- the virtual function ta#les for Oase4lass and 9erived4lass have the mem#er functions listed in the same order- with the de1 structor first and then do5omething! This allows C++ to invoke virtual functions 5uickly and efficiently! Sup1 pose that we have the followin& code=
Oase4lass2 m%<tr = Candom4han*e(0.K)S ne6 (ase;lass 3 ne6 Deri)ed;lass; myPtr->doSomething!$; delete myPtr;
1 BBB 1
0e assi&n a random o#*ect to m%<tr that is either a Oase4lass or a 9erived4lass usin& the Candom4han*e function we wrote in the chapter on Snake! 0e then invoke the do5omething mem#er function on the o#*ect and then delete it! To implement this functionality- the C++ compiler compiles the second two lines into ma1 chine code that performs the followin& operations=
TT myPtr->doSomething!$;
1! ook at the first four #ytes of the o#*ect pointed at #y m%<trR this is the vtable2 for the o#*ect! ;! 8ollow the vtable2 and retrieve the second function pointer from the ta#leR this corresponds to do5omething! A! Call this function!
TT delete myPtr;
1! ook at the first four #ytes of the o#*ect pointed at #y m%<tr! ;! 8ollow the vtable2 and retrieve the first function pointer from the ta#leR this corresponds to the de1 structor! A! Call this function! B! 2eallocate the memory pointed at #y m%<tr! This se5uence of commands can #e e)ecuted 5uickly and is efficient no matter how many su#classes of Oase, 4lass e)ist! 'f there are millions of derived classes- this code still only has to make a sin&le lookup throu&h the virtual function ta#le to call the proper function! ,lthou&h this a#ove implementation of virtual function calls is considera#ly more efficient than a nalve ap1 proach- it is still noticea#ly slower than a re&ular function call #ecause of the necessary virtual function ta#le lookups! This e)tra overhead is the reason that C++ re5uires you to e)plicitly mark mem#er functions you want to treat polymorphically virtual ( if all functions were called this way- you would pay a performance hit irre1 spective of whether you actually used inheritance- a violation of the zero1overhead principle! In7oking Hirtual #ember !unctions <onCHirtually 8rom time to time- you will need to #e a#le to e)plicitly invoke a #ase class's version of a virtual function! 8or e)ample- suppose that you're desi&nin& a /%brid4ar that's a specialization of 4ar- #oth of which are defined #elow=
*lass 4ar { $ubli*3 virtual ^4ar(); "" <ol%mor$hi* *lasses need virtual destru*tors )irtual )oid apply(ra+es!$; )irtual )oid accelerate!$; #; *lass /%brid4ar: public ;ar { $ubli*3 )irtual )oid apply(ra+es!$; )irtual )oid accelerate!$; $rivate3 void *hargeOatter%(); void dis*hargeOatter%(); #;
The /%brid4ar is e)actly the same as a re&ular car- e)cept that whenever a /%brid4ar slows down or speeds up- the /%brid4ar char&es and dischar&es its electric motor to conserve fuel! 0e want to implement the a$,
1 BB? 1
$l%Ora+es and a**elerate functions inside /%brid4ar such that they perform e)actly the same tasks as the 4ar's version of these functions- #ut in addition perform the e)tra motor mana&ement!
The a#ove code is well1intentioned #ut incorrect! ,t a hi&h level- we want to have the hy#rid car accelerate or apply its #rakes #y doin& whatever a re&ular car does- then mana&in& the motor! ,s written- thou&h- these func1 tions will cause a stack overflow- since the calls to a$$l%Ora+es() and a**elerate() recursively invoke the /%brid4ar's versions of these functions over and over! Since this doesn't work- what other approaches mi&ht we try> 8irst- we could simply copy and paste the code from the 4ar class into the /%brid4ar class! This should cause you to crin&e ( a &ood solution to a pro#lem should ne!er involve copyin& and pastin& codeU Gore concretely- thou&h- this approach has several pro#lems! 8irst- if we chan&e the implementation of a**eler, ate() or a$$l%Ora+es() in the 4ar class- we have to remem#er to make the same chan&es inside /%brid, 4ar! 'f we for&et to do so- the code will compile #ut will #e incorrect! Goreover- if the implementation of a*, *elerate() or a$$l%Ora+es() in the 4ar class reference private data mem#ers or mem#er functions of 4arthe resultin& code will #e ille&al! This clearly isn't the ri&ht way to solve this pro#lem! 0hat other options are availa#le> , second idea is to factor out the code for a$$l%Ora+es and a**elerate into $rote*ted- non1virtual func1 tions of the 4ar class! 8or e)ample=
*lass 4ar { $ubli*3 virtual ^4ar(); virtual void a$$l%Ora+es() { doUpply(ra+es(); # virtual void a**elerate() { doUccelerate(); # $rote*ted3 )oid doUpply(ra+es!$; "" Eon,virtual fun*tion that a*tuall% slo's do'n. )oid doUccelerate!$; "" Eon,virtual fun*tion that a*tuall% a**elerates. #; *lass /%brid4ar3 $ubli* 4ar { $ubli*3 virtual void a$$l%Ora+es() { doUpply(ra+es!$; *hargeOatter%(); # virtual void a**elerate() { doUccelerate!$; dis*hargeOatter%(); # $rivate3 void *hargeOatter%(); void dis*hargeOatter%(); #;
1 BB6 1
/ere- the virtual functions a$$l%Ora+es and a**elerate are wrapped calls to non1virtual- protected functions written in the #ase class! To implement the derived versions of a$$l%Ora+es and a**elerate- we can simply call these functions! This approach is stylistically pleasin&! The code that's common to a$$l%Ora+es and a**elerate is factored out into helper mem#er functions- so chan&es to one function appear in the other! 7ut there's one minor pro#lem with this approach= this solution only works if we can modify the 4ar class! 'n small pro*ects this shouldn't #e a pro#lem- #ut if these classes are pieces in a much lar&er system the code may #e off1limits ( may#e it's #ein& de1 veloped #y another team- or perhaps it's #een compiled into a pro&ram that e)pects the class definition to pre1 cisely match a specific pattern! This idea is clearly on the ri&ht track- #ut in some cases cannot work! The optimal solution to this conundrum- however- is to simply have the /%brid4ar's implementations of these functions directly call the versions of these functions defined in 4ar! 0hen callin& a virtual function throu&h a pointer or reference- C++ ensures that the function call will 4fall down6 to the most derived class's implementa1 tion of that function! /owever- we can force C++ to call a specific version of a virtual function #y callin& it us1 in& the function's fully15ualified name! 8or e)ample- consider this version of the /%brid4ar's version of a$, $l%Ora+es5
void /%brid4ar33a$$l%Ora+es() { ;ar::apply(ra+es!$; "" 4all 4arAs version of a$$l%Ora+es, no $ol%mor$hism *hargeOatter%(); #
The synta) 4ar33a$$l%Ora+es instructs C++ to call the 4ar class's version of a$$l%Ora+es! Dven thou&h a$$l%Ora+es is virtual- since we've used the fully15ualified name- C++ will not resolve the call at runtime and we are &uaranteed to invoke 4ar's version of the function! 0e can write an a**elerate function for /%brid, 4ar similarly! 0hen usin& the fully15ualified1name synta)- you're allowed to access any superclass's version of the functionnot *ust the direct ancestor! 8or e)ample- if 4ar were derived from the even more &eneric class &our1heeled, Dehi*le that itself provides an a$$l%Ora+es method- we could invoke that version from /%brid4ar #y writ1 in& &our1heeledDehi*le33a$$l%Ora+es()! .ou can also use the fully15ualified name synta) as a class client- thou&h it is rare to see this in practice! 2b?ect Initiali3ation in 9eri7ed Classes Fecall from several chapters a&o that class construction proceeds in three steps ( allocatin& space to hold the o#1 *ect- callin& the constructors of all data mem#ers- and invokin& the o#*ect constructor! 0hile this picture is mostly correct- it omits an important step ( initialization of #ase classes! et's suppose we have the followin& classes=
1 BB@ 1
7ecause we have not defined a constructor for 9erived- C++ will automatically supply it with a default- zero1 ar&ument constructor that invokes the default constructor of the Oase o#*ect! To see what this means- let's trace throu&h the construction of a new 9erived o#*ect! 8irst- C++ &ives the o#*ect a #lock of uninitialized memory with enou&h space to hold all of the parts of the 9erived! This memory looks somethin& like this=
(ase 9ata #embers int myInt string myString WWW 1ength: WWW Te#t: WWW
)ector.int> my5ector
,t this point- C++ will initialize the Oase class usin& its default constructor! Similarly- if Oase has any parent classes- those parent classes would also #e initialized! ,fter this step- the o#*ect now looks like this=
(ase 9ata #embers int myInt string myString 7DE 1ength: 77 Te#t: "(ase String"
)ector.int> my5ector
1 BBE 1
7y default- derived class constructors invoke the default constructor for their #ase classes- or a zero1ar&ument constructor if one has e)plicitly #een defined! This is often- #ut not always- the desired #ehavior for a class! 7ut what if you want to invoke a different constructor> 8or e)ample- let's return to the 4ar e)ample from earlier in this chapter! Suppose that 4ar e)ports a sin&le constructor that accepts a string encodin& the license num#er! 8or e)ample=
*lass 4ar { $ubli*3 e#plicit ;ar!const stringS license/um$ : license!license/um$ *4 virtual ^4ar() {# virtual void a**elerate(); virtual void a$$l%Ora+es(); $rivate3 *onst string li*ense; #;
7ecause 4ar no lon&er has a default constructor- the previous definition of /%brid4ar will cause a compile1 time error #ecause the /%brid4ar constructor cannot call the none)istent default constructor for 4ar! /ow can we tell /%brid4ar to invoke the 4ar constructor with the proper ar&uments> The answer is similar to how we would construct a data mem#er with a certain value ( we use the initializer list! /ere is a modified version of the /%brid4ar class that correctly initializes its 4ar #ase class=
*lass /%brid4ar3 $ubli* 4ar { $ubli*3 e#plicit 'ybrid;ar!const stringS license$ : ;ar!license$ *4 virtual void a$$l%Ora+es(); virtual void a**elerate(); $rivate3 void *hargeOatter%(); void dis*hargeOatter%(); #;
"ote that when usin& initializer lists to initialize #ase classes- you are only allowed to specify the names of direct #ase classes! ,s an e)ample- suppose that we want to create a class called E7$erimental/%brid4ar that is similar to a /%brid4ar e)cept that it contains e)tra instrumentation to monitor the state of the motor! 7e1 cause E7$erimental/%brid4ar represents a prototype car- the car does not have a license plate- and so we want to communicate the strin& 4"one6 up to 4ar to represent this information! Then if we define the E7$eri, mental/%brid4ar class as follows=
*lass 2#perimental'ybrid;ar: public 'ybrid;ar { $ubli*: 2#perimental'ybrid;ar!$; virtual void a$$l%Ora+es(); virtual void a**elerate(); #;
1 BB< 1
The pro#lem with this code is that 4ar is an indirect #ase class of E7$erimental/%brid4ar and thus cannot #e initialized from the E7$erimental/%brid4ar initializer list! The reason for this is simple! E7$eriment, al/%brid4ar inherits from /%brid4ar- which itself inherits from 4ar! 0hat would happen if #oth /%brid, 4ar and E7$erimental/%brid4ar each tried to initialize 4ar in their initializer lists> 0hich constructor should take precedence> 'f it's /%brid4ar- then the initializer list for E7$erimental/%brid4ar would #e i&1 nored- leadin& to misleadin& code! 'f it's E7$erimental/%brid4ar- then if /%brid4ar needs to call the 4ar constructor with particular ar&uments- those ar&uments would #e i&nored and /%brid4ar mi&ht not #e initial1 ized correctly! To avoid this sort of confusion- C++ only lets you initialize direct #ase classes! Thus the proper version of the E7$erimental/%brid4ar constructor is as follows=
"2 ;ell /%brid4ar to initialiUe itself 'ith the string .Eone. 2" E7$erimental/%brid4ar33E7$erimental/%brid4ar() 3 'ybrid;ar!"/one"$ { #
Since /%brid4ar forwards its constructor ar&ument up to 4ar- this ends up producin& the correct #ehavior! Hirtual !unctions in Constructors et's take a 5uick look #ack at class construction for derived classes! 'f you'll recall- #ase classes are initialized #efore any of the derived class data mem#ers are set up! This means that there is a small window when the #ase class constructor e)ecutes where the #ase class is fully set up- #ut nothin& in the derived class has yet #een ini1 tialized! 'f the #ase class constructor could somehow access the data mem#ers of the derived class- it would read uninitialized memory and almost certainly crash the pro&ram! 7ut this seems impossi#le ( after all- the #ase class has no idea what's derivin& from it- so how could it access any of the derived class's data mem#ers> Unfortunately- there is one way ( virtual functions! Suppose the #ase class contains a virtual function and that one of the derived classes overrides that function to read a data mem#er of the derived class! 'f the #ase class calls the virtual function in its constructor- it would #e a#le to read the uninitialized value- causin& a potential pro&ram crash! The desi&ners of C++ were well1aware of this ed&e case- and to prevent this error from occurrin& they added a restriction on the #ehavior of virtual function calls inside constructors! 'f you invoke a virtual function inside a class constructor- the function is not invoked polymorphically! That is- the virtual function call will always call the version of the function appropriate for the type of the #ase class rather than the type of the derived class! To see this in action- consider the followin& code=
*lass Oase { $ubli*3 Oase() { n!$; # virtual void fn() { cout .. "(ase" .. endl; # #;
1 B?0 1
*lass 9erived: public (ase { $ubli*3 virtual void fn() { cout .. "Deri)ed" .. endl; # #;
/ere- the Oase constructor invokes its virtual function fn! 0hile normally you would e)pect that this would in1 voke 9erived's version of fn- since we're inside the #ody of the Oase constructor- the code will e)ecute Oase's version of fn- which prints out 47ase6 instead of the e)pected 42erived!6 Cases where you would invoke a vir1 tual function in a constructor are rare- #ut if you plan on doin& so remem#er that it will not #ehave as you mi&ht e)pect! Dverythin& we've discussed in this section has focused on class constructors- #ut these same restrictions apply to class destructors as well! C++ destructs classes from the outside inward- cleanin& up derived classes #efore #ase classes- and if virtual functions were treated polymorphically inside destructors it would #e possi#le to access data mem#ers of a derived class after they'd already #een cleaned up! Co&y Constructors and Assignment 2&erators for 9eri7ed Classes Copy constructors and assi&nment operators are complicated #easts that are even more perilous when mi)ed with inheritance! 'n particular- you must make sure to invoke the copy constructor and assi&nment operator for any #ase classes in addition to any other #ehavior! ,s an e)ample- consider the followin& #ase class- which has a well1defined copy constructor and assi&nment operator=
*lass Oase { $ubli*3 Oase(); Oase(*onst Oase? other); Oase? o$erator= (*onst Oase? other); virtual ^Oase(); $rivate3 "2 ... im$lementation s$e*ifi* ... 2" #;
Usin& the template outlined in the chapter on copy functions- we mi&ht write the followin& code for the 9e, rived assi&nment operator and copy constructor=
1 B?1 1
'nitially- it seems like this code should work- #ut- alas- it is seriously flawed! 2urin& this copy operation- we never instructed C++ to copy over the data from other's #ase class into the receiver o#*ect's #ase class! ,s a result- we'll end up with half1copied data- where the data specific to 9erived is correctly cloned #ut Oase's data hasn't chan&ed! To see this visually- if we have two o#*ects of type 9erived that look like this= one6( Oase t'o6( Oase
one6( 9erived
t'o6( 9erived
,fter invokin& the copy functions implemented a#ove- the o#*ects would end up in this state= one6( Oase t'o6( Oase
t6o6( 9erived
t'o6( 9erived
0e now have a partially1copied o#*ect- which will almost certainly crash at some point down the line!
1 B?; 1
0hen writin& assi&nment operators and copy constructors for derived classes- you must make sure to manually invoke the assi&nment operators and copy constructors for #ase classes to &uarantee that the o#*ect is fully1 copied! 8ortunately- this is not particularly difficult! et's first focus on the copy constructor! Somehow- we need to tell the receiver's #ase o#*ect that it should initialize itself as a copy of the parameter's #ase o#*ect! 7e1 cause 9erived is-a Oase- so we can pass the parameter to the 9erived copy constructor as a parameter to Oase's copy constructor inside the initializer list! The updated version of the 9erived copy constructor looks like this=
"2 4o$% *onstru*tor. 2" 9erived339erived(*onst 9erived ?other) : (ase!other$ "" 4orre*t { *o$%=ther(other); #
The code we have so far for the assi&nment operator correctly clears out the 9erived part of the 9erived class#ut leaves the Oase portion untouched! /ow should we &o a#out assi&nin& the Oase part of the receiver o#*ect the 9erived part of the parameter> Simple ( we'll invoke the Oase's assi&nment operator and have Oase do its own copyin& work! The code for this is a #it odd and is shown #elow=
"2 Assignment o$erator. 2" 9erived? 9erived33o$erator= (*onst 9erived ?other) { if(this 0= ?other) { *lear(); (ase::operator, !other$; "" (nvo+e the assignment o$erator from Oase. *o$%=ther(other); # return 2this; #
/ere we've inserted a call to Oase's assi&nment operator usin& the full name of the o$erator = function! This is one of the rare situations where you will need to use the full name of an overloaded operator! 'n case you're curious why *ust writin& 2this = other won't work- remem#er that this calls 9erived's version of o$erat, or =- causin& infinite recursion! ,ll of the a#ove discussion has assumed that your classes re5uire their own assi&nment operator and copy con1 structor! /owever- if your derived class does not contain any data mem#ers that re5uire manual copyin& and as1 si&nment Kfor e)ample- a derived class that simply holds an intL- none of the a#ove code will #e necessary! C++'s default assi&nment operator and copy constructor automatically invoke the assi&nment operator and copy constructor of any #ase classes- which is e)actly what you'd want it to do! 9isallowing Co&ying Usin& inheritance- it's possi#le to ele&antly and concisely disallow copyin& for o#*ects of a certain type! ,s mentioned a#ove- a class's default copy constructor and assi&nment operator automatically invoke the copy con1 structor and assi&nment operator for any #ase classes! 7ut what if for some reason the derived class can't call those functions> 8or e)ample- suppose that we have the followin& class=
1 B?A 1
,s mentioned in the chapter on copy constructors and assi&nment operators- this class cannot #e copied #ecause the copy constructor and assi&nment operator are marked private! 0hat will happen if we then create a class that inherits from @n*o$%able- as shown here=
*lass :%4lass3 public Uncopyable { "2 ... 2" #;
et's assume that :%4lass does not e)plicitly declare a copy constructor or assi&nment operator! This will cause C++ to try to create a default implementation for these functions! 'n the process of doin& so- the compiler will realize that it needs to call the copy constructor and assi&nment operator of @n*o$%able! 7ut these func1 tions are $rivate- meanin& that the derived class :%4lass can't access them! Father than reportin& this as an error- instead the compiler doesn't create default implementations of these functions! This means that :%4lass has no copy constructor or assi&nment operator- not even default implementations- and thus can't #e copied or assi&ned! 0e've successfully disallowed copyin&U /owever- #y inheritin& from @n*o$%able- we've introduced some undesira#le #ehavior! 't is now le&al for cli1 ents of :%4lass to treat :%4lass as thou&h it were an @n*o$%able- as shown here=
:%4lass2 m* = ne' :%4lass; UncopyableR uPtr = m*;
This is unfortunate- since @n*o$%able is an implementation detail of :%4lass- not a supertype! 0e are now in a rather interestin& situation! 0e want to a#sor# the functionality provided #y another class- #ut don't want to make our type a su#type of that class in the process! 'n other words- we want to a#sor# an im#lementation without its correspondin& inter"ace! 8ortunately- usin& a techni5ue called #ri!ate inheritance, we can e)press this notion precisely! So far- the inheritance you have seen has #een #ublic inheritance! 0hen a class pu#licly inherits from a #ase class- it a#sor#s the pu#lic interface of the #ase class alon& with any implementations of the functions in that in1 terface! 'n private inheritance- a derived class inherits from a #ase class solely to ac5uire its implementation! 0hile the derived class retains the implementation of all $ubli* mem#er functions from the #ase class- those functions #ecome $rivate in the derived class! 8or e)ample- &iven these two classes=
*lass Oase { $ubli*3 void do5omething(); #; *lass 9erived3 pri)ate (ase { "2 ... 2" #;
Dven thou&h do5omething was declared $ubli* in Oase- #ecause 9erived inherits privately from Oase the do5omething mem#er function is $rivate! ,dditionally- private inheritance does not define a su#typin& relationship! That is- the followin& code is ille&al=
Oase2 $tr = ne' 9erived; "" Error
0hile pu#lic inheritance models the is-a relationship- private inheritance represents the is-im#lemented-interms-o" relationship! 8or e)ample- we mi&ht use private inheritance to implement a sta*+ in terms of a deVue- since a sta*+'s entire functionality can #e e)pressed throu&h proper calls to $ushBfront- front- and $o$Bfront! This is shown here for a sta*+ of inte&ers=
*lass sta*+3 pri)ate deGue.int> { $ubli*3 void $ush(int val) { push& ront!)al$; "" 4alls deVue int)33$ushBfront. # int $o$() { *onst int result = ront!$; pop& ront!$; return result; # "2 ... et*. ... 2" #;
"otice that $ush is implemented as a call to the deVue's $ushBfront function- while $o$ is implemented throu&h a series of calls to front and $o$Bfront! 7ecause we privately inherited from deVue int)- our class contains an implementation of all of the deVue's mem#er functions- and it is as if we have our own private copy of a deVue that we can work with! Cu#lic and private inheritance are desi&ned for entirely different purposes! 0e use pu#lic inheritance to desi&n a collection of classes lo&ically related to each other #y some common #ehaviors! Crivate inheritance- on the other hand- is an implementation techni5ue used to define one class's #ehaviors in terms of another's! Hne way to re1 mem#er the difference #etween pu#lic and private inheritance is to reco&nize that they play entirely different roles in class desi&n! Cu#lic inheritance is used durin& the desi&n of a class inter"ace Kdeterminin& what #ehavi1 ors the class should provideL- while private inheritance is used durin& desi&n of a class im#lementation Khow those #ehaviors should #e performedL! This parallels the difference #etween a function prototype and a function definition ( pu#lic inheritance defines a set of prototypes- while private inheritance provides implementations! Crivate inheritance is not fre5uently encountered in practice #ecause the is-im#lemented-in-terms-o" relationship can #e modeled more clearly throu&h composition! 'f we wanted to implement a sta*+ in terms of a deVue- in1 stead of usin& private inheritance- we could *ust have the sta*+ contain a deVue as a data mem#er- as shown here=
1 B?? 1
'n practice it is recommended that you shy away from private inheritance in favor of this more e)plicit form of composition! /owever- there are several cases where private inheritance is precisely the tool for the *o#! et's return to our discussion of the @n*o$%able class! Fecall that to make a class uncopya#le- we had it pu#lically inherit from a class @n*o$%able that has its copy functions marked private! This led to pro#lems where we could convert an o#*ect that inherited from @n*o$%able into an @n*o$%able! /owever- we can remedy this #y havin& the derived class inherit #ri!ately from @n*o$%able! That way- it is not considered a su#type of @n, *o$%able and instances of :%4lass cannot #e converted into instances of @n*o$%able! 8or e)ample=
*lass :%4lass3 pri)ate Uncopyable { "2 ... 2" #;
"ow- :%4lass cannot #e copied- nor can it #e treated as thou&h it were an o#*ect of type @n*o$%able! This is precisely the idea we want to e)press! 'n C++- all inheritance is considered private inheritance unless e)plicitly mentioned otherwiseR this is why you must write $ubli* Oase to pu#licly inherit from Oase! Thus we can rewrite the a#ove class definition omit1 tin& the $rivate keyword- as shown here=
*lass :%4lass: Uncopyable { "2 ... 2" #;
This method of disallowin& copyin& is particularly ele&ant ( syntactically- we communicate that :%4lass can1 not #e copied at the same time that we actually make it uncopya#le throu&h private inheritance! 7efore concludin& this section- let's make a 5uick chan&e to the @n*o$%able class #y markin& its constructor and destructor $rote*ted! This means that classes that inherit from @n*o$%able can still access the con1 structor and destructor- #ut otherwise these functions are off1limits! This prevents us from accidentally instanti1 atin& @n*o$%able directly and only lets us use it as a #ase class! The code for this is shown here=
1 B?6 1
*lass @n*o$%able { protected: Uncopyable!$ *4 ^Uncopyable!$ *4 $rivate3 @n*o$%able(*onst @n*o$%able?); @n*o$%able? o$erator= (*onst @n*o$%able?); #;
Classes like @n*o$%able are sometimes referred to as mi7in classes since they are desi&ned to #e 4mi)ed6 into other classes to provide a particular functionality! Slicing 'n our discussion of copy constructor and assi&nment operators for derived classes- we encountered pro#lems when we copied over the the data of the 9erived class #ut not the Oase class! /owever- there's a far more seri1 ous pro#lem we can run into called slicing where we copy only the #ase class of an o#*ect while leavin& its de1 rived classes unmodified! Suppose we have two Oase 2 pointers called one and t'o that point to o#*ects either of type Oase or of type 9erived! 0hat happens if we write code like 2one = 2t'o> /ere- we're copyin& the value of the o#*ect pointed at #y t'o into the varia#le pointed at #y one! 0hile at first &lance this mi&ht seem harmless- the a#ove statement is one of the most potentially dan&erous mistakes you can make when workin& with C++ inheritance! The pro#lem is that this line e)pands into a call to
one,)operator ,(2t'o);
"ote that the version of o$erator = we're callin& here is the one defined in Oase- not 9erived- so this line will only copy over the Oase portion of t'o into the Oase portion of one- resultin& in half1formed o#*ects that are almost certainly not in the correct state and may #e entirely corrupted! Slicin& can #e even more insidious in scenarios like this one=
void 9o5omething(Oase base=b>e*t) { "" 9o something # 9erived m%9erived 9o5omething(m%9erived);
Fecall that the parameter base=b>e*t will #e initialized usin& the Oase copy constructor- not the 9erived copy constructor- so the o#*ect in 9o5omething will not #e a correct copy m%9erived! 'nstead- it will only hold a copy of the Oase part of the m%9erived o#*ect! .ou should almost never assi&n a #ase class o#*ect the value of a derived class! The second you do- you will open the door to runtime errors as your code tries to use incompletely1formed o#*ects! 0hile it may sound simple to follow this rule- at many times it mi&ht not #e clear that you're slicin& an o#*ect! 8or e)ample- con1 sider this code snippet=
ve*tor Oase) m%OaseDe*tor; Oase2 m%Oase<tr = 5ome&un*tion(); m%OaseDe*tor.$ushBba*+(Rmy(asePtr);
1 B?@ 1
/ere- the o#*ect pointed at #y m%Oase<tr could #e of type Oase or any type inheritin& from Oase! 0hen we call m%OaseDe*tor.$ushBba*+(2m%Oase<tr)- there is a &ood chance that we will slice the o#*ect pointed at #y m%Oase<tr- storin& only its Oase component in the ve*tor and droppin& the rest! .ou'll need to #e e)tra vi&ilant when workin& with derived classes- since it's easy to &enerate dan&erous- difficult1to1track #u&s! The C++ Casting 2&erators Hne of the most useful features of C++ inheritance is the a#ility to use an o#*ect of one type in place of another! 8or e)ample- a pointer of type 9erived 2 can #e used whenever a Oase 2 would #e e)pected- and the conver1 sion is automatic! /owever- in many circumstances- we may want to perform this conversion in reverse! Sup1 pose that we have a pointer that's statically typed as a Oase 2- #ut we know that the o#*ect it points to is actually of typed 9erived 2! /ow can we use the 9erived features of the pointee> 7ecause the pointer to the o#*ect is a Oase 2- not a 9erived 2- we will have to use a typecast to convert the pointer from the #ase type to the derived type! Usin& the typecasts most familiar to us in C++- the code to perform this conversion looks as fol1 lows=
Oase2 m%Oase<tr; "" Assume 'e +no' that this $oints to a 9erived ob>e*t. 9erived2 m%9erived<tr = !Deri)ed R$m%Oase<tr;
There is nothin& wron& with the a#ove code as written- #ut it is risky #ecause of the typecast (9erived 2)m%, Oase<tr! 'n C++- usin& a typecast of the form (;%$e) is e)tremely dan&erous #ecause there are only minimal compile1time checks to ensure that the typecast makes any sense! 8or e)ample- consider the followin& C++ code=
Oase2 m%Oase<tr; "" Assume 'e +no' that this $oints to a 9erived ob>e*t. ve*tor double)2 m%De*tor<tr = !)ector.double> R$m%Oase<tr; "" @h oh0
This code is completely nonsensical- since there is no reasona#le way that a pointer of type Oase 2 can end up pointin& to an o#*ect of type ve*tor double)! /owever- #ecause of the e)plicit pointer1to1pointer typecastthis code is entirely le&al! 'n the a#ove case- it's clear that the conversion we're performin& is incorrect- #ut in others it mi&ht #e more su#tle! Consider the followin& code=
const (aseR m%Oase<tr; "" Assume 'e +no' that this $oints to a 9erived ob>e*t. Deri)edR m%9erived<tr = !Deri)ed R$m%Oase<tr;
This code a&ain is totally le&al and at first &lance mi&ht seem correct- #ut unfortunately it contains a serious er1 ror! 'n this e)ample- our initial pointer was a pointer to a *onst Oase o#*ect- #ut in the second line we re1 moved that *onstness with a typecast and the resultin& pointer is free to modify the o#*ect it points at! 0e've *ust su#verted *onst- which could lead to a whole host of pro#lems down the line! The pro#lem with the a#ove style of C++ typecast is that it's *ust too powerful! 'f C++ can fi&ure out a way to convert the source o#*ect to an o#*ect of the tar&et type- it will- even if it's clear from the code that the conversion is an error! To resolve this issue- C++ has four special operators called casting o#erators that you can use to per1 form safer typecasts! 0hen workin& with inheritance- two of these castin& operators are particularly useful- the first of which is stati*B*ast! The stati*B*ast operator performs a typecast in the same way that the more familiar C++ typecast does- e)cept that it checks at compile time that the cast 4makes sense!6 Gore specificallystati*B*ast can #e used to perform the followin& conversions=Q
Q There are several other conversions that you can perform with pointers- #ut we will not discuss them here!
stati*B*ast-
void 2
1 B?E 1
1! Convertin& #etween primitive types Ke!&! int to float or *har to doubleL! ;! Convertin& #etween pointers or references of a derived type to pointers or references of a #ase type Ke!&! 9erived 2 to Oase 2L where the tar&et is at least as *onst as the source! A! Convertin& #etween pointers or references of a #ase type to pointers or references of a derived type Ke!&! Oase 2 to 9erived 2L where the tar&et is at least as *onst as the source! 'f you'll notice- neither of the errors we made in the previous code snippets are possi#le with a stati*B*ast! 0e can't convert a Oase 2 to a ve*tor double) 2- since Oase and ve*tor double) are not related to each other via inheritance! Similarly- we cannot convert from a *onst Oase 2 to a 9erived 2- since 9erived 2 is less *onst than *onst Oase 2! The synta) for the stati*B*ast operator looks resem#les that of templates and is illustrated #elow=
Oase2 m%Oase<tr; "" Assume 'e +no' this $oints to a 9erived ob>e*t. 9erived2 m%9erived<tr = static&cast.0yDeri)ed R>!my(asePtr$;
That is- stati*B*ast- followed #y the type to convert the pointer to- and finally the e)pression to convert en1 closed in parentheses! Throu&hout this discussion of typecasts- when convertin& #etween pointers of type Oase 2 and 9erived 2- we have implicitly assumed that the Oase 2 pointer we wanted to convert was pointin& to an o#*ect of type 9e, rived! 'f this isn't the case- however- the typecast can succeed #ut lead to a 9erived 2 pointer pointin& to a Oase o#*ect- which can cause all sorts of pro#lems at runtime when we try to invoke none)istent mem#er func1 tions or access data mem#ers of the 9erived class that aren't present in Oase! The pro#lem is that the stat, i*B*ast operator doesn't check to see that the typecast it's performin& makes any sense at runtime! To provide this functionality- you can use another of the C++ castin& operators- dynamic_cast- which acts like stati*B*ast #ut which performs additional checks #efore performin& the cast! d%nami*B*ast- like stat, i*B*ast- can #e used to convert #etween pointer types related #y inheritance K#ut not to convert #etween prim1 itive typesL! /owever- if the typecast re5uested of d%nami*B*ast is invalid at runtime Ke!&! attemptin& to con1 vert a Oase o#*ect to a 9erived o#*ectL- d%nami*B*ast will return a E@66 pointer instead of a pointer to the derived type! 8or e)ample- consider the followin& code=
Oase2 m%Oase<tr = ne' Oase; 9erived2 m%9erived<tr8 = (9erived 2)m%Oase<tr; 9erived2 m%9erived<trH = stati*B*ast 9erived 2)(m%Oase<tr); 9erived2 m%9erived<trF = dynamic&cast.Deri)ed R>(m%Oase<tr);
'n this e)ample- we use three different typecasts to convert a pointer that points to an o#*ect of type Oase to a pointer to a 9erived! 'n the a#ove e)ample- the first two casts will perform the type conversion- resultin& in pointers of type 9erived 2 that actually point to a Oase o#*ect- which can #e dan&erous! /owever- the final typecast- which uses d%nami*B*ast- will return a E@66 pointer #ecause the cast cannot succeed! 0hen performin& downcasts Kcasts from a #ase type to a derived typeL- unless you are a#solutely sure that the cast will succeed- you should consider usin& d%nami*B*ast over a stati*B*ast or raw C++ typecast! 7e1 cause of the e)tra check at runtime- d%nami*B*ast is slower than the other two casts- #ut the e)tra safety is well worth the cost! There are two more interestin& points to take note of when workin& with d%nami*B*ast! 8irst- you can only use d%nami*B*ast to convert #etween types if the #ase class type contains at least one virtual mem#er func1 tion! This may seem like a stran&e re5uirement- #ut &reatly improves the efficiency of the lan&ua&e as a whole and makes sense when you consider that it's rare to hold a pointer to a 9erived in a pointer of type Oase when Oase isn't polymorphic! The other important note is that if you use d%nami*B*ast to convert #etween re"er-
1 B?< 1
ences rather than pointers- d%nami*B*ast will throw an o#*ect of type badB*ast rather than returnin& a 4E@66 reference6 if the cast fails! Consult a reference for more information on badB*ast! Im&licit Interfaces and $%&licit Interfaces 'n the chapter on templates we discussed the concept of an im#licit inter"ace- #ehaviors re5uired of a template ar&ument! 8or e)ample- consider a function that returns the avera&e of the values in an ST container of doubles- as shown here=
tem$late t%$ename 4ontainer;%$e) double GetAverage(*onst 4ontainer;%$e? *) { return a**umulate(*.begin(), *.end(), 0.0) " *.siUe(); #
The function GetAverage may #e parameterized over an ar#itrary type- #ut will only compile if the type 4on, tainer;%$e e)ports functions begin and end that return iterators over doubles Kor o#*ects implicitly convert1 i#le to doublesL and a siUe function that returns some inte&er type! Contrast this with a similar function that uses inheritance=
*lass 4ontainer { $ubli*3 t%$edef something-dereferencable-to-a-double *onstBiterator; )irtual const&iterator begin!$ const , -; )irtual const&iterator end!$ const , -; #; double GetAverage(*onst ;ontainer? *) { return a**umulate(*.begin(), *.end(), 0.0) " *.siUe(); #
This function is no lon&er a template an instead accepts as an ar&ument an o#*ect that derives from 4ontainer! 'n many aspects these functions are similar! 7oth of the implementations have a set of constraints enforced on their parameter- which can #e of any type satisfyin& these constraints! 7ut these similarities o#scure a funda1 mental difference #etween how the two functions work ( at what point the function calls are resolved! 'n the in1 heritance1#ased version of GetAverage- the calls to begin- end- and siUe are virtual function calls which are resolved at runtime usin& the system descri#ed earlier in this chapter! 0ith the template1#ased version of Get, Average- the version of the begin- end- and siUe functions to call are resolved at com#ile-time! 0hen you call a template function- C++ instantiates the template #y replacin& all instances of the template ar&u1 ment with a concrete type! Thus if we call the template function GetAverage on a ve*tor int)- the compiler will instantiate GetAverage on ve*tor int) to yield the followin& code=
double GetAverage )ector.int> )(*onst )ector.int>? *) { return a**umulate(*.begin(), *.end(), 0.0) " *.siUe(); #
"ow that the template has #een instantiated- it's clear what functions *.begin() and the like correspond to ( they're the ve*tor int)'s versions of those functions! Since those functions aren't virtual- the compiler can hardcode which function is #ein& called and can optimize appropriately!
1 B60 1
The template version of this function is desira#le from a performance perspective #ut is not always the #est op1 tion! 'n particular- while we can pass as a parameter to GetAverage any o#*ect that conforms to the implicit in1 terface- we cannot treat those classes polymorphically! 8or e)ample- in the a#ove case it's perfectly le&al to call GetAverage on a ve*tor double) or a set double)- #ut we cannot write code like this=
4ontainer2 $tr = Candom4han*e(0.K) S ne' ve*tor double) 3 ne' set double);
Templates and inheritance are desi&ned to solve fundamentally different pro#lems! 'f you want to write code that operates over any type that meets some minimum re5uirements- the template system can help you do so effi1 ciently and concisely provided that you know what types are #ein& used at compile1time! 'f the types cannot #e determined at compile1time- you can use inheritance to descri#e what #ehaviors are e)pected of function ar&u1 ments! #ore to $%&lore 1! #ulti&le Inheritance= Unlike other o#*ect1oriented lan&ua&es like $ava- C++ lets classes inherit from multiple #ase classes! .ou can use this #uild classes that act like o#*ects of multiple types- or in con1 *unction with mi)in classes to #uild hi&hly1customiza#le classes! 'n most cases multiple inheritance is strai&htforward and simple- #ut there are many situations where it acts counterintuitively! 'f you plan on pursuin& C++ more seriously- #e sure to read up on multiple inheritance! ;! const&cast and reinterpret&cast= C++ has two other conversion operators- *onstB*ast- which can add or remove *onst from a pointer- and reinter$retB*ast- which performs fundamentally un1 safe typecasts Ksuch as convertin& an int 2 to a string 2L! 0hile the use mcases of these operators are far #eyond the scope of this class- they do arise in practice and you should #e aware of their e)ist1 ences! Consult a reference for more information! A! The Curiously 1ecurring Tem&late -attern KCFTCL= Tirtual functions make your pro&rams run sli&htly slower than pro&rams with non1virtual functions #ecause of the e)tra overhead of the dynamic lookup! 'n certain situations where you want the #enefits of inheritance without the cost of virtual func1 tions- you can use an advanced C++ trick called the curiously recurring tem#late #attern- or CFTC! The CFTC is also known as 4static interfacin&6 and lets you &et some of the #enefits of inheritance without any runtime cost! B! -olicy Classes= , nascent #ut popular C++ techni5ue called #olicy classes com#ines multiple inherit1 ance and templates to desi&n classes that are simultaneously customiza#le and efficient! , full treatment of policy classes is far #eyond the scope of this reader- #ut if you are interested in seein& e)actly how customiza#le C++ can #e ' stron&ly encoura&e you to read more a#out them! -ractice -roblems 1! 'n the Generi*Oat*h<rinter e)ample from earlier in this chapter- recall that the 9o*ument #ase class had several methods defined purely virtually- meanin& that they don't actually have any code for those mem#er functions! 'nside Generi*Oat*h<rinter- why don't we need to worry that the 9o*u, ment 2 pointer from the Vueue points to an o#*ect of type 9o*ument and thus mi&ht cause pro#lems if we tried invokin& those purely virtual functions> M ;! 'n the ne)t e)ercises- we'll e)plore a set of classes that let you #uild and modify functions at runtime us1 in& tools similar to those in the ST fun*tional) pro&rammin& li#rary! Consider the followin& a#stract class=
*lass &un*tion { $ubli*3 virtual ^&un*tion() = 0; virtual double evaluateAt(double value) = 0; #;
1 B61 1
A!
B!
?! 6!
@!
E! <!
This class e)ports a sin&le function- evaluateAt- that accepts a double as a parameter and returns the value of some function evaluated at that point! 0rite a derived class of &un*tion- 5im$le&un*tionwhose constructor accepts a re&ular C++ function that accepts and returns a double and whose evalu, ateAt function returns the value of the stored function evaluated at the parameter! The composition of two functions ! and : is defined as !K:K%LL ( that is- the function ! applied to the value of : applied to %! 0rite a class 4om$osition&un*tion whose constructor accepts two &un*, tion 2 pointers and whose evaluateAt returns the composition of the two functions evaluated at a point! The derivative of a function is the slope of the tan&ent line to that function at a point! The derivative of a function ! can #e appro)imated as !NK%L n K!K% + Q%L ( !K% C Q%LL I Q% for small values of Q%! 0rite a class 9erivative&un*tion whose constructor accepts a &un*tion 2 pointer and a double repres1 entin& Q% and whose evaluateAt appro)imates the derivative of the stored function usin& the initial value of Q%! M 8or the a#ove classes- why did we make a function named evaluateAt instead of providin& an imple1 mentation of o$erator()> <+int% &ill we be using actual >unction objects, or #ointers to them'= , common mistake is to try to avoid pro#lems with slicin& #y declarin& o$erator = as a virtual func1 tion in a #ase class! 0hy won't this solve the slicin& pro#lem> <+int% what is the #arameter ty#e to operator 9'= Suppose you have three classes- Oase- 9erived- and Der%9erived where 9erived inherits from Oase and Der%9erived inherits from 9erived! ,ssume all three have copy constructors and assi&n1 ment operators! 'nside the #ody of Der%9erived's assi&nment operator- why shouldn't it invoke Oase33o$erator = on the other o#*ect> 0hat does this tell you a#out lon& inheritance chains- copy1 in&- and assi&nment> The C++ castin& operators were deli#erately desi&ned to take up space to discoura&e pro&rammers from usin& typecasts! D)plain why the lan&ua&e desi&ners frown upon the use of typecasts! The unaryVfunction and #inaryVfunction classes from [functionalo do not define operatorKL as a virtual mem#er function! Considerin& that every adapta#le function must #e a su#class of one of these two classes- it seems lo&ical for the two classes to do so! 0hy don't they> K/int= the ST is desi&ned for ma)imum possi#le efficiency! 0hat would happen if operatorKL was virtual>L
There are a hu&e num#er of ways to define a function or function1like o#*ect in C++- each of which has sli&htly different synta) and #ehavior! 8or e)ample- suppose that we want to write a function that accepts as input a function that can accept an int and return a double! 0hile of course we could accept a double (2) (int) ( a pointer to a function acceptin& an int and returnin& a double ( this is overly restrictive! 8or e)ample- all of the followin& functions can #e used as thou&h they were functions takin& in an int and returnin& a double=
double &n8(*onst int?); int &nH(int); int &nF(*onst int?); "2 A**e$t int b% referen*e,to,*onst, return double. 2" "2 A**e$t $arameter as a int, return int. 2" "2 A**e$t referen*e,to,*onst int, return int. 2"
'n addition- if we *ust accept a double (2) (int)- we also can't accept functors as input- meanin& that neither of these o#*ects #elow ( #oth of which can accept an int and return a double ( could #e used=
"2 &un*tor a**e$ting an int and returning a double. 2" stru*t :%&un*tor { double o$erator() (int); #; "2 Ada$table fun*tion a**e$ting double and returning a double. 2" bindHnd(multi$lies int)(), 8FG);
'n the chapter on functors- we saw how we can write functions that accept any of the a#ove functions usin& tem1 plates- as shown here=
template .typename UnaryFunction> void 9o5omething(UnaryFunction fn) { "2 ... 2" #
'f we want to write a "unction that accepts a function as input we can rely on templates- #ut what if we want to write a class that needs to store a function of any ar#itrary type> To &ive a concrete e)ample- suppose that we're desi&nin& a class representin& a &raphical window and we want the client to #e a#le to control the window's size and position! The window o#*ect- which we'll assume is of type 1indo'- thus allows the user to provide a func1 tion that will #e invoked whenever the window is a#out to chan&e size! The user's function then takes in an int representin& the potential new width of the window and returns an int representin& what the user wants the new window size to #e! 8or e)ample- if we want to create a window that can't #e more than 100 pi)els wide- we could pass in this function=
int 4lam$;o800<i7els(int siUe) { return min(siUe, 800); #
,lternatively- if we want the window size to always #e 100 pi)els- we could pass in this function=
int Al'a%s800<i7els(int unused) { return 800; "" (gnore $arameter #
1 B6B 1
3iven that we need to store a function of an ar#itrary type inside the 1indo' class- how mi&ht we desi&n 1in, do'> Usin& the approach outlined a#ove- we could templatize the 1indo' class over the type of the function it stores- as shown here=
template .typename =idthFunction> *lass 1indo' { $ubli*3 1indo'(=idthFunction n, "2 ... 2"); "2 ... 2" $rivate3 =idthFunction 6idth; #; "2 ... 2"
3iven this implementation of 1indo'- we could then specify that a window should #e no more than 100 pi)els wide #y writin&
=indo6.int !R$!int$> m%1indo'(4lam$;o800<i7els);
This 1indo' class lets us use any reasona#le function to determine the window size- #ut has several serious draw#acks! 8irst- it re5uires the 1indo' client to e)plicitly parameterize 1indo' over the type of call#ack #e1 in& stored! 0hen workin& with function pointers this results in lon& and convoluted varia#le declarations Klook a#ove for an e)ampleL- and when workin& with li#rary functors such as those in the ST fun*tional) li#rary Ke!&! bindHnd ($trBfun(:%&un*tion), 8FG)LQ- we could end up with a 1indo' of such a complicated type that it would #e infeasi#le to use with without the aid of t%$edef! 7ut a more serious pro#lem is that this approach causes two 1indo's that don't use the same type of function to compute width to have completely dif1 ferent types! That is- a 1indo' usin& a raw C++ function to compute its size would have a different type from a 1indo' that computed its size with a functor! Conse5uently- we couldn't make a ve*tor 1indo')- #ut instead would have to make a ve*tor 1indo' int (2)(int)) ) or a ve*tor 1indo' :%&un*tor;%$e) )! Similarly- if we want to write a function that accepts a 1indo'- we can't *ust write the followin&=
void 9o5omething(*onst 1indo'? ') "" Error Q 1indo' is a tem$late, not a t%$e { "2 ... 2" #
't should #e clear that templatizin& the 1indo' class over the type of the call#ack function does not work well! /ow can we resolve this pro#lem> 'n this e)tended e)ample- we'll e)plore how to solve this pro#lem usin& in1 heritance- then will a#stract away from the concrete pro#lem at hand to create a versatile class encapsulatin& a function of any possi#le type!
Q ,s an 8.'- the type of bindHnd($trBfun(:%&un*tion),
8FG)
is
where Arg8- ArgH- and Cet are the ar&ument and return types of the :%&un*tion function!
1 B6? 1
et's take a few minutes to think a#out the pro#lem we're facin&! 0e have a collection of different o#*ects that each have similar functionality Kthey can #e called as functionsL- #ut we don't know e)actly which o#*ect the user will provide! This sounds e)actly like the sort of pro#lem we can solve usin& inheritance and virtual functions! 7ut we have a pro#lem ( inheritance only applies to objects- #ut some of the values we mi&ht want to store are simple function pointers- which are primitives! 8ortunately- we can apply a techni5ue called the Fundamental Dule o" ,o"tware Engineering to solve this pro#lem= Theorem (The Fundamental Theorem of oft!are "ngineering)% ,ny pro#lem can #e solved #y addin& enou&h layers of indirection! This is a very useful pro&rammin& concept that will prove relevant time and time a&ain ( make sure you remem1 #er itU 'n this particular application- the 8TSD says that we need to distance ourselves #y one level from raw function pointers and functor classes! This leads to the followin& o#servation= while we mi&ht not #e a#le to treat func1 tors and function pointers polymorphically- we certainly can create a new class hierarchy and then treat that class polymorphically! The idea &oes somethin& like this ( rather than havin& the user provide us a functor or function pointer which could #e of any type- instead we'll define an a#stract class e)portin& the call#ack function- then will have the user provide a su#class which implements the call#ack! Hne possi#le #ase class in this hierarchy is shown #elow=
*lass (nt&un*tion { $ubli*3 "2 <ol%mor$hi* *lasses should have virtual destru*tors. 2" virtual ^(nt&un*tion() { # "2 e7e*ute() a*tuall% *alls the $ro$er fun*tion and returns the value. 2" )irtual int e#ecute!int )alue$ const , -; #; (nt&un*tion e)ports a sin&le function called e7e*ute which accepts an int and returns an int! This func1
tion is marked purely virtual since it's unclear e)actly what this function should do! ,fter all- we're tryin& to store an ar#itrary function- so there's no clearly1defined default #ehavior for e7e*ute! 0e can now modify the implementation of 1indo' to hold a pointer to an (nt&un*tion instead of #ein& tem1 platized over the type of the function- as shown here=
*lass 1indo' { $ubli*3 =indo6!IntFunctionR siLeFunctionB TR ... RT$ "2 ... 2" $rivate3 IntFunctionR #;
n;
"ow- if we wanted to clamp the window to 100 pi)els- we can do the followin&=
1 B66 1
*lass 4lam$;o800<i7els&un*tion3 public IntFunction { $ubli*3 )irtual int e#ecute!int siLe$ const { return min(siUe, 800); # #; 1indo' m%1indo'(ne6 ;lampTo7--Pi#elsFunction, "2 ... 2");
Similarly- if we want to have a window that's always 100 pi)els wide- we could write
*lass &i7ed5iUe&un*tion3 public IntFunction { $ubli*3 )irtual int e#ecute!int siLe$ const { return 800; # #; 1indo' m%1indo'(ne' &i7ed5iUe&un*tion, "2 ... 2");
't seems as thou&h we've solved the pro#lem ( we now have a 1indo' class that allows us to fully customize its resizin& #ehavior ( what more could we possi#ly want> The main pro#lem with our solution is the sheer amount of #oilerplate code clients of 1indo' have to write if they want to chan&e the window's resizin& #ehavior! Hur initial &oal was to let class clients pass raw C++ func1 tions and functors to the 1indo' class- #ut now clients have to su#class (nt&un*tion to &et the *o# done! 7oth of the a#ove su#classes are len&thy even thou&h they only e)port a sin&le function! 's there a simpler way to do this> The answer- of course- is yes! Suppose we have a pure C++ function that accepts an int #y value and returns an int that we want to use for our resizin& function in the 1indo' classR perhaps it's the 4lam$;o800<i7els function we defined earlier- or perhaps it's Al'a%s800<i7els! Father than definin& a new su#class of (nt, &un*tion for every sin&le one of these functions- instead we'll #uild a sin&le class that's desi&ned to wrap up a function pointer in a packa&e that's compati#le with the (nt&un*tion interface! That is- we can define a su#1 class of (nt&un*tion whose constructor accepts a function pointer and whose e7e*ute calls this function! This is the 8undamental Theorem of Software Dn&ineerin& in action ( we couldn't directly treat the raw C++ function polymorphically- #ut #y a#stractin& #y a level we can directly apply inheritance! /ere's one possi#le implementation of the su#class=
*lass A*tual&un*tion3 $ubli* (nt&un*tion { $ubli*3 e#plicit UctualFunction!int !R n$!int$$ : virtual int e7e*ute(int value) *onst { return unction!)alue$; # $rivate3 int !R unction$!int$; #;
unction! n$ *4
1 B6@ 1
There is a #it of e)tra code for creatin& the A*tual&un*tion o#*ect- #ut this is a one1time cost! 0e can now use A*tual&un*tion to wrap any raw C++ function acceptin& an int and returnin& an int and will save a lot of time typin& out new su#classes of (nt&un*tion for every call#ack! "ow- suppose that we have a functor class- which we'll call :%&un*tor- that we want to use inside the 1indo' class! Then we could define a su#class that looks like this=
*lass :%&un*tor&un*tion3 $ubli* (nt&un*tion { $ubli*3 e#plicit 0yFunctorFunction!0yFunctor n$ : virtual int e7e*ute(int value) *onst { return unction!)alue$; # $rivate3 0yFunctor unction; #;
unction! n$ *4
0here we assume for simplicity that the :%&un*tor class has a unary constructor! 0e're &ettin& much closer to an ideal solution! /an& in thereR the ne)t step is pretty e)citin&! Tem&lates to the 1escue ,t this point we a&ain could *ust call it 5uits ( we've solved the pro#lem we set out to solve and usin& the a#ove pattern our 1indo' class can use any C++ function or functor we want! /owever- we are close to an o#serva1 tion that will &reatly simplify the implementation of 1indo' and will yield a much more &eneral solution! et's reprint the two su#classes of (nt&un*tion we *ust defined a#ove which wrap function pointers and func1 tors=
1 B6E 1
*lass A*tual&un*tion3 $ubli* (nt&un*tion { $ubli*3 e7$li*it A*tual&un*tion(int (2fn)(int)) 3 fun*tion(fn) {# virtual int e7e*ute(int value) *onst { return fun*tion(value); # $rivate3 int (2*onst fun*tion)(int); #; *lass :%&un*tor&un*tion3 $ubli* (nt&un*tion { $ubli*3 e7$li*it :%&un*tor&un*tion(:%&un*tor fn) 3 fun*tion(fn) {# virtual int e7e*ute(int value) *onst { return fun*tion(value); # $rivate3 :%&un*tor fun*tion; #;
'f you'll notice- #esides the name of the classes- the only difference #etween these two classes is what type of o#1 *ect is #ein& stored! This similarity is no coincidence ( any calla#le function or functor would re5uire a su#class with e)actly this structure! Father than re5uirin& the client of 1indo' to reimplement this su#class from scratch each time- we can instead create a tem#late class that's a su#class of (nt&un*tion! 't's rare in practice to see templates and inheritance mi)ed this way- #ut here it works out #eautifully! /ere's one implementation=
template .typename UnaryFunction> *lass 5$e*ifi*&un*tion3 $ubli* (nt&un*tion { $ubli*3 e7$li*it 5$e*ifi*&un*tion(UnaryFunction fn) 3 fun*tion(fn) {# virtual int e7e*ute(int value) *onst { return fun*tion(value); # $rivate3 UnaryFunction fun*tion; #;
The synta) here mi&ht #e a #it tricky- #ut we've &reatly reduced the comple)ity associated with the 1indo' class since clients no lon&er have to implement their own su#classes of (nt&un*tion! 2ne #ore Abstraction This e)tended e)ample has consisted primarily of addin& more and more a#stractions on top of the system we're desi&nin&- and it's time for one final leap! et's think a#out what we've constructed so far! 0e've #uilt a class
1 B6< 1
hierarchy with a sin&le #ase class and a template for creatin& as many su#classes as we need! /owevereverythin& we've written has #een hardcoded with the assumption that the 1indo' class is the only class that mi&ht want this sort of functionality! /avin& the a#ility to store and call a function of any conceiva#le type is enormously useful- and if we can somehow encapsulate all of the necessary machinery to &et this workin& into a sin&le class- we will #e a#le to reuse what we've *ust #uilt time and time a&ain! 'n this ne)t section- that's e)actly what we'll #e&in doin&! 0e'll #e&in #y movin& the code from 1indo' that mana&es the stored function into a dedicated class called &un*tion! The #asic interface for &un*tion is shown #elow=
class Function { $ubli*3 "2 4onstru*tor and destru*tor. Function!IntFunctionR n$; ^&un*tion();
"2 &un*tion is a fun*tor that *alls into the stored resour*e. 2" int operator!$ !int )alue$ const; $rivate3 IntFunctionR #; unction;
0e'll leave the &un*tion constructor left as an implicit conversion constructor- since that way we can implicitly convert #etween a calla#le (nt&un*tion pointer and a &un*tion functor! 0e can then implement the a#ove functions as follows=
"2 4onstru*tor a**e$ts an (nt&un*tion and stores it. 2" &un*tion33&un*tion((nt&un*tion2 fn) 3 fun*tion(fn) { # "2 9estru*tor deallo*ates the stored fun*tion. 2" &un*tion33^&un*tion() { delete fun*tion; # "2 &un*tion *all >ust *alls through to the $ointer and returns the result. 2" int &un*tion33o$erator() (int value) *onst { return unction->e#ecute!)alue$; #
"othin& here should #e too out1of1the1ordinary ( after all- this is pretty much the same code that we had inside the 1indo' class! 3iven this version of &un*tion- we can write code that looks like this=
Function myFunction , ne6 Speci icFunction.int !R$!int$>!;lampTo7--Pi#els$; *out myFunction!7DE$ endl; "" <rints 800 *out myFunction!J8$ endl; "" <rints JH
'f you're a #it worried that the synta) ne' 5$e*ifi*&un*tion int (2)(int))(4lam$;o800<i7els) is unnecessarily #ulky- that's a#solutely correct! 2on't worry- in a #it we'll see how to eliminate it! 'n the mean1 time- however- let's implement the copy #ehavior for the &un*tion class! ,fter all- there's no reason that we
1 B@0 1
shouldn't #e a#le to copy &un*tion o#*ects- and definin& copy #ehavior like this will lead to some very im1 pressive results in a #it! 0e'll #e&in #y definin& the proper functions inside the &un*tion class- as seen here=
*lass &un*tion { $ubli*3 "2 4onstru*tor and destru*tor. 2" &un*tion((nt&un*tion2 fn); ^&un*tion(); "2 4o$% su$$ort. 2" Function!const FunctionS other$; FunctionS operator, !const FunctionS other$; "2 &un*tion is a fun*tor that *alls into the stored resour*e. 2" int o$erator() (int value) *onst; $rivate3 (nt&un*tion2 fun*tion; void *lear(); void *o$%=ther(*onst &un*tion? other); #;
"ow- since the &un*tion class contains only a sin&le data mem#er Kthe (nt&un*tion pointerL- to make a deep1copy of a &un*tion we simply need to make a deep copy of its re5uisite (nt&un*tion! 7ut here we run into a pro#lem! (nt&un*tion is an a#stract class and we can't tell at compile1time what type of o#*ect is actu1 ally #ein& pointed at #y the fun*tion pointer! /ow- then- can we make a deep1copy of the (nt&un*tion> The answer is surprisin&ly strai&htforward ( we'll *ust introduce a new virtual function to the (nt&un*tion class that returns a deep copy of the receiver o#*ect! Since this function duplicates an e)istin& o#*ect- we'll call it *lone! The interface for (nt&un*tion now looks like this=
*lass (nt&un*tion { $ubli*3 "2 <ol%mor$hi* *lasses should have virtual destru*tors. 2" virtual ^(nt&un*tion() { # "2 e7e*ute() a*tuall% *alls the $ro$er fun*tion and returns the value. 2" virtual int e7e*ute(int value) *onst = 0; "2 *lone() returns a dee$,*o$% of the re*eiver ob>e*t. 2" )irtual IntFunctionR clone!$ const , -;
#;
0e can then update the template class 5$e*ifi*&un*tion to implement *lone as follows=
1 B@1 1
/ere- the implementation of *lone returns a new 5$e*ifi*&un*tion initialized via the copy constructor as a copy of the receiver o#*ect! "ote that we haven't e)plicitly defined a copy constructor for 5$e*ifi*&un*tion and are relyin& here on C++'s automatically1&enerated copy function to do the trick for us! This assumes- of course- that the @nar%&un*tion type correctly supports deep1copyin&- #ut this isn't a pro#lem since raw func1 tion pointers can trivially #e deep1copied as can all primitive types and it's rare to find functor classes with no copy support! 0e can then implement the copy constructor- assi&nment operator- destructor- and helper functions for &un*, tion as follows=
&un*tion33^&un*tion() { *lear(); # &un*tion33&un*tion(*onst &un*tion? other) { *o$%=ther(other); # &un*tion? &un*tion33o$erator= (*onst &un*tion? other) { if(this 0= ?other) { *lear(); *o$%=ther(other); # return 2this; # void &un*tion33*lear() { delete unction; # void &un*tion33*o$%=ther(*onst &un*tion? other) { "2 /ave the stored fun*tion tell us ho' to *o$% itself. 2" unction , other. unction->clone!$; #
1 B@; 1 Hur &un*tion class is now startin& to take shapeU 0iding Speci icFunction
Fi&ht now our &un*tion class has full deep1copy support and usin& 5$e*ifi*&un*tion ;) can store any type of calla#le function! /owever- clients of &un*tion have to e)plicitly wrap any function they want to store inside &un*tion in the 5$e*ifi*&un*tion class! This has several pro#lems! 8irst and foremost- this #reaks encapsulation! 5$e*ifi*&un*tion is only used internally to the &un*tion class- never e)ternally- so re5uir1 in& clients of &un*tion to have e)plicit knowled&e of its e)istence violates encapsulation! Second- it re5uires the user to know the type of every function they want to store inside the &un*tion class! 'n the case of 4lam$, ;o800<i7els this is rather simple- #ut suppose we want to store bindHnd(multi$lies int)(), 8FG) in1 side of &un*tion! 0hat is the type of the o#*ect returned #y bindHnd(multi$lies int)(), 8FG)> 8or reference- it's binderHnd multi$lies int) )- so if we wanted to store this in a &un*tion we'd have to write
&un*tion m%&un*tion = ne6 Speci icFunction.binder8nd.multiplies.int> > >!bind8nd!multiplies.int>!$B7DE$$;
This is a syntactic ni&htmare and makes the &un*tion class terri#ly unattractive! 8ortunately- however- this pro#lem has a 5uick fi) ( we can templatize the &un*tion constructor over the type of ar&ument passed into it- then construct the relevant 5$e*ifi*&un*tion for the &un*tion client! Since C++ automatically infers the parameter types of template functions- this means that clients of &un*tion never need to know the type of what they're storin& ( the compiler will do the work for them! D)cellentU 'f we do end up makin& the &un*tion constructor a template- we should also move the (nt&un*tion and 5$e*ifi*&un*tion classes so that they're inner classes of &un*tion! ,fter all- they're specific to the imple1 mentation of &un*tion and the outside world has no #usiness usin& them! The updated interface for the &un*tion class is shown here=
*lass &un*tion { $ubli*3 "2 4onstru*tor and destru*tor. 2" template .typename UnaryFunction> Function!UnaryFunction$; ^&un*tion(); "2 4o$% su$$ort. 2" &un*tion(*onst &un*tion? other); &un*tion? o$erator= (*onst &un*tion? other); "2 &un*tion is a fun*tor that *alls into the stored resour*e. 2" int o$erator() (int value) *onst; $rivate3 class IntFunction * TR ... RT 4; template .typename UnaryFunction> class Speci icFunction * TR ... RT 4; (nt&un*tion2 fun*tion; void *lear(); void *o$%=ther(*onst &un*tion? other);
#;
Cha#ter ;$% E7tended E7am#le% Function 0e can then implement the constructor as follows=
tem$late t%$ename @nar%&un*tion) &un*tion33&un*tion(@nar%&un*tion fn) { unction , ne6 Speci icFunction.UnaryFunction>! n$; #
1 B@A 1
Since we've left the &un*tion constructor not marked e7$li*it- this template constructor is a conversion con1 structor! Coupled with the assi&nment operator- this means that we can use &un*tion as follows=
&un*tion fn = 4lam$;o800<i7els; *out fn(8FG) endl; "" <rints 800 *out fn(JH) endl; "" <rints JH fn = bindHnd(multi$lies int)(), H); *out fn(8FG) endl; "" <rints HGJ *out fn(JH) endl; "" <rints IJ
This is e)actly what we're lookin& for ( a class that can store any calla#le function that takes in an int and re1 turns an int! 'f this doesn't strike you as a particularly ele&ant piece of code- take some time to look over it a&ain! There's one final step we should take- and that's to rela) the restriction that &un*tion always acts as a function from ints to ints! There's nothin& special a#out int- and #y &ivin& &un*tion clients the a#ility to specify their own parameter and return types we'll increase the scope of what &un*tion is capa#le of handlin&! 0e'll thus templatize &un*tion as &un*tion Arg;%$e, Ceturn;%$e) and have it inherit from unar%Bfun*, tion Arg;%$e, Ceturn;%$e) so that ST 1minded clients can treat it as an adapta#le function! 0e also need to make some minor edits to (nt&un*tion Kwhich we'll rename to Arbitrar%&un*tion since (nt&un*tion is no lon&er applica#leL- #ut in the interest of #revity we won't reprint them here! The final interface for &un*tion thus looks like this Ka full listin& is included at the end of this chapterL=
template .typename UrgTypeB typename CeturnType> *lass &un*tion3 public unary& unction.UrgTypeB CeturnType> { $ubli*3 "2 4onstru*tor and destru*tor. 2" tem$late t%$ename @nar%&un*tion) &un*tion(@nar%&un*tion); ^&un*tion(); "2 4o$% su$$ort. 2" &un*tion(*onst &un*tion? other); &un*tion? o$erator= (*onst &un*tion? other); "2 &un*tion is a fun*tor that *alls into the stored resour*e. 2" CeturnType o$erator() (*onst UrgTypeS value) *onst; $rivate3 *lass UrbitraryFunction { "2 ... 2" #; tem$late t%$ename @nar%&un*tion) *lass 5$e*ifi*&un*tion { "2 ... 2" # UrbitraryFunctionR fun*tion; void *lear(); void *o$%=ther(*onst &un*tion? other); #;
1 B@B 1
To conclude our discussion of 1indo'- usin& the new &un*tion type we could rewrite the 1indo' class usin& &un*tion as follows=
*lass 1indo' { $ubli*3 1indo'(const Function.intB int>S 6idthFn, "2 ... 2" "2 ... other member fun*tions ... 2" $rivate3 Function.intB int> 6idthFunction; #;
"ow- clients can pass any unary function Kor functorL that maps from ints to ints as a parameter to 1indo' and the code will compile correctly! $%ternal -olymor&hism The &un*tion type we've *ust developed is su#tle in its cleverness! 7ecause we can convert any calla#le unary function into a &un*tion- when writin& code that needs to work with some sort of unary function- we can have that code use &un*tion instead of any specific function type! This techni5ue of a#stractin& away from the par1 ticular types that provide a #ehavior into an o#*ect representin& that #ehavior is sometimes known as e7ternal #olymor#hism! ,s opposed to internal #olymor#hism- where we e)plicitly define a set of classes containin& vir1 tual functions- e)ternal polymorphism 4&rafts6 a set of virtual functions onto any type that supports the re5uisite #ehavior! ,s mentioned in the previous chapter- virtual functions can #e sli&htly more e)pensive than re&ular functions #e1 cause of the virtual function ta#le lookup re5uired! D)ternal polymorphism is implemented usin& inheritance and thus also incurs an overhead- #ut the overhead is sli&htly &reater than re&ular inheritance! Think for a minute how the &un*tion class we *ust implemented is desi&ned! Callin& &un*tion33o$erator() re5uires the followin&= 1! 8ollowin& the Arbitrar%&un*tion pointer in the &un*tion class to its virtual function ta#le! ;! Callin& the function indicated #y the virtual function ta#le- which corresponds to the particular 5$e, *ifi*&un*tion #ein& pointed at! A! Callin& the actual function o#*ect stored inside the 5$e*ifi*&un*tion! This is sli&htly more comple) than a re&ular virtual function call- and illustrates the cost associated with e)ternal polymorphism! That said- in some cases Ksuch as the &un*tion case outlined hereL the cost is overwhelmin& offset #y the fle)i#ility afforded #y e)ternal polymorphism! #ore to $%&lore The &un*tion class we've *ust finished writin& was inspired #y the 7oost li#rary's fun*tion template- which acts like &un*tion #ut works on functions of ar#itrary arity and with a sli&htly clearer template synta)! 8or e)1 ample- to create a 7oost fun*tion o#*ect that holds a function takin& three ar&uments and returnin& a boolyou could write
boost33fun*tion bool (int, string, double)) m%&un*tion;
The 7oost li#rary's fun*tion class is so overwhelmin&ly popular amon& C++ pro&rammers that it has #een ad1 opted as part of the ne)t &eneration of C++- nicknamed C++0) Ksee the upcomin& chapter for more informationL!
1 B@? 1
The implementation of boost33fun*tion is downri&ht awful and uses a more than its fair share of advanced preprocessor and template tricks to work correctly- #ut if you're interested in seein& how the techni5ues covered here can #e applied to a real1world system you mi&ht want to check it out! 'f you're interested in learnin& more a#out e)ternal polymorphism- consider lookin& into ,do#e Software Tech1 nolo&y a#'s an%Biterator class- which encapsulates an ar#itrary iterator! The documentation can #e found on the ,do#e ST 's we#site at http=IIstla#!ado#e!comI Com&lete Function Listing /ere is the complete implementation of &un*tion! Comments have #een added to the relevant sections=
tem$late t%$ename Arg;%$e, t%$ename Ceturn;%$e) *lass &un*tion3 $ubli* unar%Bfun*tion Arg;%$e, Ceturn;%$e) { $ubli*3 "2 4onstru*tor and destru*tor. 2" tem$late t%$ename @nar%&un*tion) &un*tion(@nar%&un*tion); ^&un*tion(); "2 4o$% su$$ort. 2" &un*tion(*onst &un*tion? other); &un*tion? o$erator= (*onst &un*tion? other); "2 &un*tion is a fun*tor that *alls into the stored resour*e. 2" Ceturn;%$e o$erator() (*onst Arg;%$e? value) *onst; $rivate3 "2 Oase *lass 'hi*h re$resents some fun*tion that *an be *alled 'ith an Arg;%$e 2 that returns a Ceturn;%$e. Arbitrar%&un*tions *an also be dee$,*o$ied using 2 the *lone() fun*tion. 2 2 Arbitrar%&un*tion is an abstra*t *lass sin*e there is no good default 2 im$lementation for an% of its member fun*tions. 2" *lass Arbitrar%&un*tion { $ubli*3 "2 Dirtual destru*tor ne*essar% for *leanu$. 2" virtual ^Arbitrar%&un*tion() {# "2 e7e*ute *alls the stored fun*tion and returns its return value. 2" virtual Ceturn;%$e e7e*ute(*onst Arg;%$e? $aram) *onst = 0; "2 *lone returns a dee$,*o$% of the re*eiver ob>e*t. 2" virtual Arbitrar%&un*tion2 *lone() *onst = 0;
#;
1 B@6 1
"2 &or an% t%$e of unar% fun*tion, 'e define a sub*lass of Arbitrar%&un*tion 2 'hi*h 'ra$s that ob>e*t so it *an be *alled through the Arbitrar%&un*tion 2 interfa*e. 2" tem$late t%$ename @nar%&un*tion) *lass 5$e*ifi*&un*tion3 $ubli* Arbitrar%&un*tion { $ubli*3 "2 4onstru*tor a**e$ts and stores a @nar%&un*tion. 2" e7$li*it 5$e*ifi*&un*tion(@nar%&un*tion fn) 3 fun*tion(fn) {# "2 e7e*ute >ust *alls do'n to the fun*tion. 2" virtual Ceturn;%$e e7e*ute(*onst Arg;%$e? $aram) *onst { return fun*tion($aram); # "2 4lone returns a dee$,*o$% of this ob>e*t. 2" virtual Arbitrar%&un*tion2 *lone() *onst { return ne' Arbitrar%&un*tion(2this); # $rivate3 @nar%&un*tion fun*tion; #; Arbitrar%&un*tion2 fun*tion; void *lear(); void *o$%=ther(*onst &un*tion? other); #; "2 4onstru*tor a**e$ts a @nar%&un*tion of the $ro$er t%$e, then 'ra$s it inside a 2 5$e*ifi*&un*tion 'ra$$er. Eote that there are t'o tem$late headers here sin*e 2 the *lass and *onstru*tor are both tem$lates. 2" tem$late t%$ename Arg;%$e, t%$ename Ceturn;%$e) tem$late t%$ename @nar%&un*tion) &un*tion Arg;%$e, Ceturn;%$e)33&un*tion(@nar%&un*tion fn) { fun*tion = ne' 5$e*ifi*&un*tion @nar%&un*tion)(fn); # "2 9estru*tor *alls *lear. 2" tem$late t%$ename Arg;%$e, t%$ename Ceturn;%$e) &un*tion Arg;%$e, Ceturn;%$e)33^&un*tion() { *lear(); # "2 4o$% *tor *alls *o$%=ther. 2" tem$late t%$ename Arg;%$e, t%$ename Ceturn;%$e) &un*tion Arg;%$e, Ceturn;%$e)33&un*tion(*onst &un*tion? other) { *o$%=ther(other); #
1 B@@ 1
"2 *o$%=ther uses the *lone() member fun*tion to do the *o$%. Eote that the *o$% 2 is ne*essar% instead of using a shallo' *o$% be*ause the fun*tion might be a 2 fun*tor 'ith internal state. 2" tem$late t%$ename Arg;%$e, t%$ename Ceturn;%$e) void &un*tion Arg;%$e, Ceturn;%$e)33*o$%=ther(*onst &un*tion? other) { fun*tion = other.fun*tion,)*lone(); # "2 &inall%, o$erator() >ust *alls do'n into the fun*tion and returns the result. 2" tem$late t%$ename Arg;%$e, t%$ename Ceturn;%$e) Ceturn;%$e &un*tion Arg;%$e, Ceturn;%$e)33o$erator()(*onst Arg;%$e? $aram) *onst { return fun*tion,)e7e*ute($aram); #
-art Three
#ore to $%&lore
0hewU .ou've made it throu&h the first three sections and are now a seasoned and competent C++ pro&rammer! 7ut your *ourney has *ust #e&un! There are many parts of the C++ pro&rammin& lan&ua&e that we have not covered- and it's now up to you to #e&in the rest of your *ourney! This last section of the #ook contains two chapters! The first- on C++0)- discusses what chan&es are e)pected for the C++ pro&rammin& lan&ua&e over the ne)t few years! "ow that you've seen C++'s stren&ths and weak1 nesses- ' hope that this chapter proves enli&htenin& and e)citin&! The second chapter is all a#out how to contin1 ue your *ourney into further C++ mastery and hopefully can &ive you a #oost in the ri&ht direction!
C++$7 "eels li*e a new language% The #ieces just "it together better than they used to and I "ind a higher-le!el style o" #rogramming more natural than be"ore and as e""icient as e!er) I" you timidly a##roach C++ as just a better C or as an object-oriented language, you are going to miss the #oint) The abstractions are sim#ly more "le7ible and a""ordable than be"ore) Dely on the old mantra% I" you thin* OoP" it as a se#arate idea or object, re#resent it directly in the #rogramQ model real-world objects, conce#ts, and abstractions directly in code) It(s easier now% 4our ideas will ma# to enumerations, objects, classes <e)g) control o" de"aults=, class hierarchies <e)g) inherited constructors=, tem#lates, conce#ts, conce#t ma#s, a7ioms, aliases, e7ce#tions, loo#s, threads, etc), rather than to a single Eone si:e "its allF abstraction mechanism) -y ideal is to use #rogramming language "acilities to hel# #rogrammers thin* di""erently about system design and im#lementation) I thin* C++$7 can do that @ and do it not just "or C++ #rogrammers but "or #rogrammers used to a !ariety o" modern #rogramming languages in the general and !ery broad area o" systems #rogramming) In other words, I(m still an o#timist) ( 7*arne Stroustrup- inventor of C++! OStr0<!AP C++ is constantly evolvin&! Hver the past few years the C++ standards #ody has #een developin& the ne)t revi1 sion of C++- nicknamed C++$7! C++0) is a ma*or up&rade to the C++ pro&rammin& lan&ua&e and as we wrap up our tour of C++- ' thou&ht it appropriate to conclude #y e)plorin& what C++0) will have in store! This chapter covers some of the more impressive features of C++0) and what to e)pect in the future! 7e aware that C++0) has not yet #een finalized- and the material in this chapter may not match the final C++0) specification! /owever- it should #e a &reat launchin& point so that you know where to look to learn more a#out the ne)t release of C++! Automatic Ty&e Inference Consider the followin& piece of code=
void 9o5omething(*onst multima$ string, ve*tor int) )? m%:a$) { *onst $air multima$ string, ve*tor int) )33*onstBiterator, multima$ string, ve*tor int) )33*onstBiterator) eV = m%:a$.eVualBrange(.5tring0.); for(multima$ string, ve*tor int) )33*onstBiterator itr = eV.first; itr 0= eV.se*ond; !!itr) *out itr,)siUe() endl; #
This a#ove code takes in a multima$ mappin& from strings to ve*tor int)s and prints out the len&th of all vectors in the multima$ whose key is 4Strin&U6 0hile the code is perfectly le&al C++- it is e)tremely difficult to follow #ecause more than half of the code is spent listin& the types of two varia#les- eV and itr! 'f you'll no1 tice- these varia#les can only take on one type ( the type of the e)pression used to initialize them! Since the compiler knows all of the types of the other varia#les in this code snippet- couldn't we *ust ask the compiler to &ive eV and itr the ri&ht types> 8ortunately- in C++0)- the answer is yes thanks to a new lan&ua&e feature called ty#e in"erence! Usin& type inference- we can rewrite the a#ove function in a#out half as much space=
1 BE; 1
void 9o5omething(*onst multima$ string, ve*tor int))? m%:a$) { *onst auto eV = m%:a$.eVualBrange(.5tring0.); for(auto itr = eV.first; itr 0= eV.se*ond; !!itr) *out itr,)siUe() endl; #
"otice that we've replaced all of the #ulky types in this e)pression with the keyword auto- which tells the C++0) compiler that it should infer the proper type for a varia#le! The standard iterator loop is now consider1 a#ly easier to write- since we can replace the clunky multima$ string, ve*tor int) )33*onstBiter, ator with the much simpler auto! Similarly- the hideous return type associated with eVualBrange is entirely a#sent! 7ecause auto must #e a#le to infer the type of a varia#le from the e)pression that initializes it- you can only use auto when there is a clear type to assi&n to a varia#le! 8or e)ample- the followin& is ille&al=
auto 7;
C++ function and returns an adapta#le version of that function! 'n our discussion of the li#rary's implementa1 tion- we saw that the return type of $trBfun is either $ointerBtoBunar%Bfun*tion Arg, Cet) or $ointerBtoBbinar%Bfun*tion Arg8, ArgH, Cet)- dependin& on whether the parameter is a unary or #inary function! This means that if you want to use $trBfun to create an adapta#le function and want to store the result for later use- usin& current C++ you'd have to write somethin& to the effect of
$ointerBtoBunar%Bfun*tion int, bool) ou*hies = $trBfun(5ome&un*tion);
This is terri#ly hard to read #ut more importantly #reaks the wall of a#straction of $trBfun! The entire purpose of $trBfun is to hide the transformation from function to functor- and as soon as you are re5uired to know the return type of $trBfun the #enefits of the automatic wrappin& facilities vanish! 8ortunately- auto can help maintain the a#straction- since we can rewrite the a#ove as
auto ho'Ei*e = $trBfun(5ome&un*tion);
C++0) will provide a companion operator to auto called de*lt%$e that returns the type of a &iven e)pression! 8or e)ample- de*lt%$e(8 ! H) will evaluate to int- while de*lt%$e(ne' *har) will #e *har 2! de*l, t%$e does not evaluate its ar&ument ( it simply yields its type ( and thus incurs no cost at runtime! Hne potential use of de*lt%$e arises when writin& template functions! 8or e)ample- suppose that we want to write a template function as follows=
tem$late t%$ename ;) "2 some t%$e 2" :%&un*tion(*onst ;? val) { return val.do5omething(); #
This function accepts a ; as a template ar&ument- invokes that o#*ect's do5omething mem#er function- then re1 turns its value Knote that if the type ; doesn't have a mem#er function do5omething- this results in a compile1 time errorL! 0hat should we use as the return type of this function> 0e can't tell #y simply lookin& at the type ;- since the do5omething mem#er function could theoretically return any type! /owever- #y usin& de*lt%$e and a new function declaration synta)- we can rewrite this as
1 BEA 1
"otice that we defined the function's return type as auto- and then after the parameter list said that the return type is de*lt%$e(val.do5omething())! This new synta) for function declarations is optional- #ut will make complicated function prototypes easier to read! #o7e Semantics 'f you'll recall from our discussion of copy constructors and assi&nment operators- when returnin& a value from a function- C++ initializes the return value #y invokin& the class's copy constructor! 0hile this method &uarantees that the returned value is always valid- it can #e &rossly inefficient! 8or e)ample- consider the followin& code=
ve*tor string) 6oadAll1ords(*onst string? filename) { ifstream in$ut(filename.*Bstr()); if(0in$ut.isBo$en()) thro' runtimeBerror(.&ile not found0.); "2 @se the ve*torAs insert fun*tion, $lus some istreamBiterators, to 2 load the *ontents of the file. 2" ve*tor string) result; result.insert(result.begin(), istreamBiterator string)(in$ut), istreamBiterator string)()); return result; #
/ere- we open the file specified #y filename- then use a pair of istreamBiterators to load the contents of the file into the ve*tor! ,t the end of this function- #efore the return result statement e)ecutes- the memory associated with the result ve*tor looks somethin& like this Kassumin& a ve*tor is implemented as a pointer to a raw C++ arrayL= Al$ha result elems len 8FG Oeta Gamma ... 4hi <si =mega "ow- the statement return result e)ecutes and C++ initializes the return value #y invokin& the ve*tor copy constructor! ,fter the copy the pro&ram's memory looks like this=
1 BEB 1
result
elems len 8FG
return value
elems len 8FG
,fter the return value is initialized- result will &o out of scope and its destructor will clean up its memory! Gemory now looks like this= Al$ha return value elems len 8FG Oeta Gamma ... 4hi <si =mega /ere- we made a full deep copy of the contents of the returned o#*ect- then deallocated all of the ori&inal memory! This is inefficient- since we needlessly copied a lon& list of strin&s! There is a much #etter way to re1 turn the ve*tor from the function! 'nstead of initializin& the return value #y makin& a deep copy- instead we'll make it a shallow copy of ve*tor we're returnin&! The in1memory representations of these two vectors thus look like this=
result
elems len 8FG
return value
elems 8FG len
1 BE? 1
,lthou&h the two vectors share the same memory- the returned ve*tor has the same contents as the source ve*tor and is in fact indistin&uisha#le from the ori&inal! 'f we then modify the ori&inal ve*tor #y detachin& its pointer from the array and havin& it point to E@66 Kor- since this is C++0)- the special value null$trL- then we end up with a picture like this=
result
elems len
null$tr
return value
elems 8FG len
"ow- result is an empty ve*tor whose destructor will not clean up any memory- and the callin& function will end up with a ve*tor whose contents are e)actly those returned #y the function! 0e've successfully returned the value from the function- #ut avoided the e)pensive copy! 'n our case- if we have a ve*tor of n strings of len&th at most m- then the al&orithm for copyin& the ve*tor will take HKmnL! The al&orithm for simply trans1 ferrin& the pointer from the source ve*tor to the destination- on the other hand- is HK1L for the pointer manipu1 lations! The difference #etween the current method of returnin& a value and this improved version of returnin& a value is the difference #etween copy semantics and move semantics) ,n o#*ect has co#y semantics if it can #e duplicated in another location! ,n o#*ect has mo!e semantics Ka feature introduced in C++0)L if it can #e moved from one varia#le into another- destructively modifyin& the ori&inal! The key difference #etween the two is the num#er of copies at any point! Copyin& an o#*ect duplicates its data- while movin& an o#*ect transfers the contents from one o#*ect to another without makin& a copy! To support move semantics- C++0) introduces a new varia#le type called an r!alue re"erence whose synta) is ;%$e ??! 8or e)ample- an rvalue reference to a ve*tor int) would #e a ve*tor int) ??! 'nformally- you can view an rvalue reference as a reference to a temporary o#*ect- especially one whose contents are to #e moved from one location to another! et's return to the a#ove e)ample with returnin& a ve*tor from a function! 'n the current version of C++- we'd define a copy constructor and assi&nment operator for ve*tor to allow us to return ve*tors from functions and to pass ve*tors as parameters! 'n C++0)- we can optionally define another special function- called a mo!e constructor- that initializes a new ve*tor #y movin& data out of one ve*tor into another! 'n the a#ove e)amplewe mi&ht define a move constructor for the ve*tor as follows=
1 BE6 1
"2 :ove *onstru*tor ta+es a ve*tor?? as a $arameter, sin*e 'e 'ant to move 2 data from the $arameter into this ve*tor. 2" tem$late t%$ename ;) ve*tor ;)33ve*tor()ectorSS other) { "2 1e $oint to the same arra% as the other ve*tor and have the same length. 2" elems = other.elems; len = other.len; "2 9estru*tivel% modif% the sour*e ve*tor to sto$ sharing the arra%. 2" other.elems = null$tr; other.len = 0; #
"ow- if we return a ve*tor from a function- the new ve*tor will #e initialized usin& the move constructor rather than the re&ular copy constructor! 0e can similarly define a mo!e assignment o#erator Kas opposed to the traditional co#y assi&nment operatorL- as shown here=
tem$late t%$ename ;) ve*tor ;)? ve*tor ;)33o$erator= ()ectorSS other) { if(this 0= ?other) { delete [] elems; elems = other.elems; len = other.len; "2 :odif% the sour*e ve*tor to sto$ sharing the arra%. 2" other.elems = null$tr; other.len = 0; # return 2this; #
The similarity #etween a copy constructor and copy assi&nment operator is also noticea#le here in the move con1 structor and move assi&nment operator! 'n fact- we can rewrite the pair usin& helper functions *lear and mo, ve=ther=
tem$late t%$ename ;) void ve*tor ;)33move=ther(ve*tor?? other) { "2 1e $oint to the same arra% as the other ve*tor and have the same 2 length. 2" elems = other.elems; len = other.len; "2 :odif% the sour*e ve*tor to sto$ sharing the arra%. 2" other.elems = null$tr; other.len = 0;
1 BE@ 1
Gove semantics are also useful in situations other than returnin& o#*ects from functions! 8or e)ample- suppose that we want to insert an element into an array- shufflin& all of the other values down one spot to make room for the new value! Usin& current C++- the code for this operation is as follows=
tem$late t%$ename ;) void (nsert(ntoArra%(;2 elems, int siUe, int $osition, *onst ;? toAdd) { for(int i = siUe; i ) $osition; !!i) elems[i] = elems[i Q 8]; "" 5huffle elements do'n. elems[i] = toAdd; #
There is nothin& wron& #er se with this code as it's written- #ut if you'll notice we're usin& copy semantics to shuffle the elements down when move semantics is more appropriate! ,fter all- we don't want to co#y the ele1 ments into the spot one element downR we want to mo!e them! 'n C++0)- we can use an o#*ect's move semantics Kif anyL #y usin& the special helper function move- e)ported #y utilit%)- which simply returns an rvalue reference to an o#*ect! "ow- if we write
a = move(b);
'f a has support for move semantics- this will move the contents of b into a! 'f a does not have support for move semantics- however- C++ will simply fall #ack to the default o#*ect copy #ehavior usin& the assi&nment operator! 'n other words- supportin& move operations is purely optional and a class can still use the old fashioned copy constructor and assi&nment operator pair for all of its copyin& needs! /ere's the rewritten version of (nsert(ntoArra%- this time usin& move semantics=
tem$late t%$ename ;) void (nsert(ntoArra%(;2 elems, int siUe, int $osition, *onst ;? toAdd) { for(int i = siUe; i ) $osition; !!i) elems[i] = mo)e(elems[i Q 8]); "" :ove elements do'n. elems[i] = toAdd; #
Curiously- we can potentially take this one step further #y movin& the new element into the array rather than copyin& it! 0e thus provide a similar function- which we'll call :ove(ntoArra%- which moves the parameter into the specified position=
1 BEE 1
tem$late t%$ename ;) void :ove(ntoArra%(;2 elems, int siUe, int $osition, TSS toAdd) { for(int i = siUe; i ) $osition; !!i) elems[i] = mo)e(elems[i Q 8]); "" :ove elements do'n.
"2 Eote that even though toAdd is an rvalue referen*e, 'e still must 2 e7$li*itl% move it in. ;his $revents us from a**identall% using 2 move semanti*s in a fe' edge *ases. 2" elems[i] = mo)e(toAdd);
Gove semantics and copy semantics are independent and in C++0) it will #e possi#le to construct o#*ects that can #e moved #ut not copied or vice1versa! 'nitially this mi&ht seem stran&e- #ut there are several cases where this is e)actly the #ehavior we want! 8or e)ample- it is ille&al to copy an ofstream #ecause the #ehavior asso1 ciated with the copy is undefined ( should we duplicate the file> 'f so- where> Hr should we *ust share the file> /owever- it is perfectly le&itimate to mo!e an ofstream from one varia#le to another- since at any instant only one ofstream varia#le will actually hold a reference to the file stream! Thus functions like this one=
ofstream Get;em$orar%=ut$ut&ile() { "2 @se the tm$nam() fun*tion from *stdio) to get the name of a 2 tem$orar% file. 4onsult a referen*e for more detail. 2" *har tm$namOuffer[6Btm$nam]; ofstream result(tm$nam(tm$namOuffer)); return result; "" @ses move *onstru*tor, not *o$% *onstru*tor0 #
0ill #e perfectly le&al in C++0) #ecause of move constructors- thou&h the same code will not compile in current C++ #ecause ofstream has no copy constructor! ,nother e)ample of an o#*ect that has well1defined move #ehavior #ut no copy #ehavior is the C++ autoB$tr class! 'f you'll recall- assi&nin& one autoB$tr to another destructively modifies the ori&inal autoB$tr! This is e)actly the definition of move semantics! /owever- under current C++ rules- implementin& autoB$tr is e)1 tremely difficult and leads to all sorts of une)pected side effects! Usin& move constructors- however- we can eliminate these pro#lems! C++0) will introduce a replacement to autoB$tr called uniVueB$tr which- like autoB$tr- represents a smart pointer that automatically cleans up its underlyin& resource when it &oes out of scope! Unlike autoB$tr- however- uniVueB$tr cannot #e copied or assi&ned #ut can #e moved freely! Thus code of this sort=
uniVueB$tr int) m%<tr(ne' int); uniVueB$tr int) other = m%<tr; "" Error0 4anAt *o$% uniVueB$tr.
0ill not compile! /owever- #y e)plicitly indicatin& that the operation is a move- we can transfer the contents from one uniVueB$tr to another=
uniVueB$tr int) m%<tr(ne' int); uniVueB$tr int) other = mo)e(m%<tr); "" 6egal; m%<tr is no' em$t%
Gove semantics and rvalue references may seem confusin& at first- #ut promise to #e a powerful and welcome addition to the C++ family!
1 BE< 1
Several chapters a&o we considered the pro#lem of countin& the num#er of strings in a ve*tor whose len&ths were less than some value determined at runtime! 0e e)plored how to solve this pro#lem usin& the *ountBif al&orithm and a functor! Hur solution was as follows=
*lass 5horter;han { $ubli*3 e7$li*it 5horter;han(int ma76ength) 3 length(ma76ength) {# bool o$erator() (*onst string? str) *onst { return str.length() length; # $rivate3 int length; #; *onst int m%Dalue = Get(nteger(); *ountBif(m%De*tor.begin(), m%De*tor.end(), ShorterThan!my5alue$);
This functor1#ased approach works correctly- #ut has a hu&e amount of #oilerplate code that o#scures the actual mechanics of the solution! 0hat we'd prefer instead is the a#ility to write code to this effect=
*onst int m%Dalue = Get(nteger(); *ountBif(m%De*tor.begin(), m%De*tor.end(), the string is shorter than my1alue);
Usin& a new C++0) lan&ua&e feature known as lambda e7#ressions Ka term those of you familiar with lan&ua&es like Scheme- G - or /askell mi&ht reco&nizeL- we can write code that very closely mirrors this structure! Hne possi#ility looks like this=
*onst int m%Dalue = Get(nteger(); *ountBif(m%De*tor.begin(), m%De*tor.end(), 9my5alue:!const stringS #$ * return #.length!$ . my5alue; 4);
The construct in the final line of code is a lambda e7#ression- an unnamed K4anonymous6L function that e)ists only as a parameter to *ountBif! 'n this e)ample- we pass as the final parameter to *ountBif a temporary function that accepts a sin&le string parameter and returns a bool indicatin& whether or not its len&th is less than m%Dalue! The #racket synta) [m%Dalue] #efore the parameter declaration (int 7) is called the ca#ture list and indicates to C++ that the lam#da e)pression can access the value of m%Dalue in its #ody! 7ehind the scenes- C++ converts lam#da e)pressions such as the one a#ove into uni5uely1named functors- so the a#ove code is identical to the functor1#ased approach outlined a#ove! 8or those of you with e)perience in a functional pro&rammin& lan&ua&e- the e)ample outlined a#ove should strike you as an e)traordinarily powerful addition to the C++ pro&rammin& lan&ua&e! am#da e)pressions &reatly simplify many tasks and represent an entirely different way of thinkin& a#out pro&rammin&! 't will #e interestin& to see how rapidly lam#da e)pressions are adopted in professional code!
'n the previous chapter we implemented a class called &un*tion that wrapped an ar#itrary unary function! Fe1 call that the definition of &un*tion is as follows=
template .typename UrgTypeB typename CeturnType> class Function { $ubli*3 "2 4onstru*tor and destru*tor. 2" tem$late t%$ename @nar%&un*tion) &un*tion(@nar%&un*tion); ^&un*tion(); "2 4o$% su$$ort. 2" &un*tion(*onst &un*tion? other); &un*tion? o$erator= (*onst &un*tion? other); "2 &un*tion is a fun*tor that *alls into the stored resour*e. 2" CeturnType operator!$ !const UrgTypeS )alue$ const; $rivate3 "2 ... 2" #;
0hat if we want to &eneralize &un*tion to work with functions of ar#itrary arity> That is- what if we want to create a class that encapsulates a #inary- nullary- or ternary function> Usin& standard C++- we could do this #y introducin& new classes Oinar%&un*tion- Eullar%&un*tion- and ;ernar%&un*tion that were implemen1 ted similarly to &un*tion #ut which accepted a different num#er of parameters! 8or e)ample- here's one pos1 si#le interface for Oinar%&un*tion=
template .typename UrgType7B typename UrgType8B typename CeturnType> class (inaryFunction { $ubli*3 "2 4onstru*tor and destru*tor. 2" tem$late t%$ename Oinar%&n) Oinar%&un*tion(Oinar%&n); ^Oinar%&un*tion(); "2 4o$% su$$ort. 2" Oinar%&un*tion(*onst Oinar%&un*tion? other); Oinar%&un*tion? o$erator= (*onst Oinar%&un*tion? other); "2 &un*tion is a fun*tor that *alls into the stored resour*e. 2" CeturnType operator!$ !const UrgTypeS arg7B const UrgTypeS arg8$ const; $rivate3 "2 ... 2" #;
0ritin& different class templates for functions of each arity is trou#lesome! ,s was the case with the 9imen, sion;%$e e)ample- if we write &un*tion1like classes for a fi)ed num#er of arities Ksay- functions #etween zero and ten ar&umentsL and then discover that we need a wrapper for a function with more ar&uments- we we'll have to write that class from scratch! Goreover- the structure of each function wrapper is almost identical! Compare the Oinar%&un*tion and &un*tion class interfaces mentioned a#ove! 'f you'll notice- the only dif1 ference #etween the classes is the num#er of template ar&uments and the num#er of ar&uments to o$erator()! 's there some way that we can use this commonality to implement a sin&le class that works with functions of ar1 #itrary arity> Usin& the current incarnation of C++ this is not possi#le- #ut usin& a C++0) feature called !ariadic tem#lates we can do *ust this!
1 B<1 1
, !ariadic tem#late is a template that can accept an ar#itrary num#er of template ar&uments! These ar&uments are &rouped toðer into ar&uments called #arameter #ac*s that can #e e)panded out to code for each ar&ument in the pack! 8or e)ample- the followin& class is parameterized over an ar#itrary num#er of ar&uments=
tem$late typename... Urgs) *lass ;u$le { "2 ... 2" #;
The synta) t%$ename... Args indicates that Args is a parameter pack that represents an ar#itrary num#er of ar&uments! Since Args represents a list of ar&uments rather than an ar&ument itself- it is ille&al to use Args in an e)pression #y itself! 'nstead- Args must #e used in a #attern e7#ression indicatin& what operation should #e applied to each ar&ument in the pack! 8or e)ample- if we want to create a constructor for ;u$le that accepts a list of ar&uments with one ar&ument for each type in Args- we could write the followin&=
tem$late t%$ename... Args) *lass ;u$le { $ubli*3 Tuple!const UrgsS ...$; #;
/ere- the synta) *onst Args? ... is a pattern e)pression indicatin& that for each ar&ument in Args- there should #e a parameter to the constructor that's passed #y reference1to1 *onst! 8or e)ample- if we created a ;u$le int)- the constructor would #e ;u$le int)(*onst int?)- and if we create a ;u$le int, double)- it would #e ;u$le int, double)(*onst int?, *onst double?)! et's return to the e)ample of &un*tion! Suppose that we want to convert &un*tion from encodin& a unary function to encodin& a function of ar#itrary arity! Then we could chan&e the class interface to look like this=
template .typename CeturnTypeB typename... UrgumentTypes> class Function { $ubli*3 "2 4onstru*tor and destru*tor. 2" tem$late t%$ename 4allable) &un*tion(4allable); ^&un*tion(); "2 4o$% su$$ort. 2" &un*tion(*onst &un*tion? other); &un*tion? o$erator= (*onst &un*tion? other); "2 &un*tion is a fun*tor that *alls into the stored resour*e. 2" CeturnType operator!$ !const UrgumentTypesS ... args$ const; $rivate3 "2 ... 2" #; &un*tion is now parameterized such that the first ar&ument is the return type and the remainin& ar&uments are ar&ument types! 8or e)ample- a &un*tion int, string) is a function that accepts a string and returns an int- while a &un*tion bool, int, int) would #e a function acceptin& two ints and returnin& a bool!
0e've *ust seen how the interface for &un*tion looks with variadic templates- #ut what a#out the implementa1 tion> 'f you'll recall- the ori&inal implementation of &un*tion's o$erator() function looked as follows=
1 B<; 1
tem$late t%$ename Arg;%$e, t%$ename Ceturn;%$e) Ceturn;%$e &un*tion Arg;%$e, Ceturn;%$e)33o$erator()(*onst Arg;%$e? $aram) *onst { return fun*tion,)e7e*ute($aram); #
et's #e&in convertin& this to use variadic templates! The first step is to ad*ust the si&nature of the function- as shown here=
template .typename CetTypeB typename... UrgTypes> CetType Function.CetTypeB UrgTypes...>33o$erator()(const UrgTypesS... args) *onst { "2 ... 2" #
"otice that we've specified that this is a mem#er of &un*tion Cet;%$e, Arg;%$es...)! 'n the unary version of &un*tion- we implemented o$erator() #y callin& a stored fun*tion o#*ect's e7, e*ute mem#er function- passin& in the parameter &iven to o$erator()! 7ut how can we now call e7e*ute passin& in an ar#itrary num#er of parameters> The synta) for this a&ain uses ... to tell C++ to e)pand the args parameters to the function into an actual list of parameters! This is shown here=
tem$late t%$ename Cet;%$e, t%$ename... Arg;%$es) Cet;%$e &un*tion Cet;%$e, Arg;%$es...)33o$erator()(*onst Arg;%$es?... args) *onst { return unction->e#ecute!args...$; #
$ust as usin& ... e)pands out a parameter pack into its individual parameters- usin& ... here e)pands out the varia#le1len&th ar&ument list args into each of its individual parameters! This synta) mi&ht seem a #it tricky at first- #ut is easy to pick up with practice! ,nother case where variadic templates is useful is with the 9imension;%$e e)ample from an earlier chapter! Fecall that we defined a class called 9imension;%$e parameterized over the kilo&rams- meters- and seconds of the stored 5uantity that let the compiler check unit correctness at compile1time! 7ut what if we want to e)pand 9imension;%$e to work with other S' units- such as electrical current- an&le- or luminous intensity> Hne op1 tion would #e to templatize the 9imension;%$e type over more inte&er parameters- as shown here=
tem$late int +gB int mB int sB int radB int ampB int cd> *lass 9imension;%$e { $ubli*3 e7$li*it 9imension;%$e(double amount) 3 Vuantit%(amount) {# double getWuantit%() *onst { return Vuantit%; # $rivate3 double Vuantit%; #;
/owever- what will happen now if we want to implement the mathematical operators !- ,- 2- and "> 'f you'll re1 call- those functions had very comple) si&natures! ''ve reprinted the implementation of o$erator! and o$er, ator2 here=
1 B<A 1
This si&natures are not at all clean- and if we try to update them to work with even more types of units it will 5uickly #ecome impossi#le to read or maintain! 7ut what if we opt for a different approach> 'nstead of parameterizin& 9imension;%$e over a fi)ed num#er of units- let's instead parameterize them over an arbitrary number of units! 8or e)ample=
tem$late int... units) *lass 9imension;%$e { $ubli*3 e7$li*it 9imension;%$e(double amount) 3 Vuantit%(amount) {# double getWuantit%() *onst { return Vuantit%; # $rivate3 double Vuantit%; #;
/ere- the synta) int... units indicates that there can #e multiple inte&er parameters to 9imension;%$e! 0hat would the implementation of o$erator! look like &iven this representation> Fecall that it's only le&al to add two dimensioned 5uantities if their units a&ree! Thus we'll have o$erator! accept two 9imension;%$es of the same type- then perform the addition! This is shown here=
tem$late int... units) const DimensionType.units...> o$erator! (const DimensionType.units...>? one, const DimensionType.units...>? t'o) { return DimensionType.units...>(one.getWuantit%() ! t'o.getWuantit%()); #
1 B<B 1
This new code is easier to read than the ori&inal- since the units are all #undled toðer in the sin&le parameter pack units instead of three individual unit types! "ow- let's see how we mi&ht implement o$erator2! Fecall that o$erator2 accepts two parameters represent1 in& dimensioned types of ar#itrary units- then returns a new dimensioned type whose 5uantity is the product of the input 5uantities and whose units are the sum of the ori&inal units! Usin& traditional templates- this is 5uite difficult ( as you saw- we have to accept two parameters for each unit and have to e)plicitly indicate that the re1 turn type is formed #y addin& each individual unit! 7ut usin& variadic templates and pattern e)pressions- this can #e &reatly simplified! 8or technical reasons- we'll define o$erator2 as a template mem#er function of 9i, mension;%$e- as shown here=
tem$late int... units) *lass 9imension;%$e { $ubli*3 e7$li*it 9imension;%$e(double amount) 3 Vuantit%(amount) {# double getWuantit%() *onst { return Vuantit%; # template .int... otherUnits> const DimensionType.units 3 otherUnits...> operator R!const DimensionType.otherUnits...>S other$ const * return DimensionType.units 3 otherUnits...>!Guantity R other.getHuantity!$$; 4 $rivate3 double Vuantit%; #;
This code is still very dense- #ut is much- much clearer than the ori&inal version of the code! "otice that the re1 turn type of the function is a *onst 9imension;%$e units ! other@nits...)! /ere- the pattern e)pres1 sion is units ! [email protected] indicatin& that the units in the return type can #e computed #y addin& pair1 wise the units in the input types! Hne potential pro#lem with this new approach is that it's le&al to construct 9imension;%$es parameterized over differin& num#ers of ar&umentsR for e)ample- a 9imension;%$e 8, H, F) and a 9imension;%$e 0)! Gi)in& and matchin& different num#ers of units likely indicates a pro&rammer error- so we'd like to enforce the fact that each 9imension;%$e has the same num#er of ar&uments! Usin& a C++0) construct called a static assertion- it's possi#le to check this at compile1time! ike assert- stati*Bassert accepts a predicate as a parameter and then checks whether the predicate is true- causin& an error if not! Unlike assert- howeverstati*Bassert checks occur at com#ile-time rather than runtime! Usin& stati*Bassert and the new siUeof... operator which returns the num#er of values in a parameter pack- we can enforce that 9imension, ;%$e varia#les have a specific num#er of ar&uments as follows=
1 B<? 1
static&assert!siLeo ...!units$ ,, +/umDimensionsB "=rong number o arguments to DimensionType%"$; e7$li*it 9imension;%$e(double amount) 3 Vuantit%(amount) {# double getWuantit%() *onst { return Vuantit%; # tem$late int... other@nits) *onst 9imension;%$e units ! other@nits...) o$erator 2(*onst 9imension;%$e other@nits...)? other) *onst { return 9imension;%$e units ! other@nits...)(Vuantit% 2 other.getWuantit%()); # $rivate3 double Vuantit%; #;
These two e)amples of variadic templates should demonstrate e)actly how powerful C++0) lan&ua&e feature promises to #e! 't will #e interestin& to see what other ideas future C++ developers dream up! Library $%tensions 'n addition to all of the lan&ua&e e)tensions mentioned in the a#ove sections- C++0) will provide a new set of li#raries that should make certain common tasks much easier to perform=
$nhanced Smart -ointers! C++0) will support a wide variety of smart pointers- such as the reference1 counted sharedB$tr and the aforementioned uniVueB$tr! <ew STL Containers! The current ST associative containers Kma$- set- etc!L are layered on top of #alanced #inary trees- which means that traversin& the ma$ and set always produce elements in sorted order! /owever- the sorted nature of these containers means that insertion- lookup- and deletion are all HKl& nL- where n is the size of the container! 'n C++0)- the ST will #e enhanced with unorderedBma$- unorderedBset- and multicontainer e5uivalents thereof! These containers are layered on top of hash ta#les- which have HK1L lookup and are useful when orderin& is not important! #ultithreading Su&&ort! Tirtually all ma*or C++ pro&rams these days contain some amount of multi1 threadin& and concurrency- #ut the C++ lan&ua&e itself provides no support for concurrent pro&ram1 min&! The ne)t incarnation of C++ will support a threadin& li#rary- alon& with atomic operations- locksand all of the #ells and whistles needed to write ro#ust multithreaded code! 1egular $%&ressions! The com#ination of C++ strings and the ST al&orithms encompasses a &ood deal of strin& processin& functionality #ut falls short of the features provided #y other lan&ua&es like $ava- Cython- and KespeciallyL Cerl! C++0) will au&ment the strin&s li#rary with full support for re&ular e)pressions- which should make strin& processin& and compiler1authorin& considera#ly easier in C++! E&graded . unctional> library! C++0) will e)pand on fun*tional) with a &eneric fun*tion type akin to the one descri#ed a#ove- as well as a superchar&ed bind function that can #ind ar#itrary parameters in a function with ar#itrary values!
1 B<6 1
Cha#ter ;/% C++$7 1andom <umber :eneration! C++'s only random num#er &enerator is rand- which has e)tremely low randomness Kon some implementations num#ers to&&le #etween even and oddL and is not particu1 larly useful in statistics and machine learnin& applications! C++0)- however- will support a rich random num#er &enerator li#rary- complete with a host of random num#er &enerators and pro#a#ility distri#ution functors! #eta&rogramming Traits Classes! C++0) will provide a lar&e num#er of classes called traits classes that can help &eneric pro&rammers write optimized code! 0ant to know if a template ar&ument is an a#1 stract class> $ust check if isBabstra*t ;)33t%$e evaluates to trueBt%$e or falseBt%$e!
2ther @ey Language !eatures /ere's a small samplin& of the other up&rades you mi&ht find useful=
Enified Initiali3ation Synta%= 't will #e possi#le to initialize C++ classes #y usin& the curly #race syn1 ta) Ke!&! ve*tor int) v = {8, H, F, J, K#;L 9elegating Constructors= Currently- if several constructors all need to access the same code- they must call a shared mem#er function to do the work! 'n C++0)- constructors can invoke each other in initial1 izer lists! ,etter $numerations= Currently- enum can only #e used to create inte&ral constants- and those con1 stants can #e freely compared a&ainst each other! 'n C++0)- you will #e a#le to specify what type to use in an enumeration- and can disallow automatic conversions to int! Angle ,rackets= 't is currently ille&al to terminate a nested template #y writin& two close #rackets con1 secutively- since the compiler confuses it with the stream insertion o$erator ))! This will #e fi)ed in C++0)! C)) Com&atibility= C++0) will formally introduce the long long type- which many current C++ compilers support- alon& with various preprocessor enhancements!
C+++% Today ,lthou&h C++0) has not yet #een adopted as a standard- there are several freely1availa#le compilers that support a su#set of C++0) features! 8or e)ample- the inu) compiler &++ version B!B!0 has support for much of C++0)and Gicrosoft Tisual Studio ;010 Kstill only availa#le in #eta formL has a fair num#er of features implementedincludin& lam#da e)pressions and the auto keyword! 'f you want to e)perience the future of C++ today- con1 sider downloadin& one of these compilers!
1 B<@ 1
Con&ratulationsU .ou've made it throu&h CS106 ! .ou've taken the first step on your *ourney toward a mastery of the C++ pro&rammin& lan&ua&e! This is no easy featU 'n the course of readin& throu&h this far- you now have a command of the followin& concepts= The streams li#rary- includin& how to interface with it throu&h operator overloadin&! ST containers- iterators- al&orithms- adapters- and functional pro&rammin& constructs- includin& a workin& knowled&e of how these o#*ects are put toðer! C strin&s- low1level pointer arithmetic- and how the operatin& system allocates memory to your pro&ram! The preprocessor and how to harness it to automatically &enerate C++ code! 3eneric pro&rammin& in C++ and *ust how powerful the C++ template system can #e! The *onst keyword and how to use it to communicate function side1effects to other pro&rammers! H#*ect layout and in1memory representation! Copy semantics and how to define implicit conversions #etween types! Hperator overloadin& and how to make a C++ class act like a primitive type! 0hat a functor is and how surprisin&ly useful and fle)i#le they are! D)ception handlin& and how to use o#*ects to automatically mana&e resources! 'nheritance- #oth at a hi&h1level and at the nuanced level of C++! C++0) and what C++ will look like in the future! !!! and a whole host of real1world e)amples of each of these techni5ues!
2espite all of the material we've covered here- there is much more to learn in the world of C++ and your *ourney has *ust #e&un! ' feel that it is a fittin& conclusion to this course reader to direct you toward other C++ resources that will prove invalua#le alon& your *ourney into the wondrous realm of this lan&ua&e! 'n particular- there are several e)cellent C++ resources ' would #e remiss to omit= E""ecti!e C++- -ore E""ecti!e C++- and E""ecti!e ,TL #y Scott Geyers! Cickin& up and readin& this trio of #ooks is perhaps the #est thin& you can do for yourself as a C++ pro&rammer! The #ooks in the E""ecti!e C++ series will help you transition from a solid C++ pro&rammer into an e)cellent C++ pro&rammer and are widely re1 &arded as amon& the #est C++ #ooks on the market! 0hat separates the E""ecti!e C++ series from most other C++ #ooks is that E""ecti!e C++ focuses almost e)clusively on correct usa&e of core C++ lan1 &ua&e features and how to avoid common pitfalls! 'f you plan on us1 in& C++ in the professional world- you should own copies of this #ook!
1 B<E 1
Cha#ter ;1% &here to 0o From +ere E7ce#tional C++ #y /er# Sutter! This #ook is an invalua#le tool in any C++ pro&rammer's arsenal! The #ook is lar&ely or&anized as a set of puzzles that &ive you a chance to think a#out the #est way to solve a pro#lem and what C++ issues you'll encounter in the process! ,lon& with E""ecti!e C++- E7ce#tional C++ is one of the most hi&hly1recommended C++ #ooks out there! /er# Sutter's personal we#site is also an e)cellent resource for all your C++ needs!
The esign and E!olution o" C++ #y 7*arne Stroustrup! This #ookaffectionately known to hardcore C++ pro&rammers as RE- is a &limpse into 7*arne Stroustrup's thou&ht processes as he went a#out desi&nin& C++! 2pD is not a pro&rammin& &uide- #ut rather a his1 tory of the evolution of C++ from the small lan&ua&e C with Classes into the modern lan&ua&e we know and love today! 2pD was writ1 ten #efore C++ had #een 'SH standardized and even predates the ST - meanin& that it can offer a new perspective on some of the lan1 &ua&e features and li#raries you may take for &ranted! 'f you want an interestin& &limpse into the mind of the man #ehind C++- this is the #ook for you!
-odern C++ esign% 0eneric Programming and esign Patterns A##lied #y ,ndrei ,le)andrescu! Considered the seminal work in modern C++ pro&rammin&- this #ook is an e)cellent introduction into an entirely new way of thinkin& in C++! ,le)andrescu takes many advanced lan&ua&e features like templates and multiple inheritancethen shows how to harness them to achieve syner&istic effects that are far more powerful than any of the individual features used! ,s an e)ample- the first chapter shows how to write a sin&le smart pointer class that is capa#le of storin& any type of value- performin& any sort of resource mana&ement- and havin& any copy #ehavior that the cli1 ent desires! The #ook is very lan&ua&e1intensive and re5uires you to have a &rasp of C++ sli&htly #eyond the scope of this reader- #ut is a most wonderful te)t for all who are interested!
1 B<< 1
't's #een 5uite a trip since we first started with the stream li#rary! .ou now know how to pro&ram with the ST write well1#ehaved C++ o#*ects- and even how to use functional pro&rammin& constructs! 7ut despite the im1 mense amount of material we've covered- we have #arely scratched the surface of C++! There are volumes of articles and #ooks out there that cover all sorts of amazin& C++ tips and tricks- and #y takin& the initiative and e)plorin& what's out there you can hone your C++ skills until pro#lem solvin& in C++ transforms from 4how do ' solve this pro#lem>6 to 4which of these many options is #est for solvin& this pro#lem>6 C++ is an amazin& lan&ua&e! 't has some of the most e)pressive synta) of any modern pro&rammin& lan&ua&eand affords an enormous latitude in pro&rammin& styles! Hf course- it has its flaws- as critics are ea&er to point out- #ut despite the advent of more modern lan&ua&es like $ava and Cython C++ still occupies a prime position in the software world! ' hope that you've en*oyed readin& this course reader as much as ' en*oyed writin& it! 'f you have any commentssu&&estions- or criticisms- feel free to email me at htiek9cs!stanford!edu! ike the C++ lan&ua&e- CS106 and this course reader are constantly evolvin&- and if there's anythin& ' can do to make the class more en*oya#le- #e sure to let me knowU /ave fun with C++- and ' wish you the #est of luck wherever it takes youU
A&&endices
C++ owes a &reat de#t to the C pro&rammin& lan&ua&e! /ad it not #een rooted in C synta)- C++ would have at1 tracted fewer earlier adopters and almost certainly would have vanished into the mists of history! /ad it not kept C's emphasis on runtime efficiency- C++ would have lost relevance over time and would have &one e)tinct! 7ut despite C++'s history in C- C and C++ are very different lan&ua&es with their own idioms and patterns! 't is a common mistake to think that knowled&e of C entails a knowled&e of C++ or vice1versa- and e)perience with one lan&ua&e often leads to su#optimal codin& skills in the other! 'n particular- pro&rammers with a #ack&round in pure C often use C constructs in C++ code where there is a safer or more ele&ant alternative! This is not to say that C pro&rammers are somehow worse coders than C++ pro&rammers- #ut rather that some patterns en1 &rained into the C mentality are often incompati#le with the lan&ua&e desi&n &oals of C++! This appendi) lists ten idiomatic C patterns that are either deprecated or unsafe in C++ and su&&ests replace1 ments! There is no new C++ content here that isn't already covered in the main #ody of the course reader- #ut ' hi&hly recommend readin& throu&h it anyway if you have si&nificant #ack&round in C! This #y no means an e)1 haustive list of differences #etween C and C++- #ut should nonetheless help you transition #etween the lan1 &ua&es! Ti& +5 -refer streams to stdio.h C++ contains the C runtime li#rary in its standard li#rary- so all of the 'IH functions you've seen in stdio.h) K$rintf- s*anf- fo$enL are availa#le in C++ throu&h the *stdio) header file! 0hile you're free to use $rintf and s*anf for input and output in C++- ' stron&ly advise you a&ainst doin& so #ecause the functions are inherently unsafe! 8or e)ample- consider the followin& C code=
*har m%5tring[80HJ] = {AT0A#; int m%(nt; $rintf(.Enter an integer and a string3 .); s*anf(.Zd Z80HFs., ?m%(nt, m%5tring); $rintf(.Rou entered Zd and ZsTn., m%(nt, m%5tring);
/ere- we prompt the user for an inte&er and a strin&- then print them #ack out if the user entered them correctly! ,s written there is nothin& wron& with this code! /owever- consider the portions of the code ''ve hi&hli&hted here=
*har m%5tring[7-8J] = {AT0A#; int m%(nt; $rintf(.Enter an integer and a string3 .); s*anf(.Zd Z7-8Ds., ?m%(nt, m%5tring); $rintf(.Rou entered Zd and ZsTn., m%(nt, m%5tring);
Consider the size of the #uffer- 10;B! 0hen readin& input from the user- if we don't e)plicitly specify that we want to read at most 10;A characters of input- we risk a #uffer overrun that can trash the stack and allow an at1 tacker to fully compromise the system! 0hat's worse- if there is a mismatch #etween the declared size of the #uffer K10;BL and the num#er of characters specified for readin& K10;AL- the compiler will not provide any warn1 in&s! 'n fact- the only way we would discover the pro#lem is if we were very careful to read over the code checkin& for this sort of mistake- or to run an advanced tool to dou#le1check the code for consistency! Similarly- consider the hi&hli&hted #its here=
1 ?0B 1
*har m%5tring[80HJ] = {AT0A#; int m%(nt; $rintf(.Enter an integer and a string3 .); s*anf(.Xd X7-8Ds., SmyInt, myString); $rintf(.Rou entered Xd and XsTn., myInt, myString);
"otice that when readin& values from the user or writin& values to the console- we have to e)plicitly mention what types of varia#les we are readin& and writin&! The fact that m%(nt is an int and m%5tring is a *har2 is insufficient for $rintf and s*anfR we have to mention to read in an int with Zd and a strin& with Zs! 'f we &et these #ackwards or omit one- the pro&ram contains a #u& #ut will compile with no errors!Q ,nother ve)in& point alon& these lines is the parameter list in s*anf ( we must pass in a pointer to m%(nt- #ut can *ust specify m%5tring #y itself! Confusin& these or &ettin& them #ackwards will cause a crash or a compiler warnin&which is 5uite a price to pay for use input! The pro#lem with the C 'IH li#raries is that they completely #ypass the type system! Fecall that the si&natures of $rintf and s*anf are
int $rintf(*onst *har2 formatting, ...); int s*anf (*onst *har2 formatting, ...);
The ... here means that the caller can pass in any num#er of ar&uments of any type- and in particular this means that the CIC++ compiler cannot do any type analysis to confirm that you're usin& the ar&uments correctly! 2on't &et the impression that C or C++ are type1safe ( they're not ( #ut the static type systems they have are de1 si&ned to prevent runtime errors from occurin& and su#vertin& this system opens the door for particularly nasty errors! 'n pure C- code like the a#ove is the norm! 'n C++- however- we can write the followin& code instead=
int m%(nt; string m%5tring; *out .Enter an int and a string3 .; *in )) m%(nt )) m%5tring; *out .Rou entered . m%(nt . and .
m%5tring
endl;
'f you'll notice- the only time that the types of m%(nt and m%5tring are mentioned is at the point of declaration! 0hen readin& and writin& m%(nt and m%5tring- the C++ can automatically infer which version of o$erat, or )) and o$erator to call to perform 'IH and thus there is no chance that we can accidentally read a string value into an int or vice1versa! Goreover- since we're usin& a C++1style strin&- there is no chance that we'll encounter a #uffer overflow! 'n short- the C++ streams li#rary is *ust plain safer than the routines in stdio.h)! 0hen workin& in pure C++- #e wary of the stdio.h) functions! .ou are missin& out on the chance to use the streams li#rary and are e)posin& yourself and your code to all sorts of potential security vulnera#ilities! Ti& 15 Ese C++ strings instead of CCstyle strings ife is short- nasty- and #rutish- and with C strin&s it will #e even worse! C strin&s are notoriously tricky to &et ri&ht- have a cryptic ,C'- and are the cause of all sorts of security #u&s! C++ strings- on the other hand- are el1 e&ant- pretty- and difficult to use incorrectly! 'f you try truncatin& a C++ string at an invalid inde) with erase- the string will throw an e)ception rather than clo##erin& memory! 'f you append data to a C++ string- you don't need to worry a#out reallocatin& any memory ( the o#*ect does that for you! 'n short- C
Q Gany compilers will report errors if you make this sort of mistake- #ut they are not re5uired to do so!
1 ?0? 1
strin&s are tricky to &et right- and C++ strings are tricky to &et wrong! 47ut waitU-6 you mi&ht e)claim- 47e1 cause C strin&s are so low1level- ' can sometimes outperform the heavywei&ht C++ string!6 This is a#solutely true ( #ecause C strin&s are so e)posed- you have a &reat deal of fle)i#ility and control over how the memory is mana&ed and what operations &o on #ehind the scenes! 7ut is it really worth it> /ere's a small samplin& of what can &o wron& if you're not careful with C strin&s= 1! .ou mi&ht write off the end of a #uffer- clo##erin& other data in memory and pavin& the way for a massive security #reach! ;! .ou mi&ht for&et to deallocate the memory- causin& a memory leak! A! .ou mi&ht overwrite the terminatin& null character- leadin& to a runtime error or incomprehensi#le pro1 &ram outputs! ,re C strin&s faster than their C++ counterparts> Hf course! 7ut should you nonetheless sacrifice a little speed for the peace of mind that your pro&ram isn't &oin& to let hackers take down your system> ,#solutely! Ti& 5 Ese C++ ty&ecasts instead of C ty&ecasts 7oth C and C++ have static type systems ( that is- if you try to use a varia#le of one type where a varia#le of an1 other type is e)pected- the compiler will report an error! 7oth C and C++ let you use typecasts to convert #etween types when needed- sometimes safely and sometimes unsafely! C has only one style of typecast- which is conveniently du##ed a 4C1style typecast!6 ,s mentioned in the chapter on inheritance- C1style typecasts are powerful almost to a fault! Convertin& #etween a double and an int uses the same synta) for unsafe operations like convertin& pointers to inte&ers- inte&ers to pointers- *onst varia#les to non1*onst varia#les- and pointers of one type to pointers of another type! ,s a result- it is easy to accidentally perform a typecast other than the one you wanted! 8or e)ample- suppose we want to convert a *har2 pointer to an int2 pointer- perhaps #ecause we're manually walkin& over a #lock of memory! 0e write the followin& code=
*onst *har2 m%<tr = "2 ... 2" int2 m%(nt<tr = !int R$m%<tr;
"otice that in this typecast we've converted a *onst *har2 to an int2- su#vertin& *onstness! 's this deli#er1 ate> 's this a mistake> 3iven the a#ove code there's no way to know #ecause the typecast does not communic1 ate what sort of cast is intended! 2id we mean to strip off *onstness- convert from a *har2 to an int2- or #oth> C++ provides three castin& operators K*onstB*ast- stati*B*ast- reinter$retB*astL that are desi&ned to clarify the sorts of typecasts performed in your code! Dach performs e)actly one function and causes a compile1 time error if used incorrectly! 8or e)ample- if in the a#ove code we only meant to convert from a *onst *har2 to a *onst int2 without strippin& *onstness- we could write it like this=
*onst *har2 m%<tr = "2 ... 2" *onst int2 m%(nt<tr = reinterpret&cast.const intR>(m%<tr);
"ow- if we leave off the *onst in the typecast- we'll &et a compile1time error #ecause reinter$retB*ast can't strip off *onstness! 'f- on the other hand- we want to convert the pointer from a *onst *har2 to a re&u1 lar int2- we could write it as follows=
*onst *har2 m%<tr = "2 ... 2" int2 m%(nt<tr = const&cast.intR>!reinterpret&cast.const intR>!myPtr$$;
This is admittedly much lon&er and #ulkier than the ori&inal C version- #ut it is also more e)plicit a#out e)actly what it's doin&! 't also is safer- since the compiler can check that the casts are #ein& used correctly!
1 ?06 1
0hen writin& C++ code that uses typecasts- make sure that you use the C++1style castin& operators! ,re they len&thy and ver#ose> ,#solutely! 7ut the safety and clarity &uarantees they provide will more than make up for it! Ti& "5 -refer ne6 and delete to malloc and ree 'n C++- you can allocate and deallocate memory either usin& ne' and delete or usin& mallo* and free! 'f you're used to C pro&rammin&- you may #e tempted to use mallo* and free as you have in the past! This can lead to very su#tle errors #ecause ne' and delete do not act the same as mallo* and free! 8or e)ample- con1 sider the followin& code=
string2 one = ne6 string; string2 t'o = static&cast.stringR>!malloc!siLeo string$$;
/ere- we create two string o#*ects on the heap ( one usin& ne' and one usin& mallo*! Unfortunately- the strin& allocated with mallo* is a tickin& time#om# waitin& to e)plode! 0hy is this> The answer has to do with a su#tle #ut critical difference #etween the two allocation routines! 0hen you write ne' string- C++ performs two steps! 8irst- it con*ures up memory from the heap so that the new string o#*ect has a place to &o! Second- it calls the string constructor on the new memory location to initialize the string data mem#ers! Hn the other hand- if you write mallo*(siUeof string)- you only per1 form the memory allocation! 'n the a#ove e)ample- this means that the string o#*ect pointed at #y t'o has the ri&ht size for a string o#*ect- #ut isn't actually a string #ecause none of its data mem#ers have #een set ap1 propriately! 'f you then try usin& the string pointed at #y t'o- you'll &et a nasty crash since the o#*ect is in a &ar#a&e state! To avoid pro#lems like this- make sure that you always allocate o#*ects usin& ne' rather than mallo*! 'f you do end up usin& #oth ne' and mallo* in a C++ pro&ram Kperhaps #ecause you're workin& with le&acy codeL- make sure that you are careful to deallocate memory with the appropriate deallocator function! That isdon't free an o#*ect allocated with ne'- and don't delete an o#*ect allocated with mallo*! mallo* and ne' are not the same thin&- and memory allocated with one is not necessarily safe to clean up with the other! 'n fact- do1 in& so leads to undefined #ehavior- which can really ruin your day! Ti& '5 A7oid )oidR -ointers Code in pure C a#ounds with void2 pointers- particularly in situations where a function needs to work with data of any type! 8or e)ample- the C li#rary function Vsort is prototyped as
void Vsort(void2 elems, siUeBt numElems, siUeBt elem5iUe, int (2*m$&n)(*onst void2, *onst void2));
That's 5uite a mouthful and uses void2 three times ( once for the input array and twice in the comparison func1 tion! The reason for the void2 here is that C lacks lan&ua&e1level support for &eneric pro&rammin& and con1 se5uently al&orithms that need to operate on ar#itrary data have to cater to the lowest common denominator ( raw #its and #ytes! 0hen usin& C's Vsort- you have to #e e)tremely careful to pass in all of the ar&uments correctly! 0hen sortin& an array of ints- you must take care to specify that elem5iUe is siUeof(int) and that your comparison func1 tion knows to interpret its ar&uments as pointers to ints! Cassin& in a comparison function which tries to treat its ar&uments as #ein& of some other type Kperhaps *har22s or double2sL will cause runtime errors- and spe1 cifyin& the size of the elements in the array incorrectly will pro#a#ly cause incorrect #ehavior or a #us error! Contrast this with C++'s sort al&orithm=
1 ?0@ 1
0ith C++'s sort- the compiler can determine what types of elements are stored in the ran&e O begin- endL #y lookin& at the type of the iterator passed as a parameter! The compiler can thus automatically fi&ure out the size of the elements in the ran&e! Goreover- if there is a type mismatch #etween what values the 4om$arator para1 meter accepts and what values are actually stored in the ran&e- you'll &et a compile1time error directin& you to the particular template instantiation instead of a difficult1to1dia&nose runtime error! This e)ample hi&hli&hts the key weakness of void2 ( it completely su#verts the CIC++ type system! 0hen us1 in& a void2 pointer- you are tellin& the compiler to for&et all type information a#out what's #ein& pointed at and therefore have to e)plicitly keep track of all of the relevant type information yourself! 'f you make a mistakethe compiler won't reco&nize your error and you'll have to dia&nose the pro#lem at runtime! Contrast this with C++'s template system! C++ templates are stron&ly1typed and the compiler will ensure that everythin& type1 checks! 'f the pro&ram has a type error- it won't compile- and you can dia&nose and fi) the pro#lem without hav1 in& to run the pro&ram! 0henever you're thinkin& a#out usin& a void2 in C++ pro&rammin&- make sure that it's really what you want to do! There's almost always a way to replace the void2 with a template! Then a&ain- if you want to directly ma1 nipulate raw #its and #ytes- void2 is still your #est option! Hne point worth notin&= 'n pure C- you can implicitly convert #etween a void2 pointer and a pointer of any type! 'n C++- you can implicitly convert any pointer into a void2- #ut you'll have to use an e)plicit typecast to convert the other way! 8or e)ample- the C code
int2 m%Arra% = mallo*(numElems 2 siUeof(int));
2oes not compile in C++ since mallo* returns a void2! 'nstead- you'll need to write
int2 m%Arra% = !int R$mallo*(numElems 2 siUeof(int));
Usin& the C++ stati*B*ast operator! Ti& *5 -refer )ector to raw arrays ,rrays live in a sort of nether universe! They aren't 5uite varia#les- since you can't assi&n them to one anotherand they're not 5uite pointers- since you can't reassi&n where they're pointin&! ,rrays can't remem#er how #i& they are- #ut when &iven a static array you can use siUeof to &et the total space it occupies! 8unctions that op1 erate on arrays have to either &uess the correct size or rely on the caller to supply it! 'n short- arrays are a #it of a mess in C and C++! Contrast this with the C++ ve*tor! vectors know e)actly how lar&e they are- and can tell you if you ask! They are first1class varia#les that you can assi&n to one another- and aren't implicitly converti#le to pointers! Hn top of that- they clean up their own messes- so you don't need to worry a#out doin& your own memory mana&ement! 'n short- the ve*tor is everythin& that the array isn't! 'n addition to #ein& safer than re&ular arrays- vectors can also #e much cleaner and easier to read! 8or e)ampleconsider the followin& C code=
1 ?0E 1
void :%&un*tion(int siUe) { int2 arr = mallo*(siUe 2 siUeof(int)); memset(arr, 0, siUe 2 siUeof(int)); "2 ... 2" free(arr); #;
"o u&ly computations a#out the size of each element- no messy cleanup code at the end- and no memsets! $ust a sin&le line and you &et the same effect! Goreover- since the ve*tor always cleans up its memory after it &oes out of scope- the compiler will ensure that no memory is leaked! 'sn't that nicer> Hf course- there are times that you mi&ht want to use a fi)ed1size array- such as if you know the size in advance and can fit the array into a struct! 7ut in &eneral- when &iven the choice #etween usin& arrays and usin& vectorsthe ve*tor is the more natural choice in C++! Ti& G5 A7oid goto For a number o" years I ha!e been "amiliar with the obser!ation that the Auality o" #rogrammers is a decreasing "unction o" the density o" go to statements in the #rograms they #roduce) -ore recently I disco!ered why the use o" the go to statement has such disastrous e""ects, and I became con!inced that the go to statement should be abolished "rom all Shigher le!elS #rogramming languages <i)e) e!erything e7ce#t, #erha#s, #lain machine code= ( Dds&er 2i*kstra O2i*6EP The goto keyword #een widely criticised since 2i*kstra pu#lished 43o To Statement Considered /armful6 in 1<6E- yet still mana&ed to make its way into C and conse5uently C++! 2espite its apparent simplicity- goto can cause all sorts of pro&rammin& ni&htmares #ecause it is inherently unstructured! goto can *ump pretty much anywhere and conse5uently can lead to unintuitive or even counterintuitive code! 8or e)ample- here's some code usin& goto=
int 7 = 0; start3 if (7 == 80) goto out; $rintf(.ZdTn., 7); !!7; goto start; out3 $rintf(.9one0Tn.);
1 ?0< 1
2espite goto's #ad reputation- modern C pro&rammin& still has several places in which goto can still #e useful! 8irst- goto can #e used as a sort of 4super brea+6 to #reak out of multiple levels of loop nestin&! This use is still le&itimate in C++- #ut is frowned upon stylistically! Second- goto can #e used as a way of performin& ne1 cessary cleanup in an error condition! 8or e)ample=
"2 Ceturns a string of the first num4hars *hara*ters from a file or E@66 in an 2 error *ase. 2" *har2 Cead&rom&ile(*onst *har2 filename, siUeBt num4hars) { &(6E2 f; *har2 buffer; "2 Allo*ate some s$a*e. 2" buffer = mallo*(num4hars ! 8); if(buffer == E@66) return E@66; "2 =$en the file, abort on error. 2" f = fo$en(filename, .rb.); if(f == E@66) goto error; "2 Cead the first num4hars *hara*ters, failing if 'e donAt read enough. 2" if(fread(buffer, num4hars, 8, f) 0= num4hars) goto error; "2 4lose the file, null,terminate the string, and return. 2" f*lose(f); buffer[num4hars] = AT0A; return buffer; "2 =n error, *lean u$ the resour*es 'e o$ened. 2" error3 free(buffer); if(f 0= E@66) f*lose(f); return E@66; #
/ere- there are several error conditions in which we need to clean up the temporary #uffer and potentially close an open file! 0hen this happens- rather than duplicatin& the cleanup code- we use goto to *ump to the error1 handlin& su#routine! 'n pure C this is perfectly fine- #ut in C++ would #e considered a &ross error #ecause there are much #etter al1 ternatives! ,s mentioned in the chapter on e)ception handlin&- we could instead use a catch1and1rethrow strate&y to &et the e)act same effect without goto- as shown here=
1 ?10 1
"2 Ceturns a string of the first num4hars *hara*ters from a file. 2 ;hro's a runtimeBerror on error. 2" *har2 Cead&rom&ile(*onst *har2 filename, siUeBt num4hars) { &(6E2 f; *har2 buffer = E@66; try { "2 Allo*ate some s$a*e. ;his 'ill thro' on error rather than returning 2 E@66. 2" buffer = ne' *har[num4hars ! 8]; "2 =$en the file, abort on error. 2" f = fo$en(filename, .rb.); if(f == E@66) thro6 runtime&error!";an@t open ile%"$; "2 Cead the first num4hars *hara*ters, failing if 'e donAt read enough. 2" if(fread(buffer, num4hars, 8, f) 0= num4hars) thro6 runtime&error!";an@t read enough characters%"$; "2 4lose the file, null,terminate the string, and return. 2" f*lose(f); buffer[num4hars] = AT0A; return buffer; # catch!...$ { "2 =n error, *lean u$ the resour*es 'e o$ened. 2" delete [] buffer; if(f 0= E@66) f*lose(f); # # thro6;
"ow that we're usin& e)ception1handlin& instead of goto- the code is easier to read and allows the caller to &et additional error information out of the function! ,n even #etter alternative here would #e to use an ifstream and a string to accomplish the same result! Since the ifstream and string classes have their own destructors- we don't need to e)plicitly clean up any memory! This is shown here=
1 ?11 1
This version is very clean and concise and doesn't re5uire any goto1like structure at all! Since the o#*ect de1 structors take care of all of the cleanup- we don't need to worry a#out doin& that ourselves! Gy advice a&ainst goto also applies to set>m$ and long>m$! These functions are #est replaced with C++'s e)1 ception1handlin& system- which is far safer and easier to use! Ti& (5 Ese C++Ns bool ty&e when a&&licable Crior to C<<- the C pro&rammin& lan&ua&e lacked a standard bool type and it was common to find idioms such as
enum bool {true, false#;
Hr
-define bool int -define true 8 -define false 0
Hr
for(;;) { "2 ... 2" #
2efinin& your own custom bool type is risky in C++ #ecause a custom type will not interact with lan&ua&e fea1 tures like templates and overloadin& correctly! Similarly- while #oth of the 4loop forever6 loop constructs listed a#ove are le&al C++ code- they are #oth less reada#le than the simpler
1 ?1; 1
6hile!true$ { "2 ... 2" #
'f you aren't already used to workin& with bools- ' su&&est that you #e&in doin& so when workin& in C++! Sureyou can emulate the functionality of a bool usin& an int- #ut doin& so o#scures your intentions and leads to all sorts of other messes! 8or e)ample- if you try to emulate bools usin& ints- you can &et into nasty scrapes where two ints each representin& true don't compare e5ual #ecause they hold different nonzero values! This isn't possi#le with the bool type! To avoid su#tle sources of error and to make your code more reada#le- try to use bool whenever applica#le! Ti& J5 A7oid Rtypede
structS
Then to create an instance of the struct you would declare a varia#le as follows=
struct pointT m%<oint;
'n C++- this use of stru*t is unnecessary! 't is also #ad practice- since veteran C++ pro&rammers will almost certainly have to pause and think a#out e)actly what the code means! Hf course- most C pro&rammers are also not particularly fond of this synta)- and to avoid havin& to spell out stru*t each time would write
typede struct pointT& { int 7, %; # $oint;;
This synta) is still valid C++- #ut is entirely unnecessary and makes the code si&nificantly trickier to read! Goreover- if you want to add constructors or destructors to the stru*t you would have to use the name $oint;B even thou&h e)ternally the o#*ect would #e called $oint; without the underscore! This makes the code more difficult to read and may confuse class clients! 'n the interests of clarity- avoid this use of t%$edef when writin& C++ code! Ti& )5 A7oid memcpy and memset 'n pure C- code like the followin& is perfectly normal=
stru*t $oint; { int 7, %; #; stru*t $oint; m%<oint; memset!SmyPointB -B siLeo !pointT$$;
/ere- the call to memset is used to initialize all the varia#les in the $oint; to zero! Since C lacks constructors and destructors- this code is a reasona#ly &ood way to ensure that the $oint; is initialized #efore use!
1 ?1A 1
7ecause C++ a#sor#ed C's standard li#rary- the functions memset- mem*$%- and the like are all availa#le in C++! /owever- usin& these functions can lead to su#tle #ut dan&erous errors that can cause all sorts of runtime woes! 8or e)ample- consider the followin& code=
string one = .;his is a string0.; string t'o = .( li+e this string more..; memcpy!SoneB St6oB siLeo !string$$; "" 5et one eVual to t'o Q does this 'or+S
/ere- we use mem*$% to set one e5ual to t'o! 'nitially- it mi&ht seem like this code works- #ut unfortunately this mem*$% results in undefined #ehavior and will almost certainly cause a runtime crash! The reason is that the string o#*ect contains pointers to dynamically1allocated memory- and when mem*$%in& the data from t'o into one we've made #oth of the strin& o#*ects point to the same memory! ,fter each pointer &oes out of scope- #oth will try to reclaim the memory- causin& pro#lems when the underlyin& strin& #uffer is dou#ly1deleted! Goreover- if this doesn(t immediately crash the pro&ram- we've also leaked the memory one was ori&inally usin& since all of its data mem#ers were overwritten without first #ein& cleaned up! 'f we wanted to set one e5ual to t'o- we could have *ust written this instead=
string one = .;his is a string0.; string t'o = .( li+e this string more..; t6o , one;
This uses the string's assi&nment operator- which is desi&ned to safely perform a deep copy! 'n &eneral- mi)in& mem*$% with C++ classes is *ust askin& for trou#le! Gost classes maintain some comple) in1 variants a#out their data mem#ers and what memory they reference- and if you mem*$% a #lock of data over those data mem#ers you mi&ht destroy those invariants! mem*$% doesn't respect $ubli* and $rivate- and thus completely su#verts the encapsulation safe&uards C++ tries to enforce! 7ut the pro#lem runs deeper than this! Suppose that we have a polymorphic class representin& a #inary tree node=
*lass Oinar%;reeEode { $ubli*3 Oinar%;reeEode(); virtual ^Oinar%;reeEode(); "" <ol%mor$hi* *lasses need virtual destru*tors "2 ... et*. ... 2" $rivate3 Oinar%;reeEode2 left, 2right; #;
0e want to implement the constructor so that it sets left and right to E@66- indicatin& that the node has no children! 'nitially- you mi&ht think that the followin& code is safe=
Oinar%;reeEode33Oinar%;reeEode() { "2 ]ero out this ob>e*t. (s this safeS 2" memset!thisB -B siLeo !(inaryTree/ode$$; #
Since the null pointer has value zero- it seems like this should work ( after all- if we overwrite the entire o#*ect with zeros- we've effectively nulled out all of its pointer data mem#ers! 7ut this code is a recipe for disaster #e1 cause the class contains more than *ust a left and right pointer! 'n the chapter on inheritance- we outlined
1 ?1B 1
how virtual functions are implemented usin& a virtual function ta#le pointer that sits at the #e&innin& of the class! 'f we use memset to clear out the o#*ect- we'll overwrite this virtual function ta#le pointer with E@66meanin& that any attempt to call a virtual function on this o#*ect will result in a null pointer dereference and a pro&ram crash!Q The key pro#lem with memset and mem*$% is that they completely su#vert the a#stractions C++ provides to in1 crease pro&ram safety! Dncapsulation is supposed to prevent clients from clo##erin& critical class components and o#*ect layout is done automatically specifically to prevent pro&rammers from havin& to e)plicitly manipu1 late low1level machinery! memset and mem*$% remove these #arriers and e)pose you to dan&erous you could otherwise do without! This is not to say- of course- that memset and mem*$% have no place in C++ code ( they certainly do ( #ut their role is considera#ly less prominent than in pure C! 7efore you use low1level manipulation routines- make sure that there isn't a #etter way to accomplish the same &oal throu&h more 4le&itimate6 C++ channels! 0ith that said- welcome to C++U Dn*oy your stayU
Q This e)ample is #ased on a conversation ' had with Ddward uon&- who encountered this very pro#lem when writin& a lar&e pro&ram in C++!
Streams
Problem 1) There are two steps necessary to &et /as/e76etters workin&! 8irst- we transform the input num1 #er into a strin& representation of its he)adecimal value! "e)t- usin& techni5ues similar to that for Get(ntegerwe check to see if this strin& can #e interpreted as an int when read in #ase 10! 'f so- the he)adecimal repres1 entation of the num#er must not contain any letters Ksince letters can't #e interpreted as a decimal valueL- other1 wise the representation has at least one letter in it! Hne possi#le implementation is &iven here=
bool /as/e76etters(int value) { "2 &unnel the data into a stringstream, using the he7 mani$ulator to re$resent 2 it in he7ade*imal. 2" stringstream *onverter; *onverter he# value; "2 Eo', tr% e7tra*ting the string as an int, using the de* mani$ulator to read 2 it in de*imal. 2" int dumm%; *onverter )) dec )) dumm%; "2 (f the stream failed, 'e *ouldnAt read an int and 'eAre done. 2" if(*onverter.fail()) return true; "2 =ther'ise, tr% reading something else from the stream. (f 'e su**eed, it 2 must have been a letter and 'e +no' that the integer has letters in its he7 2 re$resentation. 2" *har leftover; *onverter )) leftover; # return 0*onverter.fail();
1 ?16 1
Problem 3b) 8or this solution we'll use an ST Vueue- since we don't need access to any element of the key list e)cept the first! Then one solution is as follows=
string Digenere4i$her(string toEn*ode, Vueue int) values) { for(int + = 0; + toEn*ode.length(); !!+) { to2ncode9+: 3, )alues. ront!$; "" En*r%$t the *urrent *hara*ter )alues.push!)alues. ront!$$; "" Add the *urrent +e% to the ba*+. )alues.pop!$; "" Cemove the *urrent +e% from the front. # #
STL Iterators
Problem 3) Hne possi#le implementation of the function is as follows=
ve*tor string) 6oadAll;o+ens(string filename) { ve*tor string) result; "2 =$en the file, if 'e *anAt, >ust return the em$t% ve*tor. 2" ifstream in$ut(filename.*Bstr()); if(in$ut.fail()) return result; "2 @sing the istreamBiterator iterator ada$ter, read ever%thing out of the 2 file. 5in*e b% default the streams librar% uses 'hites$a*e as a se$arator 2 *hara*ter, this reads in all of the to+ens. 2" result.insert!result.begin!$B istream&iterator.string>!input$B istream&iterator.string>!$$; # return result;
1 ?1@ 1
"2 (terate over the ma$ u$dating the freVuen*% *ount. Eoti*e that 'e are 2 *he*+ing for the number of du$li*ate values, so 'eAll inde7 into the ma$ b% 2 loo+ing at itr,)se*ond. Also note that 'e donAt *he*+ for the *ase 'here 2 the ma$ doesnAt alread% *ontain this +e%. 5in*e 5;6 *ontainers initialiUe 2 all stored integers to Uero, if the +e% doesnAt e7ist a fresh $air 'ill be 2 *reated 'ith value Uero. 2" for(ma$ string, string)33iterator itr = in$ut.begin(); itr 0= in$ut.end(); !!itr) 33counter9itr->second:; int result = 0; "2 Eo' iterate over the entries and a**umulate those that have at least value 2 t'o. 2" for(ma$ string, string)33iterator itr = in$ut.begin(); itr 0= in$ut.end(); !!itr) if(itr,)se*ond ) 8) result != itr,)se*ond; return result; #
Problem 3) There are many &ood solutions to this pro#lem! Gy personal favorite is this one=
void 4ount6etters(ifstream? in$ut, ma$ *har, int)? freV:a$) { *har *h; 'hile(in$ut.get(*h)) !!freV:a$[*h]; #
This code is dense and relies on several properties of the stream li#rary and the ST ! 8irst- the mem#er function get accepts as input a *har #y reference- then reads in a sin&le character from the stream! Hn success- the func1 tion fills the *har with the read value! Hn failure- the value is unchan&ed and the stream &oes into a fail state! The get function then returns a reference to the stream o#*ect that did the readin&- meanin& that 'hile(in, $ut.get(*h)) is e5uivalent to
'hile(true) { in$ut.get(*h); if(0in$ut) brea+; "2 ... bod% of loo$ ... 2" #
,nd since 0in$ut is e5uivalent to in$ut.fail()- this one1liner will read in a character from the file- then #reak out of the loop if the read failed! Hnce we've read in the character- we can simply write !!freV:a$[*h]- since if the key already e)ists we're in1 crementin& the older value and if not a new keyIvalue pair will #e created with value 0- which is then incremen1 ted up to one!
1 ?1E 1
Problem ?) ,s mentioned in the hint- the trick is to use the structure of le)ico&raphic comparisons to construct a pair of strin&s lower1 and upper1#oundin& all strin&s with the &iven prefi)! 8or e)ample- suppose that we want to find all words #e&innin& with the prefi) anti! "ow- any word #e&innin& with anti must compare le)ico1 &raphically &reater than or e5ual to anti- since the first four characters will match #ut the word #e&innin& with anti must also #e lon&er than anti! 8or e)ample- antigen and antidisestablishmentarianism #oth compare &reater than anti since they have the same prefi) as anti #ut are lon&er! The ne)t o#servation is that any word that doesn(t start with anti falls into one of two cate&ories ( those that compare le)ico&raphically less than anti and those that compare le)ico&raphically &reater than anti! The first of these sets can #e i&nored- #ut how can we filter out words with non1anti prefi)es that compare le)ico&raph1 ically &reater than anti> The trick is to note that if the word doesn't have anti as a prefi)- then somewhere in its first four letters it must disa&ree with anti! 'f we take the ne)t le)ico&raphically1hi&her prefi) than anti Kwhich is formed #y incrementin& the last letterL- we &et ant>! This is the smallest possi#le prefi) any word not startin& #y anti can have! Goreover- every word that starts with anti compares le)ico&raphically less than ant>- and so if we only look at words that compare le)ico&raphically &reater than or e5ual to anti and le)ico1 &raphically less than ant>- we have all of the words that start with anti! Usin& the set's lo'erBbound func1 tion- we can find which words in the set match these criteria efficiently Kin HKl& nL timeL usin& the followin& code=
void <rint:at*hing<refi7es(set string)? le7i*on, string $refi7) { "2 1eAll assume the $refi7 is nonem$t% in this ne7t ste$. 2" string ne7t<refi7 = $refi7; 33ne#tPre i#9ne#tPre i#.siLe!$ - 7:; "2 4om$ute the range to iterate over. 1e store these iterators outside of the 2 loo$ so that 'e donAt have to re*om$ute them ever% time. 2" set string)33iterator end = le#icon.lo6er&bound!ne#tPre i#$; for(set string)33iterator itr = le#icon.lo6er&bound!pre i#$; itr 0= end; !!itr) *out 2itr endl;
STL Algorithms
Problem /) Crintin& out a ve*tor is easy thanks to ostreamBiterator and *o$%! Fecall that an ostreamBiterator is an iterator which prints out the values stored in it to *out- and that the *o$% al&orithm accepts three inputs ( two iterators definin& a ran&e to copy and one iterator representin& the destination ( then copies the input ran&e to the output source! Thus we can print out a ve*tor as follows=
void <rintDe*tor(ve*tor int)? v) { copy!).begin!$B ).end!$B ostream&iterator.int>!coutB " "$$; #
Problem 8! 7y default- if you compare two strin&s to one another usin& - the result is whether the first strin& le)ico&raphically precedes the second strin&! Thus if we have a ve*tor string) called v- callin& sort(v.begin(), v.end()) will sort the input le)ico&raphically! 'n our case- thou&h- we want to 4hack6 the sort function so that it always puts 4Ge 8irstU6 at the front of the sorted orderin&! To do this- we'll write a custom call#ack function that performs a default strin& comparison if neither strin& is 4Ge 8irstU6 #ut which skews the result otherwise! /ere's one implementation=
1 ?1< 1
\ust do a default
"2 4ase three3 one is the 'inner string, t'o isnAt. 2 the sort so that the 'inner string *omes first. 2" if(one == +1inner5tring) return true;
"2 =ther'ise, t'o is the 'inner string and one isnAt 2" return false;
'n the chapter on templates- you'll see how to &eneralize this function to operate over any type of iterators!
1 ?;0 1
C Strings
Problem /! The code m%5tring = .5tring. ! A0A *ust loo*s ri&ht- doesn't it> 't seems like we're concaten1 atin& an e)clamation point on to the end of a strin& and then assi&nin& the new strin& to m%5tring! /ad we #een workin& with C++ string o#*ects- this would #e true- #ut remem#er that .5tring. is a C strin& and that ! means pointer arithmetic rather than concatenation! 'n fact- what this code does is o#tain a pointer to the strin& .5tring. somewhere in memory- then advance that pointer #y the numerical value of the e)clamation point character Kthirty1threeL and store the resultin& pointer in m%5tring! This results in a strin& that's pointin& into random memory we don't own- resultin& in KsurpriseUL undefined #ehavior!
The -re&rocessor
Problem 3) This restriction e)ists #ecause the preprocessor is a com#ile-time construct whereas functions are a runtime construct! That is- the code that you write for a function is e)ecuted only when the pro&ram already runs- and preprocessor directives e)ecute #efore the pro&ram #e&ins runnin& Kor has even finished compilin&- for that matterL! 'f a preprocessor directive were to e)ecute a C++ function- the compiler would need to compile and run that function durin& preprocessin&- which mi&ht cause dependency issues if the function referenced code that hadn't yet #een preprocessed- or would have to defer preprocessin& to runtime- defeatin& the entire purpose of the preprocessor! Problem >) The code for a not1reached macro is actually simpler than that for an assert macro #ecause we don't need to verify any conditions and instead can immediately a#ort! 0e can #e&in #y writin& the code that actually performs the action associated with the not1reached statement=
void 9o4580M6EotCea*hed(string message, int line, string filename) { *err .4580M6EotCea*hed failed3 . message endl; *err .6ine number3 . line endl; *err .&ilename3 . filename endl; abort(); # -define 4580M6EotCea*hed(msg) Do;S7-Y1/otCeached!msgB &&1I/2&&B &&FI12&&$
"e)t- we need to disa#le the macro in case E=B4580M6BE=;CEA4/E9 is defined! This can #e done as follows=
Pi nde /<&;S7-Y1&/<TC2U;'2D
void 9o4580M6EotCea*hed(string message, int line, string filename) { *err .4580M6EotCea*hed failed3 . message endl; *err .6ine number3 . line endl; *err .&ilename3 . filename endl; abort(); # -define 4580M6EotCea*hed(msg) 9o4580M6EotCea*hed(msg, BB6(EEBB, BB&(6EEA:EBB) Pelse Pde ine ;S7-Y1/otCeached!msg$ TR /othing RT Pendi
1 ?;1 1
Problem //! ,ddin& a E=;BAB4=6=C sentinel to the 4olor enumeration is much easier than it sounds! The J Gacro code we have for &eneratin& the 4olor enumeration is currently
enum 4olor { -define 9E&(EEB4=6=C(*olor, o$$osite) colorB "" Eame follo'ed b% *omma -in*lude .*olor.h. -undef 9E&(EEB4=6=C #;
These preprocessor directives e)pand out to the full list of colors with a comma followin& the name of the last color! Thus all we need to do is chan&e the code to look like this=
enum 4olor { -define 9E&(EEB4=6=C(*olor, o$$osite) *olorB "" Eame follo'ed b% *omma -in*lude .*olor.h. -undef 9E&(EEB4=6=C /<T&U&;<1<C #;
"ow- the enum contains a constant called E=;BAB4=6=C that follows all other colors! Problem /;) 0e want to chan&e the definition of the 4olor enumeration so that the names of the colors are pre1 faced with e4olorB! Thus the code we want to &enerate should look like this=
enum 4olor { e4olorBCed, e4olorBGreen, "2 ... 2" e4olorB1hite #;
Fecall that the ori&inal J Gacro code we had for automatically &eneratin& the 4olor enumeration was
enum 4olor { -define 9E&(EEB4=6=C(*olor, o$$osite) colorB "" Eame follo'ed b% *omma -in*lude .*olor.h. -undef 9E&(EEB4=6=C #;
0e can modify this to &enerate the a#ove code #y usin& the token1pastin& operator LL to concatenate e4olorB #efore the name of each of the colors! The resultin& code is
enum 4olor { -define 9E&(EEB4=6=C(*olor, o$$osite) e;olor&PPcolorB -in*lude .*olor.h. -undef 9E&(EEB4=6=C #;
Introduction to Tem&lates
Problem /) *o$%Bif accepts four parameters ( two iterators definin& an input ran&e- one iterator definin& the output ran&e- and a predicate function determinin& whether we should copy a particular element! Since we can provide any sort of input iterator- any sort of output iterator- and a predicate that could theoretically accept any type- we'll templatize *o$%Bif over the types of the ar&uments- as shown here=
1 ?;; 1
template .typename InputIteratorB typename <utputIteratorB typename Predicate> inline <utputIterator *o$%Bif(InputIterator start, InputIterator end, <utputIterator 'here, Predicate fn) { "2 ... 2" #
"ote that althou&h the type of the predicate function depends on the type of iterators Kthat is- if we're iteratin& over strings we can't &ive a function acceptin& an intL- we've templatized the function with respect to the pre1 dicate! This &ives the client more leeway in what predicates they can provide! 8or e)ample- if the iterators iter1 ate over a ve*tor int)- they could provide a predicate function acceptin& a double! ater when we cover functors you'll see a more &eneral reason to templatize the predicate parameter! "ow all that's left to do is write the function #ody! 8ortunately this isn't too tricky ( we *ust keep advancin& the start iterator forward- checkin& if the element iterated over passes the predicate and copyin& the element if ne1 cessary! The final code looks like this=
tem$late t%$ename (n$ut(terator, t%$ename =ut$ut(terator, t%$ename <redi*ate) inline =ut$ut(terator *o$%Bif((n$ut(terator start, (n$ut(terator end, =ut$ut(terator 'here, <redi*ate fn) { or!; start %, end; 33start$ * i ! n!Rstart$$ * R6here , Rstart; 336here; 4 4 return 6here; #
0ritten as
2'here!! = 2start;
This is a trick that relies on the fact that the !! operator #inds more ti&htly than the 2 operator! Thus the code is interpreted as 2('here!!) = 2start- meanin& that we advance the 'here iterator #y one step- then store in the location it use to point at the value of 2start! ' personally find this synta) more attractive than the lon&hand version- #ut it is admittedly more confusin&!
A##endi7 /% ,olutions to Practice Problems Problem ?) Fecall that the code for Get(nteger is as follows=
int Get(nteger() { 'hile(true) { stringstream *onverter(Get6ine()); int result; *onverter )) result; if(0*onverter.fail()) { *har leftover; *onverter )) leftover; if(*onverter.fail()) return result; *out .@ne7$e*ted *hara*ter3 . leftover .<lease enter an integer.. endl; endl;
1 ?;A 1
.Cetr%3 .;
To templatize this function over an ar#itrary type- we need to chan&e the return type- the type of result- and the error messa&e a#out enterin& an inte&er! The modified code is shown here=
template .typename 5alueType> 5alueType GetDalue(string type) { 'hile(true) { stringstream *onverter(Get6ine()); 5alueType result; *onverter )) result; if(0*onverter.fail()) { *har leftover; *onverter )) leftover; if(*onverter.fail()) return result; *out .@ne7$e*ted *hara*ter3 . leftover # else *out *out # # .<lease enter . type endl; endl;
.Cetr%3 .;
const
Problem 3) ,t first it seems like it should #e safe to convert an int 22 into a *onst int 22! ,fter all- we're *ust addin& more *onsts to the pointer- which restricts what we should #e a#le to do with the pointer! /ow could we possi#ly use this to su#vert the type system> The answer is the followin& chain of assi&nments that al1 low us to overwrite a *onst int=
1 ?;B 1
const int my;onstant , 7DE; "" 6egal int R e)ilPtr; "" 6egal, uninitialiUed
"2 ;his ne7t line is not legal 4!! be*ause Se)ilPtr is an intRR and badPtr is a 2 const int RR. 1at*h 'hat ha$$ens if 'e allo' this. 2" const intRR badPtr , Se)ilPtr; "2 9ereferen*e bad<tr and assign it the address of my;onstant. Smy;onstant is a 2 const int R and badPtr is a const intR, so the assignment is legal. /o'ever, 2 sin*e badPtr $oints to e)ilPtr, this assigns e)ilPtr the address of the 2 my;onstant variable, 'hi*h is *onst0 2" RbadPtr , Smy;onstant; "2 ;his 'ould over'rite a *onst variable. 2" Re)ilPtr , J8;
This is a su#tle ed&e case- #ut #ecause it's possi#le C++ e)plicitly disallows it! Problem ?) The initial interface for the CS1067IJ De*tor class is reprinted here=
tem$late t%$ename Elem;%$e) *lass De*tor { $ubli*3 De*tor(int siUe/int = 0); int siUe(); bool isEm$t%(); Elem;%$e getAt(int inde7); void setAt(int inde7, Elem;%$e value); Elem;%$e ? o$erator[](int inde7); void add(Elem;%$e elem); void insertAt(int inde7, Elem;%$e elem); void removeAt(int inde7); void *lear(); void ma$All(void (2fn)(Elem;%$e elem)); tem$late t%$ename 4lient9ata;%$e) void ma$All(void (2fn)(Elem;%$e elem, 4lient9ata;%$e ? data), 4lient9ata;%$e ? data); (terator iterator(); #;
The first task in *onst1correctin& this is markin& non1mutatin& operations *onst! This is reasona#ly strai&ht1 forward for most of the functions! The interestin& case is the o$erator[] function- which returns a reference to the element at a &iven position! This function needs to #e *onst1overloaded since if the De*tor is *onst we want to hand #ack a *onst reference and if the De*tor is non1*onst we want to hand #ack a non,*onst ref1 erence! The updated interface is shown here=
1 ?;? 1
"e)t- we'll update the class #y passin& all appropriate parameters #y reference1to1 *onst rather than #y value! This results in the followin& interface=
tem$late t%$ename Elem;%$e) *lass De*tor { $ubli*3 De*tor(int siUe/int = 0); int siUe() *onst; bool isEm$t%() *onst; Elem;%$e getAt(int inde7) *onst; void setAt(int inde7, const Elem;%$eS value); Elem;%$e ? o$erator[](int inde7); *onst Elem;%$e ? o$erator[](int inde7) *onst; void add(const Elem;%$eS elem); void insertAt(int inde7, const Elem;%$eS elem); void removeAt(int inde7); void *lear(); void ma$All(void (2fn)(const Elem;%$eS elem)) *onst; tem$late t%$ename 4lient9ata;%$e) void ma$All(void (2fn)(const Elem;%$eS elem, 4lient9ata;%$e ? data), 4lient9ata;%$e ? data) *onst; #; (terator iterator() *onst;
1 ?;6 1
8inally- we'll chan&e the return type of getAt to return a *onst reference to the element in the De*tor rather than a full copy- since this reduces the cost of the function! The final version of the De*tor is shown #elow=
tem$late t%$ename Elem;%$e) *lass De*tor { $ubli*3 De*tor(int siUe/int = 0); int siUe() *onst; bool isEm$t%() *onst; const Elem;%$eS getAt(int inde7) *onst; void setAt(int inde7, *onst Elem;%$e? value); Elem;%$e ? o$erator[](int inde7); *onst Elem;%$e ? o$erator[](int inde7) *onst; void add(*onst Elem;%$e? elem); void insertAt(int inde7, *onst Elem;%$e? elem); void removeAt(int inde7); void *lear(); void ma$All(void (2fn)(*onst Elem;%$e? elem)) *onst; tem$late t%$ename 4lient9ata;%$e) void ma$All(void (2fn)(*onst Elem;%$e? elem, 4lient9ata;%$e ? data), 4lient9ata;%$e ? data) *onst; #; (terator iterator() *onst;
static
Problem 1) The @niVuel%(dentified class can #e implemented #y havin& a static varia#le inside the class that tracks the most recently used '2- as well as a non1static varia#le for each instance that tracks the particular class's uni5ue '2! This can #e implemented as follows=
*lass @niVuel%(dentified { $ubli*3 @niVuel%(dentified(); int get@niVue(9() *onst; $rivate3 static int lastUsedID; *onst int *urrent(9; #; "2 Cemember, this must go outside the *lass0 2" int UniGuelyIdenti ied::lastUsedID , 7; @niVuel%(dentified33@niVuel%(dentified() 3 currentID!lastUsedID$ { 33lastUsedID; #
1 ?;@ 1
Con7ersion Constructors
Problem 3! C++ will only apply at most one conversion constructor at a time to avoid &ettin& into am#i&uous situations! 8or e)ample- suppose that we start off with types ,- 7- and C such that , is converti#le to 7 and 7 is converti#le to C! Then we can think of this &raphically as follows=
"ow- suppose we introduce another conversion into this mi)- this time directly from , to C- as shown here=
'f we try to implicitly convert from an , to a C- which se5uence of conversions is correct> 2o we first convert the , to a 7 and then convert that to a C- or *ust directly convert the , to a C> There's no clear ri&ht answer hereand to avoid confusions like this C++ sidesteps the issue #y only applyin& one implicit conversion at a time!
1 ?;E 1
The assi&nment operator doesn't have this pro#lem #ecause if the assi&nment operator were to accept its para1 meter #y value- the copy is made #y the copy constructor- which is an entirely different function!
2&erator 27erloading
Problem ;) The other operators can #e defined as follows=
A A O = O A 0(O 0(A A 0(A O A O A) O NN O O NN O O) A A)
A == O A 0= O A )= O A ) O
Problem 9! 8or reference- here's the #roken interface of the iterator class=
*lass iterator { $ubli*3 bool o$erator== (*onst iterator? other); bool o$erator0= (*onst iterator? other); iterator o$erator!! (); Elem;%$e2 o$erator2 () *onst; Elem;%$e2 o$erator,) () *onst; #;
There are five pro#lems in this implementation! The first two have to do with the declarations of the == and 0= operators! Since these functions don't modify the state of the iterator Kor at least- no sensi#le implementation shouldL- they should #oth #e marked *onst- as shown here=
*lass iterator { $ubli*3 bool o$erator== (*onst iterator? other) const; bool o$erator0= (*onst iterator? other) const; iterator o$erator!! (); Elem;%$e2 o$erator2 () *onst; Elem;%$e2 o$erator,) () *onst; #;
"e)t- take a look at the return type of o$erator2! Femem#er that o$erator2 is called whenever the iterator is dereferenced! Thus if the iterator is pretendin& to point to an element of type Elem;%$e- this function should return an Elem;%$e? Ka reference to the valueL rather than an Elem;%$e2 Ka pointer to the valueL! Htherwise code like this=
2m%(tr = 8FG;
1 ?;< 1
"e)t- look at the return type of o$erator!!! Fecall that o$erator!! is the prefi) !! operator- meanin& that we should #e a#le to write code like this=
!!(!!itr)
To increment the iterator twice! Unfortunately- with the a#ove interface this code compiles #ut does somethin& totally different! Since the return type is iterator- the returned o#*ect is a co#y of the receiver o#*ect rather than the receiver o#*ect itself! Thus the code !!(!!itr) means to increment itr #y one step- then to incre1 ment the tem#orary iterator object #y one step! This isn't at all what we want to do- so we'll fi) this #y hav1 in& o$erator!! return a reference to an iterator- as shown here=
*lass iterator { $ubli*3 bool o$erator== (*onst iterator? other) *onst; bool o$erator0= (*onst iterator? other) *onst; iteratorS o$erator!! (); Elem;%$e? o$erator2 () *onst; Elem;%$e2 o$erator,) () *onst;
#;
0e've now fi)ed four of the five errors- so what's left> The answer's a #it su#tle ( there's nothin& technically wron& with this interface any more- #ut we've left out an important function that makes the interface unintuitive! 'n particular- we've only defined a prefi) o$erator!! function- meanin& that code like !!itr is le&al #ut not code like itr!!! 0e should thus add support for a postfi) o$erator!! function! The final version of the iterator class thus looks like this=
*lass iterator { $ubli*3 bool o$erator== (*onst iterator? other) *onst; bool o$erator0= (*onst iterator? other) *onst; iterator? o$erator!! (); const iterator operator33 !int$; Elem;%$e? o$erator2 () *onst; Elem;%$e2 o$erator,) () *onst; #;
1 ?A0 1
!unctors
Problem /) forBea*h returns the function passed in as a final parameter so that you can pass in a functor that updates internal state durin& the loop and then retrieve the updated functor at the end of the loop! 8or e)amplesuppose that we want to compute the minimum- ma)imum- and avera&e of a ran&e of data in a sin&le pass! Then we could write the followin& functor class=
*lass 9ata5am$le { $ubli*3 TR ;onstructor initialiLes min5al and ma#5al so that they@re al6ays updatedB R sets the total to LeroB and the number o elements to Lero. RT 9ata5am$le() 3 minDal((E;B:AP), ma7Dal((E;B:(E), total(0.0), *ount(0) {# TR Uccessor methods. RT int get:in() *onst { return minDal; # int get:a7() *onst { return ma7Dal; # double getAverage() *onst { return total " *ount; # int getEumElems() *onst { return *ount; # TR operator!$ accepts a piece o void o$erator() (int val) { minDal = min(minDal, val); ma7Dal = ma7(ma7Dal, val); total != val; !!*ount; # $rivate3 int minDal, ma7Dal; double total; int *ount; #; input and updates state appropriately. RT
Then we can write a function which accepts a ran&e of iterators Kpresumed to #e iteratin& over int valuesL and then returns a 9ata5am$le o#*ect which contains a summary of that data=
1 ?A1 1
tem$late t%$ename (n$ut(terator) 9ata5am$le Anal%Ue5am$le((n$ut(terator begin, (n$ut(terator end) { "2 4reate a tem$orar% 9ata5am$le ob>e*t and $ass it through forBea*h. ;his 2 then invo+es o$erator() on*e for ea*h element in the range, resulting in a 2 9ata5am$le 'ith its values set a$$ro$riatel%. 1e then return the result of 2 forBea*h, 'hi*h is the u$dated 9ata5am$le. (snAt that nift%S 2" return or&each!beginB endB DataSample!$$; #
Problem ;) Advan*edOiased5ort is similar to the Oiased5ort e)ample from the chapter on ST al1 &orithms! 'f you'll recall- if we assume that the strin& that should #e the winner is stored in a constant +1inner, 5tring- the followin& comparison function can #e used to #ias the sort so that +1inner5tring always comes in front=
bool Oiased5ort/el$er(*onst string? one, *onst string? t'o) { "2 4ase one3 Eeither string is the 'inner string. \ust do a default 2 *om$arison. 2" if(one 0= +1inner5tring ?? t'o 0= +1inner5tring) return one t'o; "2 4ase t'o3 Ooth strings are the 'inner string. 2 string isnAt less than itself. 2" if(one == +1inner5tring ?? t'o == +1inner5tring) return false; ;hen return false be*ause the
"2 4ase three3 one is the 'inner string, t'o isnAt. 2 the sort. 2" if(one == +1inner5tring) return true;
"2 =ther'ise, t'o is the 'inner string and one isnAt, so return false to bias 2 the sort. 2" return false; #
To update this code so that any strin& can #e set as the winner strin&- we'll need to convert this function into a functor that stores the strin& as a data mem#er! This can #e done as follows=
*lass Oiased5ort/el$er { $ubli*3 e7$li*it Oiased5ort/el$er(*onst string? 'inner) 3 'inner5tring('inner) {# bool operator!$ !const stringS oneB const stringS t6o$ const; $rivate3 string 'inner5tring; #;
1 ?A; 1
bool (iasedSort'elper::operator!$(*onst string? one, *onst string? t'o) *onst { "2 4ase one3 Eeither string is the 'inner string. \ust do a default 2 *om$arison. 2" if(one 0= 6innerString ?? t'o 0= 6innerString) return one t'o; "2 4ase t'o3 Ooth strings are the 'inner string. 2 string isnAt less than itself. 2" if(one == 6innerString ?? t'o == 6innerString) return false; ;hen return false be*ause the
"2 4ase three3 one is the 'inner string, t'o isnAt. 2 the sort. 2" if(one == 6innerString) return true;
"2 =ther'ise, t'o is the 'inner string and one isnAt, so return false to bias 2 the sort. 2" return false; #
Compute the avera&e of the data set! Compute the value of the inner sum! 2ivide the total #y the num#er of elements! Feturn the s5uare root of this value!
0e can use the sVrt function e)ported #y *math) to perform the s5uare root and a**umulate for the rest of the work! 0e'll #e&in #y computin& the avera&e of the data- as shown here=
tem$late t%$ename &or'ard(terator) double 5tandard9eviation(&or'ard(terator begin, &or'ard(terator end) { const ptrdi &t num2lems , distance!beginB end$; const double a)erage , accumulate!beginB endB -.-$ T num2lems; "2 ... 2" #
"ote that we've computed the size of the ran&e usin& the distan*e function- which efficiently returns the num1 #er of elements #etween two iterators! 0e've stored the num#er of elements in a varia#le of type $trdiffBtwhich- as the name su&&ests- is a type desi&ned to hold the distance #etween two pointers! Technically speakin&
1 ?AA 1
we should 5uery the iterator for the type of value returned when computin& the distance #etween the iterators#ut doin& so is #eyond the scope of this te)t! "ow- let's see how we can use a**umulate to evaluate the sum! Fecall that the four1parameter version of a*, *umulate allows us to specify what operation to apply pairwise to the current accumulator and any element of the ran&e! 'n our case- we'll want to add up the sum of the values of K7i ( 7L; for each element- so we'll construct a functor that stores the value of the avera&e and whose o$erator() function takes the accumulator and the current element- computes K7i ( 7L;- then adds it to the current accumulator! This is shown here=
*lass 5tandard9eviation/el$er { $ubli*3 e#plicit StandardDe)iation'elper!double a)erage$ : mean!a)erage$ *4 double operator!$ !double accumulatorB double current5alue$ const * const double e#pr , current5alue ? mean; TT xi - x return accumulator 3 e#pr R e#pr; TT !xi x$8 4 $rivate3 const double mean; #;
Problem G) 'f we were &iven an ar#itrary C strin& and told to set it to the empty strin&- we could do so #y callin& str*$%(m%5tr, ..)- where m%5tr is the strin& varia#le! To do this to every strin& in a ran&e- we can use forBea*h to apply the function everywhere and bindHnd to lock the second parameter of str*$% in place! This is shown here=
tem$late t%$ename &or'ard(tr) void 4learAll5trings(&or'ard(tr begin, &or'ard(tr end) { forBea*h(begin, end, bind8nd!ptr& un!strcpy$B ""$); #
This works #ecause bindHnd($trBfun(str*$%), ..) is a one1parameter function that passes the ar&ument as the first parameter into str*$% with the second parameter locked as the empty strin&! Problem >) "ow that we're dealin& with re&ular C++ strings- we can clear the strin&s #y callin& the .*lear() mem#er function on each of them! Fecallin& that the memBfunBref function transforms a mem#er function into a one1parameter function that calls the specified function on the ar&ument- we can write 4lear, All5trings as follows=
1 ?AB 1
tem$late t%$ename &or'ard(tr) void 4learAll5trings(&or'ard(tr begin, &or'ard(tr end) { forBea*h(begin, end, mem& un&re !Sstring::clear$); #
Problem //) The re$la*eBif al&orithm takes four parameters ( two iterators definin& a ran&e of elements- a predicate function- and a value ( then replaces all elements in the ran&e for which the predicate returns true with the specified value! 'n our case- we're &iven a value as a parameter to the 4a$AtDalue function and want to re1 place elements in the ran&e that compare &reater than the parameter with the parameter! Usin& bindHnd and the greater operator function- we have the followin&=
tem$late t%$ename &or'ard(tr, t%$ename Dalue;%$e) void 4a$AtDalue(&or'ard(tr begin, &or'ard(tr end, Dalue;%$e ma7Dalue) { re$la*eBif(begin, end, bind8nd!greater.5alueType>!$B ma#5alue$, ma7Dalue); #
So that if the 9o5omething function throws an e)ception we're sure to put the element to$Elem #ack on the sta*+ #efore propa&atin& the e)ception! This can #e done as follows=
1 ?A? 1
Problem 3! 0e want to solve the same pro#lem as in the a#ove case- #ut #y usin& F,'' to mana&e the resource instead of manually catchin& and rethrowin& any e)ceptions! 0e'll #e&in #y definin& an Automati*5ta*+, :anager class that takes in a reference to a sta*+- then pops the top and stores the result internally! 0hen the destructor e)ecutes- we'll push the element #ack on! This looks like this=
tem$late t%$ename Elem;%$e) *lass Automati*5ta*+:anager { $ubli*3 e7$li*it Automati*5ta*+:anager(sta*+ Elem;%$e)? s) 3 to:anage(s), to$Elem(s.to$()) { to:anage.$o$(); # ^Automati*5ta*+:anager() { to:anage.$ush(to$Elem); # $rivate3 sta*+ Elem;%$e)? to:anage; *onst Elem;%$e to$Elem; #;
"otice that we've templatized this class with respect to the type of element stored in the sta*+- since there's nothin& special a#out string! This is in &eneral a &ood desi&n philosophy ( if you don't need to specialize your code over a sin&le type- make it a template! 0e can then rewrite the function as follows=
1 ?A6 1
void :ani$ulate5ta*+(sta*+ string)? m%5ta*+) { if(m%5ta*+.em$t%()) thro' invalidBargument(.Em$t% sta*+0.);
"otice how much cleaner and shorter this code is than #efore ( #y havin& o#*ects mana&e our resources we're sure that we won't leak any resources here! True- we had to write a #it of code for Automati*5ta*+:anager#ut provided that we use it in more than one circumstance the savin&s in code simplicity over the manual catch1 and1rethrow approach are impressive!
Introduction to Inheritance
Problem /) 0e don't have to worry that a pointer of type 9o*ument2 points to an o#*ect of concrete type 9o*u, ment #ecause 9o*ument is an a#stract class and thus can't #e instantiated! .ou do not need to worry a#out a pure virtual function call realizin& that there's no actual code to e)ecute since C++ will raise a compile1time er1 ror if you try to instantiate an a#stract class! Problem 8) Hne possi#le implementation for 9erivative&un*tion is shown here=
*lass 9erivative&un*tion3 $ubli* &un*tion { $ubli*3 e7$li*it 9erivative&un*tion(&un*tion2 to4all) 3 fun*tion(to4all) {# virtual double evaluateAt(double 'here) *onst { *onst double +E$silon = 0.00008; "" 5mall a7 return ( unction->e)aluateUt!6here 3 +2psilon$ Q unction->e)aluateUt!6here - +2psilon$) " (H 2 +E$silon); # $rivate3 &un*tion 2*onst fun*tion; #
/ere- the constructor accepts and stores a pointer to an ar#itary &un*tion- and the evaluateAt function in1 vokes the virtual evaluateAt function of the stored function at the proper points to appro)imate the derivative!
,ibliogra&hy
OStr<BP= 7*arne Stroustrup- The esign and E!olution o" C++! ,ddison10esley= 1<<B! OStr0<P= 7*arne Stroustrup! `Stroustrup= 8,N` UF = http=IIwww!research!att!comIi#sI#sVfa5!htmlSdecline! ,ccessed @ $ul ;00<! OSte0@P= ,le)ander Stepanov! `Short /istory of ST ` UF = http=IIwww!stepanovpapers!comIhistoryq;0of q;0ST !pdf! ,ccessed ; $ul ;00<! O'ntelP= 'ntel Corporation! `<6?Vdia&ram!&if` UF = http=IIwww!intel!comIassetsIima&eIdia&ramI<6?Vdia&ram!&if! ,ccessed @ $ul ;00<! OCic<6P= "eil8red Cicciotto! `"eilI8red's 3i&antic ist of Calindromes` UF = http=IIwww!derf!netIpalindromesIold!palindrome!html! ,ccessed 10 $ul ;00<! OStr0<!;P= 7*arne Stroustrup! `Stroustrup= C++` UF = http=IIwww!research!att!comIi#sIC++!html! ,ccessed ;B $un ;00<! OSpo0EP= $oel Spolsky- Moel on ,o"tware% And on i!erse and 5ccasionally Delated -atters That &ill Pro!e o"
Interest to ,o"tware e!elo#ers, esigners, and -anagers, and to Those &ho, &hether by 0ood Fortune or Ill Luc*, &or* with Them in ,ome Ca#acity! ,press= ;00E! O3"UP= 3"U Cro*ect! `strfry` UF = http=IIwww!&nu!or&IsIli#cImanualIhtmlVnodeIstrfry!htmlSstrfry! ,ccessed B ,u& ;00<! OSut<EP= /er# Sutter! `,dvice from the C++ D)perts= 7e Const1Correct` UF = http=IIwww!&otw!caIpu#licationsIadvice<E!htm! ,ccessed 10 $un ;00<! OGCHP= -ars Climate 5rbiter -isha# In!estigation Joard Phase I De#ort! "ovem#er 10- 1<<<! UF = ftp=IIftp!h5!nasa!&ovIpu#IpaoIreportsI1<<<IGCHVreport!pdf OGey0?P= Scott Geyers- E""ecti!e C++% 33 ,#eci"ic &ays to Im#ro!e 4our Programs and esigns, Third Edition! ,ddison10esley= ;00?! OStr0<!AP= 7*arne Stroustrup! `C++0) 8,N` UF = http=IIwww!research!att!comIi#sIC++0)8,N!htmlSthink! ,ccessed 10 $ul ;00<! O2i*6EP= Dds&er 2i*kstra! `3o To Statement Considered /armful!` Communications o" the AC-- Tol! 11- "o! A- Gar 1<6E- p&! 1B@11BE
Inde%
A
abort- 1@;- ;@E uniGue&ptr- BEE
adapta#le functions- A@?- AE1 operator functions- AE0 address1of operator- 1AE assert- 1@0- ;;< assi&nment operators- ;6A copy1and1swap- ;@@ disa#lin&- ;@? for derived classes- B?; implementation of- ;6E return value of- ;@; self1assi&nment- ;@0 auto&ptr- A<E and e)ception1safety- B01
,
7CC - ?
binary& unction- A@@ bind7st- A@E
implementation- AE?
bind8nd- A@E- B16
#itwise e5uivalence- ;@A 7*arne Stroustrup- ? and pure virtual functions- BA@ and the preprocessor- 16@ 7oost C++ li#raries- ;A #usy loop- 6A
C
C strin&sin1memory layout- 1BE strcat- 1?0 strchr- 1?A strcmp- 1?1 strcpy- 1?0 strlen- 1B< strncpy- 1?A strstr- 1?; terminatin& null- 1BE c&str!$- ;6 C++0)- BE1 lam#da e)pressions- BE< rvalue references- BE? siLeo ...- B<B static&assert- B<B type inference- BE1
variadic templates- B<0 Caesar cipher- ?; castin& operators- B?@ const&cast- ?0? dynamic&cast- B?E reinterpret&cast- ?0? static&cast- 6B- 66- B?@- ?0@ catch- A<A catch!...$- A<@ cerr- ;6 cin- ;? cloc+- 6A cloc+&t- 6B ;1<;MS&P2C&S2;- 6A comple#- B;6 const- ;0? #itwise constness- ;11 const and pointers- ;06 const and static- ;?; const mem#er functions- ;0@- ;1@ const references- ;0<- ;16 const1correctness- ;1?- ;E; semantic constness- ;11 conversion constructors- ;??- ;E? copy constructors- ;6A disa#lin&- ;@? for derived classes- B?; implementation of- ;6? copy semantics- A<<- BE? cout- ;? ctime- ;66
9
default ar&uments- ;B1 de ined Kpreprocessor predicateL- 16; delete- 1B; delete9:- 1BB deterministic finite automaton- 100 dimensional analysis- AA1 distance- ?A; dum# pointer- A1?
1 ?B0 1
Inde7
$
e)ception handlin&- A<A and resource leaks- A<? catch1and1rethrow- A<6 resource ac5uisition is initialization- B00 e#plicit- ;?E- ;E? e)ternal polymorphism- B@B
#
macros- 16B dan&ers of- 16? ma+e&pair- <B- 10?- ;AA malloc- ?06 mem& un- A@@ mem& un&re - A@@ mem#er initializer list- ;A@ memory se&ments- 1BE mi)in classes- B?6 monoalpha#etic su#stitution cipher- 1;? move semantics- A<<- BE? mutable- ;1;
!
ree- ?06 riend- A0?
functors- A66 as comparison o#*ects- A@1 as parameters to functions- A6< fundamental theorem of software en&ineerin&- B6?
<
namespace- 1E ne6- 1B; ne69:- 1BB
:
getline- AB- 61- ;1B goto- ?0E
0
hi&her1order pro&rammin&- A@A
nondeterministic finite automaton- 10@ not7- A@< implementation- AE1 not8- A@< /U11- 1B1 numeric&limits- A@A
I
i stream- ;6- ?E- ;0A- ;1A
2
o#*ect construction- ;A@ in derived classes- BB6 o stream- ;6 operator overloadin&- ;<1 as free functions- A0; compound assi&nment operators- ;<@ element selection operator- ;<6- A?; function call operator- A66- B6< &eneral principles- ;<A increment and decrement operators- A0B mathematical operators- A00- AA< mem#er selection operator- A10- A;0 pointer dereference operator- A0<- A;0 relational operators- A0?- A?6 stream insertion operator- A0@ unary minus operator- ;<<
implicit conversions- ;?? include &uard- 16A inheritance- B;< a#stract classes- BA@ #ase classes- BA0 copy functions for derived classes- B?; derived classes- BA0 disallowin& copyin& with- B?; function overrides- BAE is1a relationship- BA0 polymorphism- BA6 private inheritance-- B?A pure virtual functions- BA@ slicin&- B?6 inline functions- 166 in)alid&argument- A<B- B;A isalpha- 1;E ispunct- 11E isspace- A<- 1;<
L
le)ico&raphical orderin&- A?@ lvalue- 1A@- ;<B
palindromes- 1;@ pointers- 1A@ pointer arithmetic- 1?1 pointer assi&nment- 1A< pointer dereference- 1A< )oidR- ?06 principle of least astonishment- ;<;
Inde7
print - ?0B protected- BB0 remo)e©&i - 11E- AE< remo)e&i - 11E- 1;E replace&i - A<0 re)erse- 1;E rotate- 116 set&di erence- 11@ set&intersection- 11@ set&symmetric&di erence- 11@ set&union- 11@ sort- 11?- AE< s6ap- ;@@ s6ap&ranges- B;0 trans orm- 11<- 1;<- ;0;- A@A- B16- B1E
1 ?B1 1
pro)y o#*ects- A?A ptr& un- A@@ implementation- AEB ptrdi &t- ?A;
T
Gsort- ?06
1
rand- 66- ;?B CU/D&0UV- 66
reference countin&- A1? references- 1A6 row1ma*or order- AB6 rule of three- ;6?- ;E6 rvalue- ;<B
S
scan - ?0B
se&mentation fault- 1B< semantic e5uivalence- ;@A si#lin& access- ;6@ Simula- ? sin&leton class- ;@E smart pointer- A1? sGrt- ?A; srand- 66- ;?B standard deviation- AE< static- ;B@ static data mem#ers- ;B@ static mem#er functions- ;?1 ST al&orithms- 11A accumulate- 11A- A@; binary&search- 116 copy- 11E- ;6@- ;EE- B;A count- 1?6 count&i - A6? eGual- 11@- 1;E- ;0; ill- 1;A ind- 116 ind&i - A1A- B1< or&each- 11<- AE< generate- AE< includes- 11@
le#icographical&compare- A?E lo6er&bound- 11@- A@; random&shu le- 116 remo)e- 11E remo)e©- 11E
ST containers- BA deGue- B<- ?6 map- <?- 10B- ;;@ multimap- <6- 10< multiset- <6 Gueue- BB- BA0- BA6 set- <1- 10B stac+- BB )ector- B?- ?B- ;1A- AB6- ?0@ ST iterators- E1 begin!$- E; const&iterator- ;10 definin& ran&es with- E; end!$- EA iterator adapters- E6 bac+&insert&iterator- E6 bac+&inserter- E@ ront&insert&iterator- EE ront&inserter- EE insert&iterator- EE inserter- EE- 11@ istream&iterator- EE- 1A0- ;1A istreambu &iterator- EE- ;0A ostream&iterator- E6- ;EE ostreambu &iterator- EE iterator cate&ories- EB iterator- E1 re)erse&iterator- EB- 1;E stream e)traction operator- ;? stream failure- A; stream insertion operator- ;?
1 ?B; 1 stream manipulators- ;@ boolalpha- A1 dec- A; endl- ;@ he#- A; le t- ;< noboolalpha- A1 oct- A; right- ;< set ill- A0 set6- ;<- A1 stringstream- A?- 1A0 synta) su&ar- ;<A system- 6B
, -- BAB virtual function ta#le- BBA- B@B
Inde7
D
J Gacro trick- 1@A
.
zero1overhead principle- 1AA
&&DUT2&&- 16< &&FI12&&- 16< &&1I/2&&- 16< &&TI02&&- 16< W: operator- @0- 166 P Kstrin&izin& operatorL- 16< PP Ktoken1pastin& operatorL- 1@0 Pde ine- 1?E
T
templates- 1EA implicit interface- 1<E inte&er template ar&uments- AAE template classes- 1EA mem#er functions of- 1E? template functions- 1<? template mem#er functions- 1<< of template classes- ;00 type inference- 1<?- AEB- B@; typename- 1EA- ;00 temporary o#*ect synta)- ;EE- ABE- A6< this- ;B< and assi&nment operators- ;@1 and static mem#er functions- ;?1 as an invisi#le parameter- ;?0 thro6- A<A lone thro6 statement- A<@ time- 66- ;?B time&t- ;66 tolo6er- 11< try- A<A typede - <1 typede struct- ?1;
E
unary& unction- A@6
undefined #ehavior- 1BA union1find data structure- ;;? using namespace std- 1@
H
)alarray- A1A
Ti&en\re cipher- ?; )irtual- BAB in constructors and destructors- BB< virtual destructors- BB1