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:
- You must use an include guard to prevent the
Carclass from being defined twice if it is included in multiple files. - You should include
<ostream>and<cstddef>(which declaressize_t). - 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,
- 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 constexprinside the class.- It should be
staticso that the same constant is shared by all objects of the class. - It should be
constexprso that it is a constant known at compile time.
- It should be
- It should be
publicso that it can be accessed outside the class. - Inside the
Carclass, you can access this value as justCAPACITY. - Outside the
Carclass, you can access it asCar::CAPACITY.
- Member variable:
bins_, an array ofbools.- The array should have
CAPACITYelements. - It should be
privateso 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.
- The array should have
- Member variable:
binsInUse_, asize_t.- This variable will keep track of how many bins are occupied in a given car.
- It should be
privateso that it can only be accessed by member functions of the class.
Declare the Member Functions
- Your
Carclass should explicitly disable- The copy constructor
Car(const Car& other). - The assignment operator
Car& operator=(const Car& other).
- The copy constructor
- Your
Carclass should state that it is using the synthesized destructor~Car(). - Your
Carclass must contain the following public member functions:- A default constructor,
Car(), that initializes all the data members properly. - A function,
getUsage(), that- Takes no arguments,
- Finds how many bins are in use, and
- Returns that information as a
size_t.
- A function,
isEmpty(), that- Takes no arguments,
- Finds whether the car is empty, and
- Returns that information as
bool.
- A function,
isFull(), that- Takes no arguments,
- Finds whether the car is full, and
- Returns that information as
bool.
- A function,
addPackage(),- That takes no arguments,
- Adds a package to the car, and
- Doesn't return anything.
- A function,
removePackage()that- Takes no arguments,
- Removes a package from the car, and
- Doesn't return anything.
- A function,
printToStream(std::ostream& outStream), that- Takes an
std::ostream,std::coutis an example of anstd::ostream, so someone might call this function asmyCar.printToStream(std::cout);
- Displays the car (by printing to the given
ostream), and, - Doesn't return anything.
- Takes an
- A default constructor,
- 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 keywordconston thefar right , after the close parenthesis of the parameter list. (This rule is pretty much universal.)
- If a member function doesn't modify the object, declare it as
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
Carobject to work with the streaming operator (<<). Similar tooperator=, 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.
- This function should be declared outside of the class, after the closing curly brace,
Remind me, what's the deal with
std::ostreaminprintTostream?
You've used a
std::ostreambefore!std::coutis anstd::ostreamthat sends output text to the terminal!
Another common example is a
std::ofstream, which is anstd::ostreamthat sends output text to a file.
Why not just have a
print()member function and always print tostd::cout?
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.
MORE choices!
And what about that
operator<<thing? What's that all about?
When we say
cout << x;, that is equivalent to writingoperator<<(cout, x);. When we define our own version ofoperator<<for a type (e.g., forCar) allows us to specify what the<<operator does to aCar.
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.
But I can skip saying
std::by saying:using namespace std;
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
stdnamespace opened whether they wanted that or not.
(When logged in, completion status appears here.)