Я написал небольшую программу, но главный деструктор работает неправильно. Вот код программы:
#include<iostream.h>
class citizen {
private:
char* name;
char* nationality;
public:
citizen(char* name, char* nationality) {
this->name = name;
this->nationality = nationality;
}
citizen(const citizen &obj) {
name = obj.name;
nationality = obj.nationality;
}
void display() {
cout << "Name: " << name << endl;
cout << "Nationality: " << nationality << endl;
}
~citizen() {
if(name){
delete[]name;
}
if(nationality) {
delete []nationality;
}
}
};
main() {
citizen obj1("Ali", "Pakistani");
obj1.display();
{
citizen obj2 = obj1;
}
obj1.display();
system("pause");
}
Я знаю, что в main
функции, где я назначаю состояние obj1
в obj2
, с того места они оба указывают на ту же область памяти. В то время как код citizen obj2 = obj1;
находится между двумя фигурными фигурными скобками.
{
citizen obj2 = obj1;
}
Поэтому после выполнения второй фигурной скобки obj2
должен уничтожить, а также удалить name
переменных и nationality
. И когда я вызываю obj1.display();
во второй раз он должен отображать мусор на экране.
Но obj1
все еще печатает точное имя, которое я предоставил в конструкторе, хотя его не должно быть.
Пожалуйста, объясните это поведение.
Спасибо всем. Я заменил этот код конструктора
citizen(char* name, char* nationality) {
this->name = name;
this->nationality = nationality;
}
с этим кодом
citizen(char* name, char* nationality){
this->name = new char[strlen(name)+1];
strcpy(this->name, name);
this->nationality = new char[strlen(nationality)+1];
strcpy(this->nationality, nationality);
}
и, наконец, он отлично работает. благодаря
Ваше delete[]
вызывает неопределенное поведение, потому что вы пытаетесь уничтожить строковые литералы. Все может случиться.
Даже если вы сами выделили память, вы все равно столкнетесь с неопределенным поведением, потому что будете пытаться получить доступ к уже удаленной памяти:
obj1.display();
{
citizen obj2 = obj1;
}
obj1.display(); // ILLEGAL
Поскольку вы не определяете оператор присваивания, будет использоваться генерируемый компилятором, который просто назначает указатели на ту же память - память, которую вы уничтожаете, а затем пытаетесь получить доступ.
Ваш конструктор копирования просто копирует указатели (как неявный, если вы не предоставили свой собственный), что означает, что оба объекта попытаются удалить те же массивы. Кроме того, вы указываете, что указатели указывают на строковые литералы, которые не должны удаляться вообще; вы должны удалять только те объекты, которые были созданы с помощью new
. Простым решением является делегирование управления памятью классу, предназначенному для этого:
std::string name;
std::string nationality;
Теперь вам не нужно беспокоиться о своем собственном деструкторе, конструкторе копирования или операторе копирования-назначения; и в качестве бонуса ваш класс также будет правильно перемещаться на С++ 11.
Если вам нравится иметь дело с проблемами памяти самостоятельно, тогда вам понадобятся ваши конструкторы для выделения новых буферов и копирования содержимого. Будьте осторожны с безопасностью исключений, поскольку вы пытаетесь манипулировать двумя отдельными динамическими ресурсами в одном классе, что всегда является рецептом ошибок. Вам также понадобится оператор присваивания копий (по правилу 3), и для эффективности вы также можете рассмотреть конструктор перемещения и оператор переадресации.
Кроме того, возможно, стоит обновить одну из версий этого языка в этом веке. <iostream.h>
не был стандартным заголовком около пятнадцати лет.
Этот код неисправен.
if(name){
delete[]name;
}
if(nationality){
delete []nationality;
}
Вы удаляете то, что вы не выделили на кучу, используя new
оператор.
Это просто удача, что ваш второй экран вызова работает.
Как отмечает Джек Эйдли, удаление не буквально удаляет значения, просто отмечает область памяти как пригодную для использования. Таким образом, если никакое другое приложение не выделяет и не изменяет эту освобожденную область, прежние значения могут оставаться там. Кроме того, после удаления вы по-прежнему сохраняете указатели на эти адреса.
Таким образом, вы получаете доступ к той же области памяти, в которой есть старые значения. Но это просто удача, потому что этот регион никем не изменился.
Чтобы предотвратить подобные ошибки, вы всегда должны назначать NULL неиспользуемым указателям, чтобы в следующий раз, когда вы получите ошибку " Нарушение прав доступа" при попытке получить к ним доступ. Некоторые компиляторы (например, MSVC) переписывают освобожденную память с сигнатурным значением (например, 0xDDDDDD), так что во время отладки вы можете легко поймать проблемы. Проверьте этот ответ за подробностями
Наконец, delete
должен соответствовать new
иначе поведение не определено, так что это снова просто удача. Я даже не могу запускать ваше приложение и показывать результаты. Он продолжает сбой и дает ошибки памяти.
name = NULL
и nationality = NULL
в деструктор, чтобы предотвратить несанкционированный доступ. Потому что доступ к свободному пространству памяти считается незаконным.
Другие указали на ошибку, связанную с строкой, но я думаю, что вы делаете гораздо более фундаментальную ошибку: delete
не уничтожает вещи *; он просто освобождает память для повторного использования и вызывает соответствующий деструктор. Это означает, что его часто полностью можно использовать удаленные объекты после операции delete
без возврата мусора.
* - В некоторых реализациях, в режиме __DEBUG
он будет __DEBUG
на выпущенной памяти, чтобы вы могли обнаружить эти ошибки, но это не является частью стандарта.
вы должны только delete
блок памяти, который был dynamically allocated
в классе с использованием new operator
citizen(char* aname, char* anationality){
size_t nameSize = strlen(aname)
size_t nationalitySize = strlen(anationality)
this->name = new char[nameSize];
this->nationality = new char[nationalitySize];
strcpy(this->name, aname, nameSize);
this->name[nameSize ] = NULL;
strcpy(this->nationality , anationality , nationalitySize);
this->nationality [nationalitySize] = NULL;
}
если вы создаете память в constructor
, то вы удаляете их в destructor
. и потому, что у вас есть какое-то назначение, тогда вы должны реализовать copy constructor
, WHICH создать память и скопировать данные с правой стороны operator
=
конечная мысль, вам лучше использовать строковый объект, если вы не имеете дело с указателями, чтобы избежать memory leaks
std::string
которые все это знают за вас.