ICS 45C Fall 2021
Exercise Set 7

Due date and time: Wednesday, November 24, 11:59pm


Getting started

First of all, be sure that you've read the Reinforcement Exercises page, which explains everything you'll need to know, generally, about how the reinforcement exercises will work this quarter. Make sure you read that page before you continue with this one.

Before you begin work on these reinforcement exercises, there's a chore that you'll need to complete on your ICS 45C VM to get it set up to proceed.

Refreshing your ICS 45C VM environment

Even if you previously downloaded your ICS 45C VM, you will probably need to refresh its environment before proceeding with these exercises. Log into your VM and issue the command ics45c version to see what version of the ICS 45C environment you currently have stored on your VM. Note, in particular, the timestamp; if you see a version with a timestamp older than the one listed below, you'll need to refresh your environment by running the command ics45c refresh to download the latest one before you proceed with these exercises.

2021-11-16 10:09:42
Exercise Set 7 template added

If you're unable to get outgoing network access to work on the ICS 45C VM — something that afflicts a handful of students each quarter — then the ics45c refresh command won't work, but an alternative approach is to download the latest environment from the link below, then to upload the file on to your ICS 45C VM using SCP. (See the Project #0 write-up for more details on using SCP.) Once the file is on your VM, you can run the command ics45c refresh_local NAME_OF_ENVIRONMENT_FILE, replacing NAME_OF_ENVIRONMENT_FILE with the name of the file you uploaded; note that you'd need to be in the same directory where the file is when you run the command.

Creating your project directory on your ICS 45C VM

A project template has been created specifically for this set of exercises, containing a similar structure to the basic template you saw in Project #0. Among other things, it contains a version of the gather script that's different from the ones in the projects, so you'll absolutely need to use the set7 template for these exercises, as opposed to the basic one.

Decide on a name for your project directory, then issue the command ics45c start YOUR_CHOSEN_PROJECT_NAME set7 to create your new project directory using the set7 template. (For example, if you wanted to call your project directory set7, you would issue the command ics45c start set7 set7.)


Problem 1 (2 points)

We saw that Abstract Base Classes have limitations, compared to the usual "concrete" classes that have no pure virtual functions. The most notable of these limitations is that you can't create an object of an abstract base class. Like most rules in C++, this rule about abstract base classes is motivated by something; there's a reason we can't have objects of abstract base classes, and one way to understand that reason is to consider what would happen if we could.

So, suppose that we had the following declaration of an abstract base class, which is similar to the one we saw in the Abstract Base Classes lecture.

class Shape
{
public:
    double getXCoordinate() const;
    double getYCoordinate() const;
    Color color() const;
    virtual double area() const = 0;
    virtual bool contains(Point p) const = 0;
};

You can further assume that Color and Point are structs, with a Color representing a color in the RGB (red/green/blue) color space and Point representing a coordinate in the two-dimensional Cartesian plane.

struct Color
{
    unsigned int red;
    unsigned int green;
    unsigned int blue;
};

struct Point
{
    double x;
    double y;
};

Now, let's suppose that we were writing the following function. Note that this is a legal function declaration by the rules of C++; even if Shape is an abstract base class, it's not illegal to have a Shape*.

void foo(Shape* s)

And, finally, let's suppose that s was actually pointing to a Shape object — this part is ordinarily illegal in C++, but we're exploring why, so let's imagine it's legal, then consider what the effect of that change to C++ would be.

  1. Would you expect it to be possible to interact with *s at all, even if it pointed to a Shape object?
    • If so, write a body of the foo function that demonstrates what you could potentially do without it being a problem.
    • If not, briefly explain (in no more than a sentence of two) why no interaction with *s could possibly be legal.
  2. Are there any interactions with *s that you would expect to be problematic if s pointed to a Shape object?
    • If so, write a body of the foo function that demonstrates what might be illegal, along with a brief explanation (in no more than a sentence or two) of why, and what kind of error, compile- or run-time, you might expect to see.
    • If not, briefly explain (in no more than a sentence or two) why any interaction with *s would be legal, and why, in that case, it should be illegal in C++ to have a Shape object.

What to submit

Add one PDF file to your problems directory with this name: problem1.pdf, which contains your answers to these two questions.


Problem 2 (2 points)

When we discussed Type Conversions, we saw that we need to be cognizant of the fact that type conversions, both implicit and explicitly, exist in C++, because both have an impact on the design of our classes. For example, some constructors can be marked explicit, which is a technique we sometimes want (and sometimes don't); it's important, though, that we can tell the difference, in practice, between when we do and don't want it. Let's apply this idea to a couple of examples, so we can be sure we understand when we should apply it.

  1. Suppose you had a class called StudentId, whose job is to encapsulate the idea of a student ID number, while forbidding it from being invalid. We'll say, for the sake of argument, that student ID numbers are seven digits and must be in the range 1111111..9999999. An attempt to construct a StudentId from a value out of this range would result in an exception being thrown.
    class StudentId
    {
    public:
        StudentId(unsigned int id);
        unsigned int toUnsignedInt() const;
    
    private:
        unsigned int id;
    };
    

    (You might wonder what value such a class would be; why not just store it as an unsigned int? The answer is that we now have a type that represents something more specific: It's an unsigned int that we know is in range or it can't possibly exist.)

    Would you suggest marking the StudentId constructor explicit? In a sentence or two, briefly explain why or why not.

  2. Suppose you had a class called Complex, whose objects represent the mathematical notion of a complex number. Recall that a complex number is one that satisfies the formula a + bi, where a and b are real numbers and i is the square root of -1. Of course, C++ provides no implementation of a "real number" in the mathematical sense, but the built-in type double makes for a reasonable approximation, which might lead us to a design like this one.
    class Complex
    {
    public:
        Complex();
        Complex(double real, double imaginary = 0.0);
        Complex(const Complex& other);
    
    private:
        double real;
        double imaginary;
    };
    

    Which of these three Complex constructors would you suggest marking as explicit? In a sentence or two, briefly explain why you made your choice.

What to submit

Add one PDF file to your problems directory with this name: problem2.pdf, which contains your answers to these two questions.


Problem 3 (2 points)

Suppose you had the following C++ class. (Note that it's not important what problem the class solves, nor what the meanings of its member variables are.)

class X
{
private:
    int a;
    static int b;
};
  1. Suppose you added a public, static member function called itIsStatic and wrote this line of code in it.
    std::cout << a;
    
    Would you expect it to be possible for this line of code to compile? Briefly explain why or why not.
  2. Suppose you added a public, non-static member function called itIsNotStatic and wrote this line of code in it.
    std::cout << b;
    
    Would you expect it to be possible for this line of code to compile? Briefly explain why or why not.

Note that you can safely assume that #include <iostream> appears at the top of the same source file in which these member functions are written.

What to submit

Add one PDF file to your problems directory with this name: problem3.pdf, which contains your answers to these two questions.


Problem 4 (1 point)

In a previous week, when we first learned about Well-Behaved Classes, we explored that concept by writing an ArrayList class whose objects act as an array-based list, not entirely unlike the std::vector type in the C++ Standard Library (or the list type in Python), albeit with a set of functionality that's more sparse.

As we discussed this week in our conversation about Contracts and Exceptions, when we design classes, there's more to the story than just writing code; we're designing a tool that's intended to be used elsewhere, so it's important that we carefully consider the way that tool will interact with the code that uses it. For that purpose, we consider the contracts associated with it.

Let's think about how our conversation about contracts applies to the previously-seen ArrayList example from the Well-Behaved Classes notes, by answering a couple of questions about it.

  1. What are the class invariants for the ArrayList class that we wrote in that earlier example? Be clear about what the invariants are, but there's no deep explanation about "why" required here.
  2. What are the postconditions for the ArrayList::add member function? Be clear about what the postconditions are, but there's no deep explanation about "why" required here.

What to submit

Add one PDF file to your problems directory with this name: problem4.pdf, which contains your answers to these two questions.


Problem 5 (3 points)

In our conversation about Contracts and Exceptions, we discussed a concept called exception safety and established the idea that once we realize that exceptions can occur in C++, we must also embrace how profoundly that impacts the design of our classes. To explore that concept, let's apply it to the ArrayList example that we wrote when we talked about Well-Behaved Classes, by rewriting it to take exception safety into account.

Create a C++ header file problems/problem5.hpp and a C++ source file problems/problem5.cpp.

In the header file, you'll write the declaration of your rewritten ArrayList class. In the source file, implement your rewritten ArrayList member functions. Your ArrayList class must have all of the same member functions as the provided version from the Well-Behaved Classes and must also meet two addiitonal requirements.

You can, of course, leave things unchanged in places where you don't think a change is required; we're not asking you to write the class differently except where it needs to be. Comments are not required anywhere in your submission.

What to submit

One C++ header file named problem5.hpp and one C++ source file named problem5.cpp, which you'll need to create in the problems directory, and which contain your declaration and implementation of your rewritten ArrayList class.


Deliverables

In Canvas, you'll find a separate submission area for each problem. Submit your solution to each problem into the appropriate submission area. Be sure that you're submitting the correct file into the correct area (i.e., submitting your Problem 1 solution to the area for Problem 1, and so on). Under no circumstances will we offer credit for files submited in the incorrect area.

Submit each file as-is, without putting it into a .tar.gz file or arranging it in any other way. (There is a gather script in the set7 template, but there's no need to use it.) If we asked for a PDF, for example, all we want is a PDF; no more, no less. If you submit something other than what we asked for, we will not be offering you any credit on the submission. There are no exceptions to this rule.

Of course, you should also be aware that you're responsible for submitting precisely the version of your work that you want graded. We won't regrade an exercise simply because you submitted the wrong version accidentally, and we won't be able to offer any credit on exercises that you forgot to submit.

Can I submit after the deadline?

Unlike some of the projects in this course, the reinforcement exercises cannot be submitted after the deadline; there is no late policy for these. Each is worth only 3% of your grade, so it's not a disaster if you miss one of them along the way.

Can I work on this outside of the VM?

Yes, but be aware that you must submit the files in the appropriate format, and that any C++ code you submit must compile and run on the ICS 45C VM.