Classes with dynamic memory

Introduction

This lab is designed to give you some exposure to using classes and defining classes when dynamically allocated memory is involved. In these cases, you have to be pretty careful with what goes on with pass-by-value, copy constuctors, const-ness, and assignment!

Part 1 (60 points): Using a class with dynamic memory

To get you started as the user of a class or module involving dynamic memory, I'm giving you a class Grades for storing and retrieving grade information on arbitrarily many assignments. Your interface to this module is grade.h:

/****************************************************
 * grade.h --- Interface for the Grades module.
 * Class Grades allows you to store grades and retrieve them by "index".
 * The first grade you store has index 0, the second grade you store has index 1, etc.
 ****************************************************/

/** DECLARATION of class Node **/
class Node;

/** DEFINITION of class Grades **/
class Grades{
private:
  // Disable pass-by-value and assignment!
  Grades(const Grades&) { }
  Grades& operator=(const Grades &G) { }

public:
  Grades() {
    head = tail = 0;
    n = 0;
  }
  ~Grades();

  // adds a grade (positive grades only)
  void addNextGrd(double score);

  // retrieves grade from index - if a
  // negative grade is returned, then
  // there was no grade with that index!
  double getGrd(int index) const;

  // returns number of grades
  int numGrd() const {
    return n;
  }

private:
  Node *head, *tail;
  int n;
};

The implementation is in a .cpp that I'm not going to let you see just yet. Instead, I will provide the object file necessary to compile your final executable. The file can be retrieved via: grade.o.

In the first part of the lab, you are going to use the provided Grades module to write a program (in a file named part1.cpp) that tracks grades for two students. The program simply has a command prompt that allows the user to:

  1. Enter new grades (NG):
    NG stu1score stu2score
  2. Request the averages of the grades entered so far for both students:
    AV
  3. Get the grades for the two students on an assignment with a particular index:
    GD index
  4. Quit the program
    QT

Hint: Your compile command should look something like this:

g++ -o part1 part1.cpp grade.o

When you've got the program working, a typical run of it might look something like this:

Note: If I were you I would define a function that would take a Grades object and return the average of the grades stored there.

Part 2 (30 points): Implementing a class that uses Grades

In the second part of the lab you are going to create a new class called Student that stores the exam, homework and quiz grades for a student, as well as a name for the student. This class is used by the driver program driver.cpp:

/**************************************************
 * driver.cpp - The program defined here is a simple
 * program for tracking student grades for two students,
 * Joe and Amy.  The user has the following commands:
 *
 * NG <type> <student01's grade> <student02's grade>
 * AV <type>
 * TG
 * QT
 *
 * Valid <type>s are "exam", "quiz", & "homework"
 * NG - enters new <type> grades for student01 and student02
 * AV - prints out the average for the provided <type>
 * TG - prints the total grades for students, weighting exams 50%,
 * quizes 30%, and homeworks 20%.
 * QT - quits the program
 **************************************************/
#include "student.h"
#include <string>
#include <iostream>
using namespace std;

// FUCNTION PROTOTYPES
// Prints out the total grade for student S, weighing
// exams 50%, quizzes 30% and homeworks 20%
double totgrd(const Student &S);

// MAIN FUNCTION
int main() {
  Student S1("Joe"), S2("Amy");
  string command;

  while(cin >> command && command != "QT") {
    if (command == "NG") {
      string tp;
      double g1, g2;
      cin >> tp >> g1 >> g2;
      S1.addNextGrd(g1,tp);
      S2.addNextGrd(g2,tp);
    }
    else if (command == "AV") {
      string tp;
      cin >> tp;
      cout << S1.name() << "'s " << tp << " average is " << S1.ave(tp) << endl;
      cout << S2.name() << "'s " << tp << " average is " << S2.ave(tp) << endl;
    }
    else if (command == "TG") {
      cout << S1.name() << "'s total grade is " << totgrd(S1) << endl;
      cout << S2.name() << "'s total grade is " << totgrd(S2) << endl;
    }
    else
      cout << "Unknown command!" << endl;
  }

  return 0;
}

// FUNCTION DEFINITIONS
double totgrd(const Student &S){
  return 0.5*S.ave("exam") + 0.3*S.ave("quiz") + 0.2*S.ave("homework");
}

This part's program will look similar to the previous one. The commands NG and AV are still there, but each of them requires additional information from the user, namely the type of assignment: exam, quiz or homework. There is a new command called TG, which should print out the two student's total grade, weighting exams 50%, quizes 30%, and homeworks 20%. The following is a typical session with this program:

Your class should be defined in files names student.h and student.cpp, and it should be defined so that the driver program driver.cpp compiles and runs properly. In particular, look at driver.cpp to see what constructors and member functions are required.

You may not modify driver.cpp. You may use the Grades class from part 1 ... in fact you'd be crazy not to!

Hint: Your compile command should look something like this:

g++ -o part2 driver.cpp grade.o student.cpp

Part 3 (10 points): Follow up

Now look at the implementation of the Grades module, grade.cpp:

#include "grade.h"
#include <cmath>
using namespace std;

/** DEFINITION OF CLASS NODE **/
class Node {
public:
  double data;
  Node *next;
  Node(double val, Node* p) {
    data = val; next = p;
  }
};

/** DEFINITION OF Grade's MEMBERS **/
void Grades::addNextGrd(double val) {
  n++;
  if (!head)
    head = tail = new Node(fabs(val),head);
  else
    tail = tail->next = new Node(fabs(val),0);
}

double Grades::getGrd(int index) const {
  Node *p = head;
  for(int i = 0; p != 0 && i != index; i++)
    p = p->next;
  if (p) return p->data;
  else   return -1;
}

Grades::~Grades() {
  while(head != 0) {
    Node *p = head;
    head = head->next;
    delete p;
  }
}

Try to understand how it works. In particular, how does this implementation make sure that any memory that's allocated with newis deallocated with delete, and no object is deallocated with delete unless it's certain that no attempt to use the object will ever be made again. Notice how it simply bypassed the pitfalls presented by pass-by-value and assignment by disallowing them! Pretty sneaky, eh? In driver.cpp, the function totgrd uses pass-by-reference rather than pass-by-value. Turn in the answers to these questions with the lab as well (in part3.txt):

  1. Why do you think it was done that way?
  2. Did it need to be done that way? And if not, what would you/someone need to change to do it another way?

A natural final step in this process would be to create a class called Section to store Student objects for each of the students in a particular section. You'd probably want to store the section number as well. What member functions would you want to be available to the user of such a class? What constructors? Would a destructor be needed? Would pass-by-value or assignment be a problem for this class?

Deliverables

Due: 2359 on Monday, 24 Sep 2018

Submit all source code necessary to complete parts 1 and 2 and your answers to part 3 (i.e. part1.cpp, student.h, student.cpp & part3.txt) to the submisison website: submit.cs.usna.edu

~/bin/submit -c=SI221 -p=Lab04 part1.cpp student.h student.cpp part3.txt

Do NOT submit any .o or executable files.