Удаление динамически размещенного массива из функции, которая его инициирует. C ++

0

Я пишу программу в C++, которая должна использовать динамически распределенные массивы из различных структур (в отдельных файлах). Много раз мне нужно инициировать эти массивы внутри функции. Обычно после их запуска я записываю данные в массив структуры или даже массив внутри массива структур, а затем позже использую данные. Поэтому я не использую delete внутри функции.

Например, вот одна из структур, которые я использую, структуру студента:

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
};

И вот функция для ввода исходных баллов.

// 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];
    }
}

В моем файле драйвера ученики также инициализируются значением. Показаны здесь:

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

Проблема в том, что я использую эти исходные баллы и массив студентов для вычислений в отдельном файле и использую их для вывода в других файлах и другими способами. Как я могу удалить некоторые из них?

Также я понимаю, что использование delete удалит структуру и указатели внутри структуры, но не объекты, на которые указывают указатели. Поэтому я беру эту связь в первый вопрос, и я не могу просто delete в конце моей программы.

Редактировать: Извините, как многие другие отметили, что я должен был указать ограничения, которые у меня есть в проекте. Мне не разрешено использовать: классы, векторы, функции внутри структур (например, конструкторы, деструкторы).

  • 0
    Вы можете просто выполнить команду delete [] после удаления всех других динамически размещаемых объектов, хранящихся в приложении Student .
  • 4
    Простое решение не использовать указатели и new вообще.
Показать ещё 10 комментариев
Теги:
arrays
pointers
struct
dynamic-memory-allocation

4 ответа

1
Лучший ответ

Учитывая ваши недавно опубликованные ограничения, я думаю, вы могли бы просто реализовать функцию удаления, чтобы проходить через массив Student и выполнять ручную очистку.

Во-первых, мы создаем функцию, которая удаляет динамические объекты одного студента. Обратите внимание, что мы могли бы использовать Student& как тип параметра, но, учитывая информацию в вашем вопросе, я не уверен, что вы уже изучили ссылки, или если вам разрешено использовать их. Поэтому мы придерживаемся указателя:

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;
}

После этого мы создаем функцию, которая позволяет удалить полный массив Student, перейдя через все элементы в массиве и вызывая функцию очистки:

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

    delete[] students;
}

Здесь обратите внимание на символ амперсанда (&students[i]), который нам нужен, чтобы получить указатель на объект (который требуется как параметр для функции очистки). После этого сам массив студентов удаляется.

Вы можете вызвать эти функции следующим образом:

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

Или с одним учеником:

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

Как вы могли заметить, мы иногда используем delete и иногда delete[]. Первый освобождает память, которая была выделена new. Последнее делает то же самое в памяти, которое было выделено new[]. Это очень важно, потому что вы получите ошибки во время выполнения. Кроме того, всегда убедитесь, что указатель EVERY в вашей структуре инициализирован с помощью NULL (C) или nullptr (C++).

Поскольку кажется, что вы просто изучаете C/C++, очень важно упомянуть, что вышеуказанный код очень опасен, и вы можете studentCount реальными проблемами, если, например, studentCount не соответствует фактическому количеству элементов в массиве. Но на данный момент, я думаю, вы не знали бы (или не допускались) делать лучше.

EDIT: Я заметил, что ваш член finalGrade имеет тип std::string*. Есть ли причина, по которой это указатель? Потому что, если вы просто хотите сохранить строку, вы можете просто сделать std::string, без причины для указателя. Пожалуйста, не путайте C-String типа char* со строкой STL std::string.

  • 0
    Большое спасибо за ответ! И единственная причина, по которой я сделал finalGrade строкой, заключалась в том, что, если для опции градации задано значение pass / not pass, я должен вывести finalGrade как «NP». Поскольку у каждого учащегося есть разные итоговые оценки, в зависимости от того, какой набор контрольных точек используется, я подумал, что буду использовать указатель, чтобы можно было выделить память для создания массива итоговых оценок, которые будут сохраняться для вывода.
  • 0
    Справедливо, но убедитесь, что если вы когда-нибудь сделаете new std::string[] вам также придется delete[] оценки в функции очистки. В настоящее время мы делаем только регулярное delete .
4

Не делай этого.

Ваша система плохо разработана, ваша struct должна быть class и внутренне обрабатывать память rawScores - использование std::vector будет самой легкой частью, но даже если вы используете обычные указатели, ключ заключается в том, что информация о том, сколько из них существует, и где они хранятся, следует отслеживать в class.

Другими словами, структура student должна отслеживать КАК МНОГО элементов, а также выделять/освобождать память по мере необходимости. Это не должно быть сделано в функции inputRawScores - эта функция может вызвать функцию setNumRawScores и вызвать функцию setRawScore(n, value), но не распределять память в функции считывателя. Это входит в функцию-член в структуре student. Затем введите метод деструктора для вашего student, который отвечает за освобождение памяти.

Конечно, использование std::vector будет "скрывать" все это от вас, и вам просто нужно установить размер (или использовать push_back).

0

Было бы очень утомительно эффективно удалять все, что вы назначили, используя новое в этом случае, когда вы передаете указатели на другие модули/функции.

Здесь у вас есть два варианта:

  1. Либо замените эти динамические массивы (rawscores/finalGrade) соответствующими векторами.

  2. Вы можете использовать интеллектуальные указатели (я думаю, auto_ptr будет достаточным, если вы не используете контейнеры), чтобы заботиться об управлении памятью.

РЕДАКТИРОВАТЬ:-

Одна из основных проблем, с которой вам приходится обращаться, если вы создаете необработанный указатель ученика, принадлежит вам. Скажем, вы выделили память для ученика *, а затем передаете этот объект нескольким модулям/функциям. Вы должны учесть, что вы не вызываете delete, когда ваш указатель все еще используется в каком-либо другом модуле. Также он также не должен вызывать удаление, когда он уже удален в некотором модуле. Вот почему я указал вам на два варианта...

0

Этот ответ устарел, так как владелец этих вопросов указал некоторые ограничения, которые не позволяют конструкторам/деструкторам.

Насколько я упускаю ваш вопрос, вы не знаете, как удалить фактические динамически выделенные rawScores и finalGrad в объекте Student, правильно? Если это так, ответ довольно прост, вы просто используете деструктор:

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;
   }
};

Как только вы освободите объект Student с помощью оператора delete:

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

Деструктор вызывается для каждого объекта, который, в свою очередь, удаляет динамически выделенные объекты внутри.

Вам не нужно беспокоиться о том, что указатели имеют NULL или нет, вызывая delete указателя, который имеет NULL, действителен. Кроме того, сначала инициализируйте ВСЕ указатели с помощью nullptr при построении объекта (как показано выше).

Пока вы не смешиваете время автономной работы на разных границах EXE/DLL, вы можете delete массив Student из любой точки вашей программы, независимо от того, в каком файле вы его выделяете/освобождаете.

Надеюсь, это ответит на ваш вопрос. Если нет, будьте более конкретны в отношении проблемы, которая у вас есть.

EDIT: Как указано в комментариях, вы должны отключить семантику копирования/перемещения, если вы делаете это таким образом (или реализуете их). Но отключение их практически привязало бы вас к динамическому распределению структуры Student и уменьшало бы гибкость большинства контейнеров STL. В противном случае вы можете также использовать std::vector вместо динамически распределенных массивов, как указано в других анверах.

  • 0
    Очень хорошее объяснение. Но вы упустили одну важную вещь в этом. Как бы вы справились с проблемами владения указателем Student, если он передается нескольким модулям / функциям.
  • 1
    Теперь вам нужен конструктор копирования и оператор присваивания.
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню