Vector of a class resetting member variables after for loop - c++

I have an assignment where we need to use this basic structure of vectors and classes to learn about parent and child classes and polymorphism. Here is the code of the function I'm supposed to write:
void assignStudents(vector<Student*>& v) {
for (int i = 0; i < 5; i++)
{
cout << "Enter a study level: ";
string input;
cin >> input;
if (input == "graduate")
{
Graduate inputClass;
Student* inputParentClassPtr = &inputClass;
v.push_back(inputParentClassPtr);
v[i]->addToVector(input);
inputParentClassPtr = nullptr;
}
else if (input == "undergraduate")
{
Undergraduate inputClass;
Student* inputParentClassPtr = &inputClass;
inputParentClassPtr->addToVector(input);
v.push_back(inputParentClassPtr);
}
else
{
cout << "Please enter a valid response, either graduate or undergraduate" << endl;
i--;
}
}
for (size_t i = 0; i < v.size(); i++)
{
vector<string> studyLevels = v[i]->getStudyLevels();
size_t size = studyLevels.size();
for (int j = 0; j < size; j++)
{
cout << studyLevels[j];
}
}
}
I debug the program and every time the first for loop moves on to the next iteration, every member variable inside each object in my vector goes blank, but then when I add a new object into the vector, then call the addToVector() function they come back.
I added the bottom for loop to check if any editing is happening, and once I get to that bottom for loop, every member variable is empty again.
I have the Student class vector where I am adding Undergraduate and Graduate classes to. Every Student class has a protected vector inside called levels. I need to add the class to vector that holds all my objects, then edit the member variable vector to include the string representing the type of class it is.
Why do the member variables (levels) go blank every time it finishes an iteration of the for loop?

I'll just focus on one part, as the same issue appears twice in your code.
{
Graduate inputClass; // create local student "on the stack"
Student* inputParentClassPtr = &inputClass;
v.push_back(inputParentClassPtr); // store address of student
v[i]->addToVector(input);
inputParentClassPtr = nullptr; // has no real effect
} // inputClass goes out of scope and is destroyed here
When the block ends, the local "stack" variables from that block are destroyed. That means the Graduate object is no longer valid, and the pointer you stored in v is now pointing at something unusable.
To fix that, you need to create the objects in dynamic memory.
You should change your vector to store std::unique_ptr<Student>, and create the objects using std::make_unique(), like this:
auto inputParentClassPtr = std::make_unique<Graduate>();
v.push_back(std::move(inputParentClassPtr));
But, if you can't do that, you will need to use new instead, like this:
Student* inputParentClassPtr = new Graduate();
v.push_back(inputParentClassPtr);
Either way, even though inputParentClassPtr is still destroyed at the end of the block, it is only a pointer and the Graduate object it pointed to is still alive.
If you use new, you'll then need to delete all the objects in the vector when you are done using them, or you'll have a memory leak. Using std::unique_ptr will handle that for you.

Related

C++ - printing objects in statistically allocated array causes segmentation fault

