0

For my last project this year I am trying to make a Gradebook program in C++. Using classes vs structs, new and delete instead of malloc and free, I am to recreate my previous homework written in C. In my last question someone told me to stop making my variables private as I have a pointer to that class from my main Class.

in gradebook.h

class Student {
 public: 
  string last;
  string first;
  int student_id;
  int count_student;
};

class Course {
 public:
   string name;
   int course_id;
   int count_course;

};

class Gradebook {
 public:
  Gradebook();
  void addCourse();
  void addStudent();
  void printCourse();
  void printStudent();

 private:
  Course *courses;
  Student *students;
 };

in gradebook.cpp

 Gradebook::Gradebook() {

  courses = new Course;
  courses->count_course=0;
  courses->course_id = 0;


  students = new Student;
  students->count_student=0;
  students->student_id=0;    

}

Gradebook::addCourse() {

 int i, loop=0;

 cout << "Enter number of Courses: ";
 cin >> loop;

 for(i=0; i<loop; i++) {

  cout << "Enter Course ID: ";
  cin >> courses[courses->count_courses].course_id;

  cout << "Enter Course Name: ";
  cin >> courses[courses->count_courses].name;

  courses->count_course++;

 }

}

 Gradebook::addStudent() {

   //same information from addCourse but goes to students variables

 }

Gradebook::printCourse() {

 int i;

 for(i=0; i<courses->count_course; i++) {

   cout << courses[i].course_id << "\t\t";
   cout << courses[i].name << endl;

 }

}

Gradebook::printStudent() {

 int i;

 for(i=0; i<students->count_student; i++) {

   cout << students[i].student_id << "\t\t";
   cout << students[i].last << "\t\t";
   cout << students[i].first << endl;

 }

}

When I run addCourse function then run printCourse it works.

Then I run addStudent function then run printStudent it works.

Problem:

After I add students and rerun printCourse I get garbage data when courses[i].course_id gets printed. But only when i=2. courses[i=2].name still prints with the correct data. I can add more courses and add more students and they print out just fine, again only when i=2 does course_id get garbage data. I've been stuck for a few days and I tried to look at it a different way until @wheaties mentioned that what I was doing previously was correct and it should be public. So can one of you guys help me out?

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
user2816227
  • 173
  • 2
  • 13
  • 2
    I don't see the need for pointers or `new` at all here. – chris Apr 23 '14 at 16:57
  • `courses` and `students` should be an array of pointers...not a single pointer to a single instance – Logan Murphy Apr 23 '14 at 16:57
  • 3
    `Course` and `Student` should have their own constructors to initialize all of the values. Also, you should be using `std::vector` for holding a list. – crashmstr Apr 23 '14 at 16:59
  • 2
    Somebody told you to stop making your member variables private? You have to be careful what advice you listen to, because you got some bad advice... Your code has a few problems. The main one being what @LoganMurphy mentioned. You have member variables `courses` and `students`, suggesting that you want to store multiple courses and students. But the variables only point to a single instance of a course/student. – Reto Koradi Apr 23 '14 at 17:05
  • 2
    In the constructor of `Gradebook`, you allocate memory for exactly one instance of `Course`. However, you access that memory through your variable `courses` like an array. This leads to accessing memory that you should not, which would explain the garbage. – blackbird Apr 23 '14 at 17:05

4 Answers4

1

Your problem:

The following:

courses[courses->count_courses].course_id;

Does not do what you'd want it to do. It treats courses like an array of Course objects, when in reality it is just a single (heap-allocated) object. So as soon as count_courses is increased, you access memory beyond the single existing Course object, which, as you already observed, yields only garbage.

How to fix it?

The correct "C++" way would be using std::vectors instead of pointers for the Course and Student lists. But if you really have to go with manual memory allocation, you have to:

  1. Move count_courses and count_students out of the corresponding classes and into CourseBook. Each student or course object represents a single course/student, so it doesn't make sense to store a count in it.
  2. Allocate the memory using "array new": courses = new Course[count];. Of course, you have to move the allocation out of the constructor then, and do it in addCourse instead, where you know how many items will be there
  3. Don't forget to release the allocated memory..
lethal-guitar
  • 4,438
  • 1
  • 20
  • 40
  • `vector`s are bad if you do not know how many items it will store. reallocations are expensive. – Bruno Ferreira Apr 23 '14 at 17:12
  • 1
    @BrunoFerreira I disagree. This is relevant if you have huge amounts of data, but even then I will always profile my code and prove that it's too slow before I switch to another data structure. But for such a simple small example, the overhead simply doesn't matter. – lethal-guitar Apr 23 '14 at 17:14
  • If you need direct access and do not know how many members it will store, use `deque` (direct access uses log(n) at worst, usually constant amortized). If you need direct access and know how many members it will store, use `vector`. For example, look at this: http://en.cppreference.com/w/cpp/container/vector/reserve – Bruno Ferreira Apr 23 '14 at 17:19
1

Problem:

in Gradebook::Gradebook(), you construct a single Course:

courses = new Course;
courses->count_course=0;
courses->course_id = 0;

and the same goes for Student. You need to construct either an array or use one of the STL's containers. For this case, I'd recommend either std::list or std::deque. A simple usage would be:

class Gradebook {
  std::list<Course> courses;
}

void Gradebook::addCouse() {
  int count = 0;
  cout << "Courses count: ";
  cin >> count;

  while (count-- > 0) {
    Course course;
    cout << "Course ID: ";
    cin >> course.id;

    cout << "Course name: ";
    cin >> course.name;

    courses.push_back(course);
  }
}

void Gradebook::printCourse() {
  cout << "There are " << courses.size() << " courses" << endl;
  for (std::list<Course>::iterator i = courses.begin(); i != courses.end(); ++i) {
    cout << "    ID: " << i->id << " name: " << i->name << endl;
  }
}

If you wish to use std::vector, only the addCourse would change:

void Gradebook::addCouse() {
  int count = 0;
  cout << "Courses count: ";
  cin >> count;
  courses.reserve(count);

  while (count-- > 0) {
    Course course;
    cout << "Course ID: ";
    cin >> course.id;

    cout << "Course name: ";
    cin >> course.name;

    courses.push_back(course);
  }
}
Bruno Ferreira
  • 1,621
  • 12
  • 19
  • Is there any reason to use a `list` instead of a `vector`? – lethal-guitar Apr 23 '14 at 17:12
  • If you do not know how many members it will allocate, use a list. If you know, use a vector. Since we are specifying "Courses count", vector would also be a good solution here. – Bruno Ferreira Apr 23 '14 at 17:13
  • 2
    I expect every reasonable standard library to implement an intelligent scheme for reallocation which has amortized constant O(1) time complexity. `list` is probably less cache friendly due to the additional indirection, and has additional dynamic memory allocation overhead. Using it just because "I think it's faster" smells like [premature optimization](http://c2.com/cgi/wiki?PrematureOptimization). – lethal-guitar Apr 23 '14 at 17:17
  • 2
    In general, one should prefer `std::vector` for most cases. The time you want to look elsewhere is based on usage such as direct lookup via a "key" (`std::map`) or insert and remove in the middle. – crashmstr Apr 23 '14 at 17:20
  • http://john-ahlgren.blogspot.com.br/2013/10/stl-container-performance.html Hope this explains. – Bruno Ferreira Apr 23 '14 at 17:35
  • @BrunoFerreira I'm aware of the different time complexities, but I still cannot see why I shouldn't use `vector` by default, unless different usage patterns are required as mentioned by crashmstr – lethal-guitar Apr 23 '14 at 17:50
  • I'll try to make it clear: `push_back` to a `vector` is constant if and only if it won't perform re-allocations. If they are to happen, there is no guarantee about the complexity, since it depends on the OS. I didn't say you shouldn't use `vector`, I said that you must `reserve` to avoid re-allocations. Even if you do not know how many elements it will store but have an approximation (i.e., it will store something around 1532 elements), vector would be good, since at most only one reallocation would occur. – Bruno Ferreira Apr 23 '14 at 18:05
  • @BrunoFerreira Your are awesome!!! You saved me a lot of sleepless nights. I used vector instead of list though. But I want to pick your brains some more. I wanted to know why you used list instead of vector? Using vector seems to be easier in my opinion as I can iterate through my array using an int i instead of the iterator as in a list. I was just curious what your thoughts are on list vs vector. Again thank you. – user2816227 Apr 23 '14 at 18:06
  • I used list for no reason. As we are discussing, lists are OK, but they are slower than vectors if the vector is well used (`reserve`'ing the memory beforehand). – Bruno Ferreira Apr 23 '14 at 18:09
  • @BrunoFerreira ok I understand. Suppose I add another variable in my class Student. vector enroll_id; because it is now a vector vector...how do i add data to enroll_id? In my next function I am doing some error checking. I have a variable temp2 that holds data input from user. I want to check temp2 == students[i].student_id. If they are equal i want to set students[i].enroll_id[temp2] using vector. I keep getting compiler errors. – user2816227 Apr 23 '14 at 18:25
  • Use this: `for (vector::size_type i = 0; i < students.size(); i++) { if (temp2 == students[i].id) { students[i].enroll_id[temp2] = value; } }` – Bruno Ferreira Apr 23 '14 at 18:28
  • @BrunoFerreira I fully understand that, but I don't consider a few reallocations a problem, especially not for "small" data sets (even with 1000s of items). So I disagree that I *"must"* always reserve. It is definitely relevant for real-time and performance-critical code. But for most "everyday" programs like the OP's, the difference won't be noticeable. And littering my code with `reserve` calls is just premature optimization. – lethal-guitar Apr 23 '14 at 19:23
0

If there is a

Course * courses;

it means that you have a variable containing an address to a memory area containing a Course. You can access the memory area (after having established it using new or malloc) by writing

*courses = ...;

or

courses->name.

However, C (and C++) lets you apply indexing to this pointer:

courses[i]

which is nothing but

*(courses + i)

i.e., following the pointer you obtain by incrementing courses with i times the size of Course.

Consider that new Course provides memory for a single Course and then look at what your loop in addCourses does...

There's no need for dynamic memory allocation if you use std::vector.

laune
  • 31,114
  • 3
  • 29
  • 42
0

There is only one object of courses class, which you are creating in the constructor of Gradebook::Gradebook()

You should try to create as many objects of courses as you want to insert. You could do that by

  Gradebook::addCourse() {

 int i, loop=0;

 cout << "Enter number of Courses: ";
 cin >> loop;
 //add these lines here and remove them form Gradebook constructor
 courses = new Course[loop];
 courses->count_course=0;
 courses->course_id = 0;
 //^^^^^

 for(i=0; i<loop; i++) {

 cout << "Enter Course ID: ";
 cin >> courses[courses->count_courses].course_id;

 cout << "Enter Course Name: ";
 cin >> courses[courses->count_courses].name;

 courses->count_course++;

 } 

}
nittoor
  • 113
  • 6