Alex
Alex

Reputation: 2299

Deleting a dynamically allocated array from a function that initiates it C++

I'm writing a program in C++ that has to use dynamically allocated arrays from various structures (in separate files). A lot of times, I need to initiate these arrays inside of a function. Usually after initiating them, I write data to the array of a structure, or even an array inside of an array of structures, and then use the data later on. Therefore I don't use delete inside of the function.

For example, here is one of the structures that I use, a student structure:

struct Student {
    int id; // student ID
    char gradeOption; // either G or P
    double totalScore;
    std::string studentName;
    int* rawScores = NULL; // array that holds raw scores for a student
    // if no scores are entered for a specific ID, we check for NULL
    // we can then set the scores to 0
    std::string* finalGrade; // final grade given in course
};

And here is the function to input raw scores.

// input raw scores for each id
void inputRawScores(int gradedArtifacts, int id, Student* student) {
    student[id].rawScores = new int[gradedArtifacts];
    for(int i = 0; i < gradedArtifacts; i++) {
        std::cin >> student[id].rawScores[i];
    }
}

In my driver file, students also gets initialized with a value. Shown here:

 Student* students = new Student[numOfStudents]; // array of students

The problem is is that I use these raw scores, and the array of students for calculations in a separate file, and use them for output in other files, and in other methods. How would I go about deleting any of these?

Also I realize that using delete will delete the structure and the pointers inside of the structure, but not the objects that the pointers point to. So I'm assuming this ties back into the first question and I can't just issue a delete at the end of my program.

Edit: I'm sorry, as many others have pointed out I should have stated the restraints that I have on the project. I'm not allowed to uses: classes, vectors, functions inside of structs (like constructors, destructors).

Upvotes: 0

Views: 7297

Answers (4)

PuerNoctis
PuerNoctis

Reputation: 1384

Given your newly posted constraints, I think you could just implement a deletion-function to traverse through the Student array and do manual cleanup.

First, we create a function that deletes the dynamic objects of one single student. Note that we could have used Student&as the parameter type, but given the information in your question I am not sure if you have learned references yet, or if you are allowed to use them. So we stick with the pointer:

void cleanupStudent(Student* student) {
    delete[] student->rawScores;
    delete student->finalGrade;

    // EDIT: Setting pointers back to NULL after deletion provides
    // some improved safety and is good practice.
    student->rawScores = NULL;
    student->finalGrade = NULL;
}

After that we create a function that allows you to delete the complete Student array by looping through all the items in the array and calling the cleanup function:

void deleteStudents(Student* students, int studentCount) {
    for(int i = 0; i < studentCount; i++) {
        cleanupStudent(&students[i]);
    }

    delete[] students;
}

Here, please note the ampersand-symbol (&students[i]) which we require to get a pointer to the object (which is required as a parameter to the cleanup function). After that, the student array itself is deleted.

You can call these functions like this:

int numOfStudents = 16;
Student* students = new Student[numOfStudents];
deleteStudents(students, numOfStudents);

Or with a single student:

Student* student = new Student;
cleanupStudent(student);
delete student;

As you might have noticed we sometimes use delete and sometimes delete[]. The first one just deallocates memory that has been allocated with new. The latter does the same thing to memory that has been allocated with new[]. This is very important to get right, otherwise you will get runtime errors. Also, make always sure that EVERY pointer in your struct is initialized wih NULL (C) or nullptr (C++).

Since it seems that you are just learning C/C++ it is crucial to mention that the above code is very unsafe, and you could get into real problems if e.g. the studentCount is not matching the actual number of items in the array. But for now I guess you wouldn't know (or aren't allowed) to do better.

EDIT: I noticed that your finalGrade member is of type std::string*. Is there a reason for this to be a pointer? Because if you just want to store a string, you can just do a std::string, no reason there to be a pointer. Please don't confuse a C-String of type char* with a STL string std::string.

Upvotes: 1

ravi
ravi

Reputation: 10733

It would be very tedious to effectively delete everything you hve allocated using new in this case as you are passing pointers to other modules/functions.

You have two options over here:-

  1. Either replace those dynamic arrays ( rawscores/finalGrade ) with corresponding vectors.

  2. You can use smart pointers ( I think auto_ptr would suffice if you are not using containers ) to take care of memory mangement.

EDIT:-

One main issue you have to handle if you are creating raw student pointer is of ownership. Let's say you have allocated memory to Student* and then you are passing this object to several modules/functions. You need to take into account that you don't call delete when your pointer is still in use in some other module. Also it should also not call delete when it's already deleted in some module. That's why I pointed you to two options...

Upvotes: 0

PuerNoctis
PuerNoctis

Reputation: 1384

This answer is obsolete since the owner of this questions specified some constraints that do not allow constructors/destructors.

As far as I unterstand your question, you do not know how to delete the actual dynamically allocated rawScores and finalGrad objects in your Student object, right? If so, the answer is quite simple, you just use a Destructor:

struct Student {
    int id; // student ID
    char gradeOption; // either G or P
    double totalScore;
    std::string studentName;
    int* rawScores = nullptr;
    std::string* finalGrade = nullptr;

    // Disable copy and move assignment
    Student& operator=(const Student& rhs) = delete;
    Student& operator=(Student&& rhs) = delete;

    // Disable copy and move construction
    Student(const Student& rhs) = delete;
    Student(Student&& rhs) = delete;

    Student() {
        // Initialize members.
    }

    // Destructor
    ~Student() {
       delete[] rawScores;
       delete finalGrade;
   }
};

As soon as you deallocate a Student object using the delete operator:

Student* students = new Student[numOfStudents];
delete[] students; // <-- deallocation

The destructor is called for each object, which in turn will delete the dynamically allocated objects inside.

You don't have to bother if the pointers are NULL or not, calling delete on a pointer that is NULL is valid. Also, please initialize ALL pointers initially with nullptr when constructing the object (as shown above).

As long as you are not mixing runtimes accross different EXE/DLL boundaries you can delete the Student array from anywhere in your program, not matter in which file you allocate/deallocate it.

I hope this answers your question. If not, please be more specific about the problem you have.

EDIT: As pointed out in the comments, you should disable copy/move semantics if you are doing it this way (or implement them). But disabling them would practically pin you to dynamic allocation of the Student struct and give you less flexibility with most of the STL containers. Otherwise you could also use an std::vector instead of dynamically allocated arrays as pointed out in the other anwers.

Upvotes: 0

Mats Petersson
Mats Petersson

Reputation: 129524

Don't do that.

Your system is badly designed, your struct should be a class and internally handle the rawScores memory - using a std::vector would be the easiest part, but even if you use regular pointers, the key is that the information about how many there are, and where they are stored, should be tracked in a class.

In other words, the student structure should keep track of HOW MANY elements there are, and allocate/deallocate the memory as needed. It should not be done in the function inputRawScores - that function may well call the function for setNumRawScores and call the function for setRawScore(n, value), but not allocate memory in the reader function. That belongs as a member function in the student structure. Then introduce a destructor method for your student, that is responsible for deallocating the memory.

Of course, using std::vector will "hide" all this from you, and you just have to set the size (or use push_back).

Upvotes: 4

Related Questions