So I'm creating a program that implements several classes representing a school, and its students and courses. I'm getting a segmentation fault when I try to prints out all the Taken objects in the studentCoursePairs[] array which represents Student objects taking a particular Course. I think my segmentation fault comes from the addTaken() function in School.cc where its job is to find the student object and course object with the given student number and course id, and then creates a new Taken object with the found student and course objects as well as a grade. I then try to add this new object to the back of the Taken collection which is studentCoursePairs.
When I comment out studentCoursePairs[i]->print() the segmentation fault goes away. I'm not exactly sure what I'm doing wrong and would appreciate some help.
I'm not sure if the other classes besides School.cc are needed but I included them anyways to help with understanding.
School.cc:
#include <iostream>
#include <iomanip>
using namespace std;
#include <string.h>
#include "School.h"
School::School(string s1) : name(s1){
numTaken = 0;
}
void School::addTaken(string number, int code, string grade){
Student* s = nullptr;
Course* c = nullptr;
for(int i = 0; i < numTaken; ++i){
if((studentsCollection->find(number, &s)) && (coursesCollection->find(code, &c))){
Taken* taken = new Taken(s, c, grade);
studentCoursePairs[i] = taken;
++numTaken;
}
}
}
void School::printTaken(){
cout << name << " === TAKEN: "<< endl;
for(int i = 0; i < sizeof(studentCoursePairs)/sizeof(studentCoursePairs[0]); ++i){
studentCoursePairs[i]->print(); //seg fault
}
}
Additional files:
StudentCollection.cc
bool StudentCollection::find(string num, Student** s){
for(int i = 0; i < size; ++i){
if(students[i]->getNumber() == num){ //find student number
*s = students[i];
}
}
}
CoursesCollection.cc
bool CoursesCollection::find(int id, Course** c){
for(int i = 0; i < numCourses; ++i){
if(courses[i]->getId() == id){ //find course id
*c = courses[i];
}
}
}
I also have a Student class and Course class which just declare and initializes information like the name, program, gpa of a student as well as the course code, instructor, name, year of a course.
Your School object has two major problems. Let us start with the one you posted in your question:
void School::printTaken(){
cout << name << " === TAKEN: "<< endl;
for(int i = 0; i < sizeof(studentCoursePairs)/sizeof(studentCoursePairs[0]); ++i){
studentCoursePairs[i]->print(); //seg fault
}
}
This for loop will always run exactly MAX_PAIRS times, as this variable was defined as
Taken* studentCoursePairs[MAX_PAIRS];
so sizeof(studentCoursePairs) === MAX_PAIRS * sizeof(studentCoursePairs[0]).
Instead, you want to loop only over the first few slots that actually contain valid pointers. You have a variable for that: numTaken. So change the condition to i < numTaken and your print loop will work.
The second major problem is in addTaken:
void School::addTaken(string number, int code, string grade){
Student* s = nullptr;
Course* c = nullptr;
for(int i = 0; i < numTaken; ++i){
if((studentsCollection->find(number, &s)) && (coursesCollection->find(code, &c))){
Taken* taken = new Taken(s, c, grade);
studentCoursePairs[i] = taken;
++numTaken;
}
}
}
Let us play computer and work out what happens if the passed in number and code are valid:
If numTaken is 0, the loop immediately stops (as 0 < 0 is false) and numTaken is not incremented. You can call addTaken as much as you want, it will never change numTaken
Assuming you fix that, let us assume numTaken = 5. On the first iteration, you check the condition and agree this is a valid number-code combination. Thus, you create a new Taken object and .. overwrite studentCoursePairs[0] with the new object. On the second iteration you do the same and overwrite studentCoursePairs[1] with an equivalent object.
That is probably not the intended behavior.
Instead, you probably want to place a new object in studentCoursePairs[numTaken] and bump numTaken:
void School::addTaken(string number, int code, string grade){
Student* s = nullptr;
Course* c = nullptr;
if((studentsCollection->find(number, &s)) && (coursesCollection->find(code, &c))){
Taken* taken = new Taken(s, c, grade);
studentCoursePairs[numTaken] = taken;
++numTaken;
}
}
Figuring out how to handle the case where the passed combination is NOT valid or when you exceed MAX_PAIRS combinations is left as an exercise to you.
EDIT: There is a third major problem in your CoursesCollection: you allocate space for one object new Course() while you treat it as an array, and you store the result in a local variable instead of a member. Your constructor should probably look like:
CoursesCollection::CoursesCollection(){
courses = new Course*[MAX_COURSES];
numCourses = 0;
}
or, using a member initializer list:
CoursesCollection::CoursesCollection()
: courses(new Course*[MAX_COURSES]), numCourses(0) {}

Delete dynamically allocated array c++

bool StudentList::remove(const char * studentName)
{
for (int i = 0; i < MAX_SIZE; i++)
{
if (this->students[i]->isEqualTo(studentName)) // Finds a name to remove
{
cout << "Remove: "; // Displays name wished to be removed
students[i]->print();
// delete[] students[i]; - Crashes
// students[i] = NULL; - Replaces removed name with null, stops working.
// students[i]->~Student(); - Call deconstructor, Crashes.
return true;
}
}
return false;
}
I just want to remove a single element out of the array, but keeps crashing when i delete that element.
students[i] is a pointer array, and i need to remove selected elements
First quetion, if you really need to delete "Student" object. If yes, you can add some bad code like:
students[i] = nullptr;
If your students are stored not only in this array, you can make that storage responsible for their deleting. But both ways aren't very good because of using null pointers later. Learn how to use collections, for example vector. You will be able just remove the pointer from an array.
It seems that you want to delete each instance of students, if you could find the studentname.
students seems a two dimensional structure pointer to a pointer. ie; **students. But, you are deleting it in wrong way.As you first need to delete the instance of students[i], then delete the instance of students.
Also, since you are calling the destructor students[i]->~Student(); after deleting instance, it may crash again, as you have assigned student[i] = NULL. then it will be, NULL->~Student() -it will also lead crash.
You need to delete it in following way :
for (int i = 0; i < MAX_SIZE; i++)
{
if (this->students[i]->isEqualTo(studentName)) // Finds a name to remove
{
students[i]->~Student();
delete students[i];
students[i] = NULL;
}
}
delete[] students;
students = NULL;

