Software Development with C++ Templates Lab Submission 1 Exercises should be solved in groups of two. However, with approval from the lecturer, exercises may also be solved alone or in groups of three. Groups of three students will face more stringent requirements on code readability, for instance, the code has to be perfectly structured and robust (i.e., error handling, etc). Each exercise should be implemented together with a small program testing the individual functions implemented for the exercise. Those test cases should check whether the code correctly handles errors, such as division by zero, etc. Several exercises build on top of each other. Before continuing to implement the successor, please create a copy of the previous example, so that each step can be shown during the exercise submission. If the code is not self-explanatory, it should be commented. For instance, a single variable assignment in the constructor or the beginning of a function should not be commented with initialization. On the other hand, if the structure of a function is not clear and cannot be split up into self-speaking sub-functions, a short comment should be inserted indicating the individual phases. Use a consistent coding style at least within a given exercise, preferably across all of your exercises. Exercise 1.1: Implement Hello World Implement a program that displays Hello World. Exercise 1.2: Fraction Numbers Implement a fraction data type that provides the following operations. These operations should be implemented by overloading the respective C++ arithmetic operators ( +, -, *, and / ) as well as input and output routines. Ensure that all fractions are presented in normalized form. Implement two small test driver that shows the operation of your fraction class and interactively allows to test the different operations. Exercise 2.1: Persistent Vector Implement a persistent vector data type (pvector) similar to the one presented in the lecture using templates. Use the persistent vector in combination with different data types such as integers, floating point numbers, different types of strings, etc. What do you observe for the different data types? Explain the behavior. Implement a set of small demo programs. Exercise 2.2: Inline Implement a program that shows a function that cannot be inlined.
Exercise 2.3: RPN Implement a reverse polish notation calculator. An RPN calculator is implemented by using a stack. When the user enters the command n followed by a number, the number is pushed onto the stack. The stack may be indefinitely deep. If the user enters a binary mathematical operator, two numbers are taken from the stack, the operation applied and the result pushed back onto the stack. Use the std::vector class as the implementation of your stack. When the user enters the command d, a number is popped from the stack and discarded. When the user enters the command q the RPN calculator is terminated. Commands may be entered in any order. The following shows how the application should run (user input in bold): Command: n 2 Command: n 4 2: 4 Command: n 3 2: 4 3: 3 Command: * 2: 12 Command: - 1: -10 Command: d Command: q In order to provide the user interface, read the user input as full line (e.g. by using getline) and then using an istringstream for parsing that line. Exercise 2.4: RPN Calculator with Templates Extend the RPN calculator from exercise 3.3 to support templates. It should be possible to instantiate the RPN calculators with at least the following data types: int, double, fraction. Once instantiated in a particular form all calculations are performed in that type. It is sufficient if the data type to be used is set in the source code before the code is compiled. Ensure that the data type only has to be changed in a single line within your source code. Also add the following functions to your RPN calculator: m that computes the minimum of the top two numbers of the stack. all+, all* to compute the sum/product of all the numbers stored on the stack. Use a Standard Library algorithm for this function. This is only necessary for groups of three, for other groups, this exercise is optional. Exercise 2.5: RPN Calculator with Persistent Vector Extend your RPN calculator to make use of the persistent vector data type. That is, when the user terminates the RPN calculators with numbers left on the stack, they should reappear when the calculator is started again.
Exercise 3.1: Persistent Vector with Traits Adapt the implementation of your persistent vector to make use of traits like presented in the lecture. Adapt the demo programs from the previous exercises. Implement a persistent set (pset) similar to the persistent vector data type which uses the same traits for persisting its elements. Exercise 3.2: Spell Checker Implement a simple spell checker. The spell checker takes two files as command line arguments; the first being the filename of a dictionary containing a list of correctly spelled words (argv[1]) and the second being the filename of the file whose content is to be checked (argv[2]). Upon start, your program stores the words contained in the dictionary file in a pset<string>. Next, it reads the word in the file to be spell-checked and checks for each word whether it is spelled correctly (i.e., contained in the dictionary file) and if not, displays it. Additionally, your program should allow the user to correct them or insert them into the dictionary if spelled correctly but not in the dictionary. Given the dictionary file dict.txt: correctly spelled words are stored in this dictionary file And the following file text.txt: A comprehensive dictionary is important. The invocation with spell dict.txt text.xt would produce the following output: A (a)dd, (i)gnore, (r)eplace? a comprehensive (a)dd, (i)gnore, (r)eplace? i is (a)dd, (i)gnore, (r)eplace? a important (a)dd, (i)gnore, (r)eplace? i Added words should be contained in the dictionary afterwards, if words are replaced, they should be checked again. The corrected file should be written to argv[2] with.corrected appended to it. Exercise 3.3: Use std::for_each in Your RPN Calculator Change your RPN calculator such that the minimum function computes the minimum of all the numbers stored on the stack. Make use of the std::for_each routine. Exercise 3.4: Template-Based Connect 4 Implement a template based version of the Connect 4 game. Connect 4 builds on a playing field composed out of 7 columns each having 6 rows. When a player puts a stone into a column, gravity pulls the stone towards the lowest unoccupied row. The player who first has 4 stones in a row (horizontally, vertically, or diagonally) wins. After each turn display the game field using simple ASCII graphics. Implement the game in such a way that players can be exchanged easily using templates. To facilitate this, implement a playfield class that is based on the following playfield
skeleton. You may extend these classes but you are not allowed to modify any element that is provided by these templates. class playfield { // the size of the field const static int width=7; const static int height=6; // these elements are used to indicate whether a given position // in the playing field is taken by a given player const static int none=0; const static int player1=1; const static int player2=2; protected: // the internal representation of the field char rep[playfield::width][playfield::height]; // return the stone (none/player1/player2) at the position(x,y) // 0 <= x <= width // 0 <= y <= height // stoneat(0,0)... top left // stoneat(width-1,height-1)... bottom right // if we insert a stone in a new game in column i, // it lands at (i,height-1) // implementation may be changed, interface not int stoneat(int x, int y) const { return rep[x][y]; ; In the next exercise, we will add a computer player and let different players compete against each other. Exercise 3.5: Template-Based Connect 4 with Computer Player Implement a computer player. The computer player of this version does not have to be intelligent. At a minimum, however, the computer player should be able to identify whether he can win the game by placing a stone. Let your computer player compete against computer players from your colleagues. Think of one more optimization and add it as well. Groups of 3 students should think of and implement add at least two more optimizations. In order to be able to let computer players from different students, use the following templates to implement your player: template<typename F> class player { // returns the column where the player decides to throw in his // stone // F is the playfield which may be any playfield implementing // the stoneat method, if you expect a different class because // you need methods to verify whether the opponent can win, // copy the field into the class that you expect. int play(const F &field); ; Make sure that your player class is self-contained and does not require functions from your playfield, etc. If you need shared functions, you can put them into an extra class that you ship with the player.
Exercise 4.1: Merging STL Containers Implement a function that merges two STL containers. Think about whether you want to merge containers of different types (vector, list, set, map, etc.) or only containers of the same type. Look into the concepts we have learned so far, depending on how you want to implement this algorithm, they could be useful. Explain your design decisions, such as how to represent the containers (container, sequence, etc.), how to add the elements to the target container, and how to map fundamentally different containers (lists and maps). Exercise 4.2: find_if in Java and in C++ Implement a function findif(iterator iter, Matcher matcher) in Java that finds the first element in the sequence defined by iter. Matcher is an interface with a single method match that returns true if the element matches. public static interface Matcher<E> { boolean match(e elem); Implement in a separate Java file a benchmark that executes the above on a Vector with several millions of elements. Implement the same benchmark in C++ with the C++ find_if method and, e.g., lambdas. Try to optimize BOTH (Java and C++) solutions. However, the matcher and the elements stored in the container shall be generic. Exercise 4.3: Declaration vs. Definition Which of the following lines belong into a header file? Explain why. char ch; string s; extern int error_number; static double sq(double); int count=1; const double pi=3.2; // according to Indiana Pi Bill struct fraction { int c; int d; ; char *prog[]={"echo","hello","world!",null; extern "C" void c_swap(int *a, int *b); double sqrt(double); void swap(int &a, int &b) { int c=a; a=b; b=c; template<typename T> T add(t a, T b) { return a+b; namespace { int a; struct user; Exercise 4.4: Separate Compilation Update all the exercises you implemented so far and split them into multiple compilation units (i.e., files) where you believe it makes sense. For instance the fraction class is a perfect candidate to put into a separate compilation unit.
Exercise 4.5: What Does This Program Do What could be the purpose of the following code? Where could it be useful? Implement a program that demonstrates its use. template <class BinOp, class Op1, class Op2> class somefunction_t : public unary_function<typename Op1::argument_type, typename BinOp::result_type> { protected: BinOp o; Op1 o1; Op2 o2; somefunction_t(binop binop, Op1 op1, Op2 op2) : o(binop), o1(op1), o2(op2) { typename BinOp::result_type operator()(const typename Op1::argument_type &x) { return o(o1(x),o2(x)); ; Exercise 5.1: dumb_pointer & smart_pointer Implement a dumb_pointer. Use the operator* and operator-> operators to implement a class that simulates a pointer Implement another class that works like the dumb_pointer but is called smart_pointer and uses reference counting. Implementing the smart_pointer on the basis of the C++11 shared_ptr is not a legal implementation choice. The following shows a code snippet of how the smart_pointer should behave: void print(smart_pointer<object> p) { cout << p.counter() << ": " << *p << endl; void foo() { Object *o1=new Object(1); Object *o2=new Object(2); // let's create our 1st object // let's create our 2nd object smart_pointer<object> p(o1); // ref counter is 1 for 1st object cout << p.counter() << endl; // displays 1 // another smart pointer pointing to o1 (overload copy constructor) smart_pointer<object> q(p); // counter() returns how many smart pointers refer to the object cout << p.counter() << endl; // displays 2 cout << q.counter() << endl; // displays 2 smart_pointer<object> r(o2); // ref counter is 1 for 2nd object cout << r.counter() << endl; // displays 1 // overload the assignment operator q=r; // decrease counter for 1st object and // increase counter for 2nd object cout << p.counter() << endl; // displays 1 cout << q.counter() << endl; // displays 2 cout << r.counter() << endl; // displays 2 print(p); // displays 2, and the 1st object print(q); // displays 3, and the 2nd object print(r); // displays 3, and the 2nd object // ensure that the above 3 functions, don t delete the objects
// overload operator* cout << *p << *r << endl; // display 1st and 2nd object // overload operator-> cout << p->method1() << q->method2() << r->method3() << endl; // invoke method1 on 1st object and // invoke method2 on 2nd object and // invoke method3 on 2nd object // now the destructors of p, q, and r are called, make sure that 1st // and 2nd object is each deleted once // (i.e., when the counter reaches 0) Exercise 5.2: C++ Under the Hood Have a look at the following two swap implementations and let the compiler generate assembly code for each of them. void swap(int &a, int &b) { void c_swap(int *a, int *b) { int c=a; a=b; b=c; int c=*a; *a=*b; *b=c; Compare the assembly code, what do you observe? How do you interpret the difference? Implement the comprehensive function sample from the first lecture twice. Once with the swap and lcm functions declared inline Once with those functions declared as normal functions Compile the two programs and generate assembly code. How many instructions do the different functions have for the two different versions of the example? How do you interpret the differences? Exercise 5.3: Connect 4 Interoperability Improve the computer player of your Connect 4 implementation and make sure you can accommodate computer players from your colleagues. Get at least 2 computer players from other colleagues and see who is better.