CS 509, Advanced Programming II

Spring 2003 - Test 2


  1. Suppose function f() has the prototype

    void f(std::vector<int>);
    

    From the point of view of the code calling f(), which of the following prototypes

    1. void f1(const std::vector<int> &);

    2. void f2(std::vector<int> &);

    are equivalent to f()'s prototype? Explain your answer, which should be given in terms of behavior; that is, which of the two choices behaves most like f()?


    From the point of view of the calling code, f1() behaves like f(). Both f1() and f2() pass their argument as a reference, but only f1() passes the argument as a const reference, which prevents the body of f1() from making any changes to the argument. f2() may change the argument, and because the argument is passed by reference, those changes will be visible to the f2()'s caller.

    f() passes its argument by value, which means that, even though f()'s body can change the argument, those changes are not visible to f()'s caller.

    See Koenig and Moo Section 4.1.3 for more details.

    Many people got this question wrong because they didn't read the question carefully. It is true that f2() is more like f() because both function bodies can change the argument vector, but that point of view is with respect to the body of the called function. The question asked about behavior with respect to the calling function, and from that point of view the important characteristic is that the actual parameter can't change.


  2. Consider the following incomplete try-catch statement:

    try { throw X; }
    /* catch clauses here */
    

    Given the class declarations

    class blob { /* whatever */ };
    
    class blue_blob : blob { /* whatever */ };
    
    class light_blue_blob : blue_blob { /* whatever */ };
    

    Write the missing catch clauses to provide the following behavior when the try-catch statement is executed:

    1. If X is a blue-blob, the try-catch statement prints Caught a blob.

    2. If X is a light-blue-blob, the try-catch statement prints Caught a light_blue_blob.

    Your catch clauses should be of the form

    catch (X) { std::cout << "Caught a X.\n"; }
    

    Don't forget to explain why your answer's correct.


    A light-blue blob is at the bottom of the inheritance hierarchy. If it's going to be explicitly caught - as it needs to be to print the light-blue blob message - its catch clause must come before any of the other clauses.

    Because a blue blob is a child of a blob, a blob catch clause will catch a thrown blue blob, resulting in the required blob message.

    The resulting try-catch clause is

    try { throw X; }
    catch (light_blue_blob) { std::cout << "Caught a light_blue_blob.\n"; }
    catch (blob)            { std::cout << "Caught a blob.\n"; }
    

    A number of people mis-interpreted this question as asking for a separate try-catch statement for each of conditions 1 and 2. Apart from the fact that the question starts off asking you to add catch clauses to a given try statement, writing one try-catch statement per condition would also make an already easy question even easier. A three or so of the people who got this question right had answers like

    catch (light-blue-blob) { ... }
    catch (blob) { ... }
    catch (blue-blob) { ... }
    

    This is sloppy programming: because a blue-blob is a child of blob, a blue-blob exception will never make it past the blob catch-clause.


  3. Describe an input test case for the second assignment that determines if a solution to the assignment correctly handles the property that addition is associative. Don't forget to describe the expected output from the test case.


    A sequence of associative operators may be parenthesized in any way (or not parenthesized at all) without changing the final value. For example, addition is associative and the following sums are all equal:

    a + b + c = (a + b) + c = a + (b + c)
    

    The simplest test case (you must have at least three operands to test for associativity) would be to implement the final two sums above:

    move  r0  a     move  r0  b
    move  r1  b	move  r1  c
    add   r0 r1	add   r0 r1
    move  r1  c     move  r1  a
    add   ro r1	add   ro r1
    move   d ro	move   d ro
    

    Two or three people got this question right. The most common way to incorrectly answer this question was to present a test case for commutativity.


  4. Explain how the following for loop can fail

    assert(X);
    
    for (int i = v.size(); i >= 0; i -= 2)
      if (v[i - 1] == v[i - 2])
        cnt++;
    

    and what the expression X should be to explode the assertion before control reaches the for loop in those cases when the for loop would fail.


    The loop can fail when the vector indices are out of bounds. Because i starts high and is reduced, we have to worry about the left end of the vector. The first index is out of bounds whenever i - 1 is less than zero i - 1 < 0 or i < 1. The second index is out of bounds whenever i - 2 is less than zero i - 2 < 0 or i < 2.

    The indices will be out of bounds when either i < 1 or i < 2. Because i < 2 is always true when i < 1 is, we need only check i < 2 for trouble. If i < 2 is false, that is i >= 2 is true, it would seem the loop executes without failure. i gets its initial value from v.size(); letting X represent v.size() >= 2 in the assertion would appear to keep the loop safe by exploding when v has less than two elements.

    Unfortunately, the value of i changes with each loop iteration, and in the last iteration i is less than two, violation of the safe access property i >= 2 derived above. This occurs whenever the loop executes, and because the loop executes whenever s.size() >= 2, we have to change our assertion to explode in those cases by adding the additional test s.size() < 2 to X.

    The final assertion is

    assert((v.size() >= 2) and (v.size() < 2))
    

    But, looking at the assertion, v.size() can't be both less than and at least 2 at the same time; this assertion is effectively equal to

    assert(false)
    

    That is, the assertion that always explodes. This suggests that something is wrong with the loop, because it can never execute without exploding. And sure enough, the loop guard i >= 0 lets the index drop below 2, which we showed above was a bad idea.

    Many answers got the less-than-two part right, but then stopped without noticing that i changes value (that is, i isn't invariant) during the loop. Loops are hard because they loop; unless you know that the loop does on every loop, you don't really understand the loop.



This page last modified on 26 February 2003.