c++ pointer to function and void pointers iteraction resulting in wierd things

Im making a little project at home about genetic algorithm. But im trying to make it generic, so i use pointers to function and void pointers. but i think it might be making some problems.
The main goal of this section of the project is to get a pointer to a function, which return a certain struct. The struct containing a void pointer
and when im trying to view the value of where it points too it isn`t quite right.I suspect that maybe the interaction between these two might be causing me some problems.
details:
struct:
struct dna_s{
int size;
void *dna;
};
population is a class contaning all the population for the process. besides, it contains 2 functions as well, init_func and fitter_func which are both pointers to functions.
pointer to function definition:
typedef dna_s (*init_func_t)();
typedef int (*fitter_func_t)(dna_s);
population class:
class population{
private:
// Parameters
int population_size;
node *pop_nodes;
// Functions
init_func_t init_func;
fitter_func_t fitter_func;
public:
population(int pop_size,init_func_t initialization_func){
// Insert parameters into vars.
this->population_size = pop_size;
this->init_func = initialization_func;
// Create new node array.
this->pop_nodes = new node[this->population_size];
for(int i = 0;i < this->population_size; i++){
dna_s curr_dna = this->init_func();
char *s = static_cast<char*>(curr_dna.dna);
cout << s << endl;
this->pop_nodes[i].update_dna(curr_dna);
}
}
};
You can see that in the constructor im inserting a pointer to function, init_func. this function is generating random words.
init_func:
dna_s init_func(){
string alphanum = "0123456789!##$%^&*ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
char init_s[STRING_SIZE+1] = {};
dna_s dna;
// Generate String
for(int i = 0; i < STRING_SIZE; i++){
init_s[i] = alphanum[rand() % alphanum.size()];
}
cout << "-->" << init_s << endl;
// Insert into struct.
dna.size = STRING_SIZE;
dna.dna = static_cast<void*>(&init_s);
// Return it
return dna;
}
the main function is not so interesting but it might be connected:
int main(){
// Init srand
srand(time(0));
// Parameters
int population_size = 10;
population pop(population_size, init_func);
}
now for the interesting part, whats the problem?
in the init_func the cout prints:
-->e%wfF
which is all good
but in the population class the cout prints:
e%Ω²(
and the wierd thing is the first 2 characters will always be the same, but the other 3 will always be this string Ω²(.
example:
-->XaYN7
XaΩ²(
-->oBK9Q
oBΩ²(
-->lf!KF
lfΩ²(
-->RZqMm
RZΩ²(
-->oNhMC
oNΩ²(
-->EGB6m
EGΩ²(
-->osafQ
osΩ²(
-->3#NQt
3#Ω²(
-->D62l0
D6Ω²(
-->tV#mu
tVΩ²(
Your code has a few lifetime issues. In your dna_S struct:
void *dna;
This is a pointer, it points to an object that exists elsewhere. Then, in your init_func:
dna_s init_func(){
...
char init_s[STRING_SIZE+1] = {};
dna_s dna;
...
dna.dna = static_cast<void*>(&init_s);
...
return dna;
}
init_s is a variable that exists inside init_func, you make dna point to that variable and then leave the function. init_s ceases to exist at this point, dna is pointing nowhere useful when the population constructor gets it, causing undefined behavior.
You could work around that by allocating memory with new char[], like you did for pop_nodes, but you are responsible for deleting that memory when it is no longer used.

Double being given wrong value when part of object

My code is supposed to be acting like a ATM machine where it can make different type of accounts and keep track of all the transactions and other information in the accounts
My problem is that the double balance which is stored in the BankAccount class which are stored in a vector to keep up with all the different accounts, well when I try and retrieve the balance it gives me "-9.25596e+061" as the balance no matter what I deposited or put in there, or even if it should be zero. The only time I got it to work is when i made balance a global variable but that is pretty much useless for the sake of the program.
activeAcounts is a vector
void BankAccount::deposit(double amount, string name)
{
balance += amount;
for (int i = 0; i < activeAccounts.size(); i++)
{
if (activeAccounts[i].getName() == name)
{
activeAccounts[i].setBalance(balance + amount);
}
}
}
Inquiry is suppose to just return the balance of an account specified by it's name
void BankAccount::inquiry(string n)
{
for (int i = 0; i < activeAccounts.size(); i++)
{
if (activeAccounts.at(i).getName() == n)
{
cout << "Balance: " << activeAccounts[i].getBalance() << endl;
}
}
}
You are more than likely not initializing balance to 0. Member variables need to be initialized, unlike globals. You should initialize balance in the BankAccount constructor.
In addition, since you are using vector, if you've coded a copy constructor and assignment operator for BankAccount, you need to make sure that balance is copied, else your copy will have a bogus balance value.
As to the global variable, when you made balance a global variable, global variables are automatically initialized to 0, which is probably why you didn't have a problem when it was made global.

C++ will this function leak?

I have started out to write a simple console Yahtzee game for practice. I just have a question regarding whether or not this function will leak memory. The roll function is called every time the dices need to be re-rolled.
What it does is to create a dynamic array. First time it is used it will store 5 random values. For the next run it will only re-roll all except for the dice you want to keep. I have another function for that, but since it isn't relevant for this question I left it out
Main function
int *kast = NULL; //rolled dice
int *keep_dice = NULL; //which dice to re-roll or keep
kast = roll(kast, keep_dice);
delete[] kast;
and here's the function
int *roll(int *dice, int *keep) {
srand((unsigned)time(0));
int *arr = new int[DICE];
if(!dice)
{
for(int i=0;i<DICE;i++)
{
arr[i] = (rand()%6)+1;
cout << arr[i] << " ";
}
}
else
{
for(int i=0;i<DICE;i++)
{
if(!keep[i])
{
dice[i] = (rand()%6)+1;
cout << "Change ";
}
else
{
keep[i] = 0;
cout << "Keep ";
}
}
cout << endl;
delete[] arr;
arr = NULL;
arr = dice;
}
return arr;
}
Yes, it can leak. Just for example, using cout can throw an exception, and if it does, your delete will never be called.
Instead of allocating a dynamic array yourself, you might want to consider returning an std::vector. Better still, turn your function into a proper algorithm, that takes an iterator (in this case, a back_insert_iterator) and writes its output there.
Edit: Looking at it more carefully, I feel obliged to point out that I really dislike the basic structure of this code completely. You have one function that's really doing two different kinds of things. You also have a pair of arrays that you're depending on addressing in parallel. I'd restructure it into two separate functions, a roll and a re_roll. I'd restructure the data as an array of structs:
struct die_roll {
int value;
bool keep;
die_roll() : value(0), keep(true) {}
};
To do an initial roll, you pass a vector (or array, if you truly insist) of these to the roll function, which fills in initial values. To do a re-roll, you pass the vector to re-roll which re-rolls to get a new value for any die_roll whose keep member has been set to false.
Use a (stack-allocated) std::vector instead of the array, and pass a reference to it to the function. That way, you'll be sure it doesn't leak.
The way you allocate memory is confusing: memory allocated inside the function must be freed by code outside the function.
Why not rewrite it something like this:
int *kast = new int[DICE]; //rolled dice
bool *keep_dice = new bool[DICE]; //which dice to re-roll or keep
for (int i = 0; i < DICE; ++i)
keep_dice[i] = false;
roll(kast, keep_dice);
delete[] kast;
delete[] keep_dice;
This matches your news and deletes up nicely. As to the function: because we set keep_dice all to false, neither argument is ever NULL, and it always modifies dice instead of returning a new array, it simplifies to:
void roll(int *dice, int *keep) {
for(int i=0;i<DICE;i++)
{
if(keep[i])
{
keep[i] = false;
cout << "Keep ";
}
else
{
dice[i] = (rand()%6)+1;
cout << "Change ";
}
}
cout << endl;
}
Also, you should move the srand call to the start of your program. Re-seeding is extremely bad for randomness.
My suggestion would be to take time out to buy/borrow and read Scott Meyers Effective C++ 3rd Edition. You will save yourselves months of pain in ramping up to become a productive C++ programmer. And I speak from personal, bitter experience.