CS 70

Modeling Quinn's Train in C++

We are going to model Quinn's train as a C++ class called Train. A train will have a dynamically allocated array of (train) cars, which we will model as a class called Car.

“Creativity is always a leap of faith. You’re faced with a blank page, blank easel, or an empty stage.” —Julia Cameron

“No matter how many technologies might be developed to augment the operation of our bodies and mind, it is hard to imagine a better starting point for thought than a blank sheet of paper. Its honesty and reliability naturally provoke excitement for the next encounter.” —John Maeda

In previous assignments, you've had extensive starter code to work with; it's often the case that you'll find yourself working with an existing codebase, and knowing how to figure out how things work and how to implement your own functionality in the same style and approach is a very important skill to have and to practice.

But in this assignment, the only code we've given you is testtrain.cpp, for testing your code once it's all written and working. The rest of the code—car.hpp, car.cpp, train.hpp, train.cpp—is up to you to write from scratch. (Also a very useful skill.)

Feel free to look at examples of C++ classes from previous lessons or homework assignments that you can use as examples to help you code!

Implementation Steps

Create the Car Header File

Your first step is to create the header file that contains the definition of your Car class. (In the next part of this assignment you will write the implementation file that contains the code defining the functions that can be called by a Car.)

Setting up car.hpp

Create a new file named car.hpp. For now, we are only defining the data members and member functions that a Car has; that is, we are focusing just on the interface.

Inside car.hpp, complete the following tasks:

  1. You must use an include guard to prevent the Car class from being defined twice if it is included in multiple files.
  2. You should include <ostream> and <cstddef> (which declares size_t).
  3. Declare the class Car. (Don’t forget the semicolon after the closing curly brace of the class definition!)

Next you’ll specify all the data members and functions. We're not writing the implementation code for these functions yet, just declaring them to say what they are and what they'll do. We'll start writing the code in the implementation file, car.cpp, in the next part of the assignment.

Declare the Data Members

Each car will have bins that can each store one package, which we will model as a Boolean array, bins_, with a constant size, CAPACITY, where bins[i] is true if there is a package stored in bin number i and false otherwise. Thus,

  1. Constant: CAPACITY, set to 4.
    • This says that a car will have space for four packages.
    • You define a class-wide constant by declaring it as static constexpr inside the class.
      • It should be static so that the same constant is shared by all objects of the class.
      • It should be constexpr so that it is a constant known at compile time.
    • It should be public so that it can be accessed outside the class.
    • Inside the Car class, you can access this value as just CAPACITY.
    • Outside the Car class, you can access it as Car::CAPACITY.
  2. Member variable: bins_, an array of bools.
    • The array should have CAPACITY elements.
    • It should be private so that it can only be accessed by member functions of the class.
    • Because this is a fixed size, known at compile time, you should not dynamically allocate it (i.e., no need for pointers or new). Just declare it as a regular array.
  3. Member variable: binsInUse_, a size_t.
    • This variable will keep track of how many bins are occupied in a given car.
    • It should be private so that it can only be accessed by member functions of the class.

Declare the Member Functions

  • Your Car class should explicitly disable
    • The copy constructor Car(const Car& other).
    • The assignment operator Car& operator=(const Car& other).
  • Your Car class should state that it is using the synthesized destructor ~Car().
  • Your Car class must contain the following public member functions:
    1. A default constructor, Car(), that initializes all the data members properly.
    2. A function, getUsage(), that
      • Takes no arguments,
      • Finds how many bins are in use, and
      • Returns that information as a size_t.
    3. A function, isEmpty(), that
      • Takes no arguments,
      • Finds whether the car is empty, and
      • Returns that information as bool.
    4. A function, isFull(), that
      • Takes no arguments,
      • Finds whether the car is full, and
      • Returns that information as bool.
    5. A function, addPackage(),
      • That takes no arguments,
      • Adds a package to the car, and
      • Doesn't return anything.
    6. A function, removePackage() that
      • Takes no arguments,
      • Removes a package from the car, and
      • Doesn't return anything.
    7. A function, printToStream(std::ostream& outStream), that
      • Takes an std::ostream,
        • std::cout is an example of an std::ostream, so someone might call this function as myCar.printToStream(std::cout);
      • Displays the car (by printing to the given ostream), and,
      • Doesn't return anything.
  • Review the functions above and determine which ones don't modify the object they're called on.
    • If a member function doesn't modify the object, declare it as const (by putting the keyword const on the far right, after the close parenthesis of the parameter list. (This rule is pretty much universal.)

Declare Global (Non-Class) Functions

Sometimes we need to write functions that aren't member functions for the class, but are closely associated with it. One such function is the << operator for printing, which is always a global function, but is often defined to work with a particular class.

  • Declare a global function (i.e., a function outside of the class) to allow a Car object to work with the streaming operator (<<). Similar to operator=, the function is named:

    std::ostream& operator<<(std::ostream& os, const Car& c);
    
    • This function should be declared outside of the class, after the closing curly brace, }, but before the the #endifof your include guard.
  • Goat speaking

    Remind me, what's the deal with std::ostream in printTostream?

  • LHS Cow speaking

    You've used a std::ostream before! std::cout is an std::ostream that sends output text to the terminal!

  • RHS Cow speaking

    Another common example is a std::ofstream, which is an std::ostream that sends output text to a file.

  • Duck speaking

    Why not just have a print() member function and always print to std::cout?

  • LHS Cow speaking

    What if we want to send the output somewhere else? Letting the user specify the stream they want the output sent to provides more flexibility.

  • Pig speaking

    MORE choices!

  • Hedgehog speaking

    And what about that operator<< thing? What's that all about?

  • LHS Cow speaking

    When we say cout << x;, that is equivalent to writing operator<<(cout, x);. When we define our own version of operator<< for a type (e.g., for Car) allows us to specify what the << operator does to a Car.

  • RHS Cow speaking

    In other words, then we can say:

    std::cout << "This car is: " << myCar << std::endl;
    

Helpful Hints

You need to put std:: on the front of standard library types like std::ostream.

  • Duck speaking

    But I can skip saying std:: by saying:

    using namespace std;
    
  • LHS Cow speaking

    That's okay to do if you want in an implementation file, but it's considered uncool in a header file, as means anyone who includes your header file gets the std namespace opened whether they wanted that or not.

To Complete This Part of the Assignment…

You'll know you're done with this part of the assignment when you've done all of the following:

(When logged in, completion status appears here.)