CS 70

Implement Train Member Functions

In this part of the assignment, you will implement the functionality of the Train in the file train.cpp. For each function, you'll fill out the stub function you wrote in Part 5.

  • LHS Cow speaking

    You should compile and test your code frequently, and, of course, fix any problems you find along the way..

  • RHS Cow speaking

    At the end of each step, we'll recommend various test functions in testtrain.cpp to exercise your new code.

  • LHS Cow speaking

    You should also run your code with Valgrind each round to check for memory leaks.

  • RHS Cow speaking

    Let's get to it!

Implement Constructor, Destructor, printToStream(), and operator<<

Train::Train(traintype type) — The Parameterized Constructor

You should now write code to fill in the stub that you previously wrote. The Train constructor should do the following:

  1. Initialize cars_ to point to an array (allocated with new) with room for exactly one car.
  2. Initialize numCars_ to 1.
  3. Initialize usage_ to 0.
  4. Initialize revenue_ to 0.
  5. Initialize operatingCost_ to 0.
  6. Initialize type_ to the value of the input parameter type (which will be either BASIC or SMART).

(All of the above can and should be done in the member-initialization list!)

Train::~Train() — The Destructor

The destructor should free up all dynamically memory that was allocated by for this Train.

Train::printToStream() — The Output Function

This function displays specific information about a train.

It should:

  • Output (
  • Output the revenue
  • Output , and a single space
  • Output the operating cost
  • Output ) and a single space
  • Loop over all the cars
    • Output each car

(Don't add any additional spaces or newlines! Don't print the revenue as dollars and cents, just as an number, which will be whole dollars given our constants.)

At this point you can only have trains with a single car, zero revenue, and zero operating cost, as we haven't added functions to change those values. A correctly printed train would look like

(0, 0) [_][_][_][_]~

Check Your Code So Far

  1. Confirm that your code still compiles without warnings using your Makefile.
  2. Uncomment the first two Train test functions in testtrain.cpp: testCreateTrain() and testOutputTrain_1().
  3. Confirm that your code compiles with your Makefile and fix issues.
  4. Run your ./testtrain, both normally and with Valgrind and fix any issues.

Implement Functions for Adding Packages

Temporary Code for upsizeIfNeeded()

Change upsizeIfNeeded to stop throwing an exception and just return without doing anything. If you like, you can make the function print a message to cerr warning that it isn't actually doing its job.

Write addPackage()

Adding a package to the train will include

  1. If necessary, adding more cars to add another package.
  2. Loading the package.
  3. Updating revenue to reflect the charge to the customer.

Your function for adding a package to a train should do the following:

  1. Call upsizeIfNeeded(). Currently, upsizeIfNeeded() is a stub that does nothing, but, for now, assume it works correctly and adds more train cars if there isn’t enough room to add another package. You will implement upsizeIfNeeded() soon!
  2. Call loadPackage(). As with upsizeIfNeeded(), this function is still just a stub, so it won't do anything. Again, don’t worry about it yet!
  3. We have done a lot of work, so let’s make some money now! Increase revenue by SHIPPING_COST once we have accepted a package (and loaded it onto a train!), which is work for which we charge customers.

Write loadPackage()

The loadPackage() function will take care of loading a package onto the appropriate car and then update usage_ and the operatingCost_. Train cars are always filled from left to right. In general, a train might end up with some full cars towards the front, a car in the middle with room for more packages, and the rest of the cars empty.

For example,

(40, 12) [x][x][x][x]~[x][x][x][x]~[x][x][_][_]~[_][_][_][_]~
  1. We’ll leverage the addPackage() function we wrote earlier.
    • First, determine which car is both closest to the front of the train and has room for additional packages.
    • Then, call that Car’s addPackage() function.
    • Think about how would you use the Train data members to determine the index of the frontmost car with unused space.
  2. Increment the usage_ variable.
  3. As loading a package is some amount of work that Quinn’s company needs to perform, increment operatingCost by HANDLING_COST.

Check Your Code So Far

Test that your function works by compiling and running it both normally and with Valgrind, using tests testTrainAddAndOutput_01(), testTrainAddAndOutput_02(), and testTrainAddAndOutput_03().

