Section 4.2 Class Design and Dynamic Memory Allocation

Each week, we will also be releasing a VS Code project containing starter code and testing infrastructure for that week’s section problems. When a problem name is followed by the name of a .cpp file, that means you can practice writing the code for that problem in the named file of the VS Code project. Here is the zip of the section starter code:

📦 Starter code

Class Design

Problem One: Random Bag Review

The very first container class we implemented was the random bag, which supported two operations:

  • add, which adds an element to the random bag, and

  • removeRandom, which chooses a random element from the bag and returns it.

Below is the code for the RandomBag class. First, RandomBag.h:

#pragma once

#include "vector.h"

class RandomBag {
public:
    void add(int value);
    int removeRandom();

    int size() const;
    bool isEmpty() const;

private:
    Vector<int> elems;
};

Next, RandomBag.cpp:

#include "RandomBag.h"
#include "random.h"

int RandomBag::size() const {
    return elems.size();
}

bool RandomBag::isEmpty() const {
    return size() == 0;
}

void RandomBag::add(int value) {
    elems += value;
}

int RandomBag::removeRandom() {
    if (isEmpty()) {
        error("That which is not cannot be!");
    }

    int index = randomInteger(0, size() - 1);
    int result = elems[index];

    elems.remove(index);
    return result;
}

Let’s begin by reviewing some aspects of this code.

  1. What do the public and private keywords mean in RandomBag.h?

Solution

The public keyword indicates that the member functions listed underneath it are publicly accessible by anyone using the RandomBag class. This essentially means that they form the public in-terface for the class.

The private keyword indicates that the data members listed underneath it are private and only accessible by the class itself. This means that those data members are part of the private implementation of the class and aren’t something that clients should be touching.

  1. What does the :: notation mean in C++?

Solution

It’s the scope resolution operator. It’s used to indicate what logical part of the program a given name belongs to. The case we’ll primarily see it used is in the context of defining member functions in a .cpp file, where we need to indicate that the functions we’re implementing are actually member functions of a class, not freestanding functions.

  1. What does the const keyword that appears in the declarations of the RandomBag::size() and RandomBag::isEmpty() member functions mean?

Solution

It indicates that those member functions aren’t allowed to change the data members of the class. Only const member functions can be called on an object when in a function that accepts an object of that class by const reference.

Problem Two: Rational Decision Making

Consider the following partially-implemented Fraction class which can be used to model rational numbers in C++, functionality that is not built-in to the language. If you’re interested in the actual source code of the public and private helper methods, feel free to refer to the section starter code.

class Fraction {
public:
    /* Creates the fraction num / denom. If neither is specified,
     * the fraction defaults to 0. If only the numerator is specified,
     * the fraction will be equal to that value exactly (the
     * denominator will be 1). For example:
     *
     * Fraction f;          // f == 0      (0/1)
     * Fraction f = 137;    // f == 137    (137/1)
     * Fraction f(137, 42); // f == 137/42
     */
    Fraction(int num = 0, int denom = 1);

    /* Access numerator and denominator. */
    int numerator() const;
    int denominator() const;

    /* Adds the given value to this fraction. For example, if this
     * fraction is 1/2 and the other fraction is 1/4, this fraction
     * ends up equal to 3/4.
     */
    void add(const Fraction& f);

    /* Multiplies this fraction by the other fraction. For example,
     * if this fraction is 1/2 and the other fraction is 1/4, this
     * fraction ends up equal to 1/8.
     */
    void multiply(const Fraction& f);

    /* Evaluates the ratio represented by this fraction. */
    double asDecimal() const;

private:
    /* The actual numerator and denominator. */
    int num;
    int den;

    /* Reduces the fraction to its simplest form. */
    void reduce();
};
  1. The constructor for the Fraction type uses two default arguments. Read the comments on the constructor. Given how the constructor is defined and given its default arguments, why does writing Fraction f = 137 produce the fraction 137 / 1?

Solution

The constructor takes in two arguments, but has the first default to 0 and the second to 1. Therefore, if only a single argument is provided, C++ will assume the second is 1. This means that Fraction f = 137 is treated as if the first argument is 137 and the second argument is 1, so we get the fraction \(\frac{137}{1} = 137\text.\)

  1. The function named add could mean one of two different things. First, it could take two fractions and add them together, producing a third fraction. Second, it could take two fractions and modify the first fraction by adding in the second. In the case of our Fraction class, it’s the latter. There are two ways you can infer this just by looking at the signature of the function add, without even needing to read the comments. What are they?

Solution

The first “tell” is that add has a void return type and thus doesn’t return anything. Therefore, it couldn’t return a new Fraction.

The second “tell” is that add is not marked const. This means that the function will modify the Fraction object it’s called on. (Otherwise, the function would be marked const.)

  1. Add a function named reciprocal to the Fraction type that returns the reciprocal of the fraction. If the fraction is zero, call error to report that the reciprocal can’t be taken.

Solution

First, we’ll add this to the .h file in the public section of the class.

/* Returns the reciprocal of this fraction. If the fraction is zero, this
 * operation is not defined, and the function calls error().
 */
Fraction reciprocal() const;

Next, we’ll add this to the .cpp file:

Fraction Fraction::reciprocal() const {
    if (numerator() == 0) {
        error("I can't divide by zero. (Maybe you can, though?)");
    }

    return Fraction(denominator(), numerator());
}
  1. Add a function divide to the Fraction type. Analogously to multiply, the divide function should accept as input another Fraction and modify the receiver by dividing its value by that of the other Fraction.

    If the other fraction is equal to zero, call error().

Solution

First, let’s add this to our .h file:

/* Divides this fraction by the fraction given in the parameter. If that
 * fraction is zero, calls error().
 */
void divide(const Fraction& rhs);

Next, the implementation, which goes in the .cpp file:

void Fraction::divide(const Fraction& rhs) {
    multiply(rhs.reciprocal());
}