метод для удаленного экземпляра класса все еще работает?

У меня есть этот код на Visual C++ 2010

#include <iostream>
#include <string>

using namespace std;

class Human {
private:
    int magic;
    int health;
    string name;
public:
    int GetMagic() const;
    int GetHealth() const;
    Human(int, string);
    ~Human();

};
//helper
int Human::GetHealth() const {
    cout <<"This returns Human::health" << endl;
    return Human::health;

}
int Human::GetMagic() const {
    cout <<"This returns this->magic"<< endl;
    return this->magic;

}

//con/destructor
Human::Human(int a, int b, string c): health(a), magic(b), name(c)
{
    cout<<c<<" is born!"<<endl;
}
Human::~Human() 
{
    cout <<this->name << " is killed!" << endl;
}

int main (){
    Human lucife(20,10,"Lucife");
    cout << lucife.GetHealth()<<endl;
    cout << lucife.GetMagic()<<endl;
    lucife.~Human();

    cout << lucife.GetHealth()<<endl;
    cout << lucife.GetMagic()<<endl;
    cout<<endl;


    lucife.~Human();

    system("pause");

}

И когда я запускаю его:

Lucife is born!
This returns Human::health;
20
This returns this->magic
10
Lucife is killed!
This returns Human::health
20
This returns this->magic
10
 is killed!

У меня есть 3 вопроса:

  1. После того, как я убил экземпляр "lucife" в первый раз, почему 2 метода GetHealth() и GetMagic() все еще работали?
  2. Во второй раз я вызвал ~Human() для экземпляра "lucife", почему он не распечатал "Lucife убит!" как в первый раз? Что именно здесь произошло? Значение имени удалено?
  3. Означает ли return Human::health и return this->health одно и то же? Я пробовал и вижу, что разницы нет. Я думаю, что оба они возвращают работоспособность экземпляра, для которого был вызван метод (в данном случае «lucife»).

Большое спасибо Вам


person ttriet204    schedule 04.06.2015    source источник
comment
Добро пожаловать в мир C/C++. Вызов вашего деструктора «явно» не гарантирует, что объект будет немедленно установлен на 0x00000000. Кроме того, вы вызываете функции-члены объекта, которые все еще существуют в том же месте двоичного файла.   -  person Dean Seo    schedule 04.06.2015


Ответы (4)


Как упоминали @R и @Rupesh, вы видите поведение, которое не определено.

Однако, если я могу предоставить вам немного больше объяснений о том, что происходит в вашем очень конкретном коде в этой конкретной среде, это может быть оно.

  1. После того, как я убил экземпляр "lucife" в первый раз, почему 2 метода GetHealth() и GetMagic() все еще работали?

Прежде всего, не вызывайте destructor вашего объекта явно так. Он автоматически сработает, как только вы выйдете из зоны действия main. Это никогда не бывает хорошей практикой.

Причина, по которой вы все еще можете вызывать GetHealth() и GetMagic(), заключается в том, что это просто функции с первым скрытым аргументом this.

Вы удивитесь, если увидите

class AAA
{
public:
    void f() { std::cout << "Hello world" << std::endl; }
}

((AAA*) 0)->f();

может быть скомпилирован и работает нормально. (в зависимости от вашего окружения).

Таким образом, даже если вы вызвали деструктор явно в середине области видимости, надеясь, что он буквально уничтожит все внутри, вы все равно сможете достичь this без разыменования nullptr и успешно передать this предполагаемым функциям, если повезет.

  1. Во второй раз я вызвал ~Human() для экземпляра "lucife", почему он не распечатал "Lucife убит!" как в первый раз? Что именно здесь произошло? Значение имени удалено?

Это потому, что при запуске Human::~Human() деструктор std::string также запускается, что в конечном итоге приводит к уборке.

  1. Означает ли return Human::health и return this->health одно и то же? Я пробовал и вижу, что разницы нет. Я думаю, что оба они возвращают работоспособность экземпляра, для которого был вызван метод (в данном случае «lucife»).

Нет, они разные. Однако в вашем коде Human::health просто преобразуется в this->health, потому что вы использовали это внутри класса Human.

person Dean Seo    schedule 04.06.2015
comment
Спасибо! Что касается вашего ответа, я узнал, что никогда не стоит явно вызывать деструктор. Но что, если я хочу удалить экземпляр? Должен ли я использовать удаление вместо этого? Кроме того, спасибо всем за помощь мне. Поскольку этот ответ отвечает на все мои вопросы очень четко и подробно, я отмечу это как ответ, но еще раз спасибо всем! - person ttriet204; 04.06.2015
comment
@ ttriet204 C/C++, в отличие от любых других управляемых языков, таких как Java, существует два способа создания объекта. Один Human lucife; другой Human * lucife = new Human();. Вам нужно delete последнее, чтобы вызвать деструктор. Вы НЕ должны delete первый, потому что деструктор в конце концов вызывается автоматически. - person Dean Seo; 05.06.2015

Вы видите симптомы неопределенного поведения.

Из стандарта С++:

12.4 Деструктор

...

15. Как только для объекта вызывается деструктор, этот объект больше не существует; поведение не определено, если деструктор вызывается для объекта, время жизни которого закончилось (3.8). [ Пример: если деструктор для автоматического объекта вызывается явно, а затем блок покидает таким образом, который обычно вызывал бы неявное уничтожение объекта, поведение не определено. — конец примера ]

person R Sahu    schedule 04.06.2015

Возможно, поэтому содержимое все еще там (поправьте меня, если я ошибаюсь).

Из C++, Free-Store vs Heap,

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

И я согласен с @R Sahu, что то, что вы пытаетесь сделать, не определено.

person Rupesh Yadav.    schedule 04.06.2015

person    schedule
comment
Благодарю вас! И добро пожаловать в Stackoverflow — потрясающее сообщество программистов. Что касается вашего ответа, я хотел бы спросить вас: - Почему рекомендуется создавать указатель, как вы сделали Human *pLucifer = new Human(20,10,Lucifer); вместо того, как я делал Human lucifer(20,10,Lucifer); - Правильно ли, что если я инициализирую по-вашему, экземпляр lucifer будет помещен в кучу, а не в стек, и наоборот? О моем использовании этого ключевого слова: я знаю, что это не обязательно, я только что прочитал, что некоторые считают это хорошим стилем кодирования. - person ttriet204; 04.06.2015
comment
Спасибо за ваш прием!! Выделение объектов из кучи (вместо стека) позволяет вам управлять своими объектами после окончания срока службы функции. Если ваши требования не таковы, что вам нужно создание экземпляра за пределами функции, это все же хорошая схема ссылок (вместо объектов, созданных в стеке). Причина в том, что конструктор для ваших объектов будет вызываться непосредственно после создания кадра стека, а деструктор вызывается непосредственно перед раскручиванием стека. - person Thomas Vincent; 05.06.2015