Implement Functions for Removing Packages

Write removePackage()

Your function for removing a package from a train will

  1. Remove a package from a train.
  2. Update usage_ accordingly.
  3. Decrease the number of train cars if necessary.

In particular, your function should

  1. Check whether the train contains a package to be removed. If so…
  2. Decrement the usage_ variable.
  3. Call the Car class’s removePackage() function on the rearmost car with any packages.
    • Think about how would you use the Train data members to figure out the index of that car.
  4. Call downsizeIfNeeded(). Currently, downsizeIfNeeded() is a stub that does nothing, but assume it works correctly. We will implement downsizeIfNeeded() soon.

It is against the rules to call removePackage() on an empty train. As with your code in the Car class, you can choose how to handle people breaking the rules.

Check Your Code So Far

Test that your function works by compiling and running both normally and with Valgrind, using tests testTrainAddRemoveAndOutput_01(), testTrainAddRemoveAndOutput_02(), testTrainAddRemoveAndOutput_03().

Implement Functions for Changing the Size of the Train

Write upsizeIfNeeded()

At this point, we are going to take into account our two different train size-changing strategies. Our goal is to figure out if we need more space, and, if so, use the changeSize() function to move all the packages to a larger train. We have not yet implemented the changeSize() function, so it will actually do nothing at this point, but that's OK!

Your upsizeIfNeeded() function should determine if the last car on the train is full, and, if so

  1. If the train is a BASIC train,
    • Call changeSize() with the current number of cars in the train plus BASIC_SIZE_CHANGE, thus switching to a train with BASIC_SIZE_CHANGE more cars than the train you're starting with.
  2. If the train is a SMART train,
    • Call changeSize with the current number of cars times SMART_SIZE_CHANGE, thus switching to a train that is SMART_SIZE_CHANGE times the size of your original train. (We will investigate why this method is the “smart” thing to do after we get it all working.)

Write downsizeIfNeeded()

This function also takes our two different train size-changing strategies into account. If we call this function, we want to switch to a smaller train to avoid using too much unnecessary space (i.e., memory).

Your downsizeIfNeeded() function should only downsize if there is more than one car in the train. To downsize,

  1. If the train is a BASIC train and the last car is empty, remove cars by calling changeSize() with the current number of cars minus BASIC_SIZE_CHANGE.
  2. If the train is a SMART train and the number of packages on the train (usage_) is less than or equal to 1/SMART_DOWNSIZE_THRESHOLD of the total current package capacity of the train, we call changeSize()with the current number of cars divided by SMART_SIZE_CHANGE. For example, if we have a car using one-quarter of its total capacity, we want to switch to a new train that is half the size.

Write changeSize()

With changesize(), we will finally be able to switch to larger or smaller trains. An important consequence of switching trains is that there is a handling cost for Quinn to transfer the packages from one train to another. To correctly capture this cost, you’ll need to use removePackage() and loadPackage(). Our outlined approach is not the only possible solution, but it is one that works. You can implement a different approach if you come up with one that makes sense to you!

Your changeSize(size_t size) function should

  1. Create a temporary pointer, say, oldCars, that points to the same place in the heap as cars_. Also create a variable to store the number of old cars.

  2. We can’t resize our array, so we have to attach the engine to a fresh bunch of cars (i.e., create a new array).

    • Set cars_ to a new memory location on the heap allocated to hold an array of size cars.
    • Set numCars_ to be size.
    • Set usage_ to 0.
  3. Iterate over the old cars. For each old car,

    • Simulate transferring the car's packages to the new train one at a time. In other words, you need to
      • Remove each package from the old car (call that car’s removePackage() function).
      • Load each package onto the new cars_ of the train by calling loadpackage().
  4. Clean up the old array pointed to by oldCars so no memory is leaked on the heap. (Metaphorically, return those cars to the station so they can be reused.)

Check Your Code So Far

Compile and use Valgrind with tests testBasicUpsize_01(), testBasicUpsize_02(), testSmartUpsize_01(), testSmartUpsize_02(), testBasicDownsize_01(), testBasicDownsize_02(), testSmartDownsize_01(), testSmartDownsize_02().

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.)