CS60 Lecture 22 ------------------ Announcements ---------------- - no jury duty - hw15 posted - class friday cancelled Today ------------------ - Review - C++ Exception Handling - n-ary trees Review ------------------- - templates are one way to make generic code - instead of writing functions that are directly compiled into code, we write 'templates' with placeholders for types. - compiler generates type specific code for each template type used templace class foo { private: T data; public: T getfoo(); }; template T foo::getfoo() { return(foo); } foo myfoo; - really should only use if we are designing an object that can operate on built in types - other way, more OO, is to use inheritance and virtual functions C++ Exceptions ----------------- - Exceptions are usually thought of as a way to handle errors, but are named 'exceptions' because they are really designed to handle 'exceptional cases' - in C we have to use control structures like if/else to handle unexpected cases, good enough mostly, somtimes gets nasty ex: int a; cin >> a; for(i=0;i<1000;i++) { for(j=0;j<1000;j++) { for(k=0;k<1000;k++) { // error in here if user input '10' and when j=10 b = (k*i) / (a-j); } // more code } // more code } - so to check if ((a-j) == 0) { cout << "divide by zero" << endl; done=1; break; } - in jloop after kloop if (done) { break; } - in iloop after jloop if (done) { break; } - These cases happen! especially in programs that accept input from nondeterministic sources (network, devices, user, etc) - C programmers sometimes default to using GOTO! Considered extremely bad programming style. if (blah) { goto error; } exit(0); error: printf("hmmm\n"); - YIKES - C++ has improved the wheel by giving us exceptions try { for(i=0;i<1000;i++) { for(j=0;j<1000;j++) { for(k=0;k<1000;k++) { // error in here if user input '10' and when j=10 if ((a-j) == 0) { throw j; } b = (k*i) / (a-j); } // more code } // more code } } catch (int val) { cout "DIVbyzero at j=" << val << endl; } - as we can see, this looks a bit cleaner than our C solution (and less code, more efficient!) - can throw exactly one parameter to be caught - programmers can design objects to throw around class divbyzero { public: string message; divbyzero(string inmess) {message = inmess;}; }; try { if (blah) { throw divbyzero("blah was zero!"); } } catch (divbyzero err) { cout << err.message << endl; } - If we are designing error objects, we usually want to give them informative names, why? - two reasons 1.) we can have multiple 'catch' clauses, and a default try { // } catch (int a) { } catch (double b) { } catch (divbyzero c) { } catch (...) { //default } - note that order is important, will go through one catch clause ata time until one matches (default has to be last, then). - note that with 'throw/catch' no automatic typechecking happens. if you throw a double and only have a catch for an int, it is not converted 2.) real usefulness of exceptions: throwing from functions int c; main() { try { foofunc() } catch (divbyzero err) { cout << err.message << endl; } } void foofunc () { int a, b; // do somthing nondeterministic to init a and b if (b == 0) { throw divbyzero("foofunc: b was zero doh"); } c = a/b; // etc } - if you are designing a library, you can include exception objects along with documentation, the user can then decide how to handle (or not to handle) thrown exceptions Discussion ------------------- - why is this useful? a.) leaping out of nested loops - C CAN use goto, but shouldn't - C++ CAN use exceptions - normally this means code is designed poorly b.) can pass information out of statement block - in C you could have variables set aside for error info - in C++ you can throw a type and have a special clause for handling that error: more efficient c.) can throw exceptions out of functions - we don't have to design return code == error code style functions anymore - model is to write functions that work in the ideal case - afterwards, put in error checking byt throwing exceptions out of the function - programmer USING the function can decide how to handle various exceptions ex: - string functions for instance in C char *f f = strdup("hello"); - 'f' has two functions now, it is supposed to be a string but is also carrying error code information from stdrup, also can only carry ONE type of error (NULL) - C has dealt with this b using errno which is part of the C library. - global errno var is set inside function, outside we can decode using if - C++ has mechanism built into language - in C++, if we wrote our own strdup function char *f; try { f = strdup("hello"); } catch(outofmem err) { } catch(emptystring err) { } catch(nullparam err) { } catch(...) { } Trees -------------------- - tree is a very useful data structure - binary, ternary, n-ary - usually created using recursive routine class node { public: int data; node *rchild, *lchild; }; addnode(int data, node *&child) { if (child == NULL) { child = new node(data); return(child); } else if (data < child->data) { return(addnode(data, child->lchild)); } else { return(addnode(data, child->rchild)); } } main() { node *root; addnode(10, root); addnode(15, root); addnode(4, root); addnode(8, root); } - tree created looks like: 10 / \ 4 15 \ 8 - cool thing is, the numbers are now implicitly ordered! left/center/right recursive routine will give you a sorted list - printnode(node) - if node->lchild and node->rchild == NULL, print data - else - printnode(node->lcihld) - print data - printnode(node->rchild) - Other kinds of trees - proj5 for instance uses a tree, but not really as a 'data' mechanism but more as an 'algorthm' mecahnism. - root node, six children representing all moves from root node - each of those has six children, each representing player moves - how would you insert? from main: { node *root = NULL; int **boardstate; // init boardstate somehow buildtree(boardstate, root) } buildtree(int **data, node *&child) if (child == NULL) child = new node(data) } // am i player of computer? me = one or the other // for each column for i = 0 to 5 // make the move as if i chose column i me make move on column i // set 'i'th child to a new node with new boardstate child->children[i] = new node(newmove) // did i win? did player win? mark the current tree depth // build a new tree starting from the child i just created buildtree(newmove, child->children[i]) - NOTE: you cannot enumerate all possible board states on 6x6 board - 3x3 had around 10k states - 4x4 had > 20 million states - approx 3 orders of magnitude, 6x6 might be ~50 trillion - pruning the tree - first of all, we're checking for wins every time right? board states don't need to go beyond a win state - that helps but still too many - is it reasonable to look 20 moves into the future? what is the probability of landing in that state? 1/number of states on the way, unlikely - can set the depth of the tree to decide how many moves in the future to examine - my computer (GHz G4) can do about 6 future moves in a 'reasonable' amount of time