2

I have this code on 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");

}

And when I run it:

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!

I have 3 questions:

  1. After I killed instance "lucife" the first time, why did the 2 methods GetHealth() and GetMagic() still worked?
  2. The second time I called ~Human() on instance "lucife", why didn't it print out "Lucife is killed!" like the first time? What exactly happened here? Is the name value deleted?
  3. Does return Human::health and return this->health mean the same thing? I tried it and see that there is no difference. I think both of them return the health of the instance on which the method was called ("lucife" in this case).

Many thanks to you

ttriet204
  • 353
  • 1
  • 5
  • 13
  • 2
    Welcome to the world of C/C++. Calling your destructor 'explictly' does not gurantee you the object will be immediately set to 0x00000000. Plus, you are calling the object's member functions, which still exist at the same location of binary. – Dean Seo Jun 04 '15 at 04:22

4 Answers4

3

You are seeing the symptoms of undefined behavior.

From the C++ Standard:

12.4 Destructor

...

15 Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended (3.8). [ Example: if the destructor for an automatic object is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. — end example ]

R Sahu
  • 204,454
  • 14
  • 159
  • 270
1

Might be the reason why the contents are still there (correct me if I'm wrong).

From C++, Free-Store vs Heap,

The free store is one of the two dynamic memory areas, allocated/freed by new/delete. Object lifetime can be less than the time the storage is allocated; that is, free store objects can have memory allocated without being immediately initialized, and can be destroyed without the memory being immediately deallocated. During the period when the storage is allocated but outside the object's lifetime, the storage may be accessed and manipulated through a void* but none of the proto-object's nonstatic members or member functions may be accessed, have their addresses taken, or be otherwise manipulated.

And I agree with @R Sahu that what you are trying to do is undefined.

Community
  • 1
  • 1
Rupesh Yadav.
  • 894
  • 2
  • 10
  • 24
1

I'm still learning about stackoverflow and those who post here. Strange environment....

Okay... First, use a pointer to your pointer to your object, then your "const" declarations and the variables defined... I'm not quite sure what your goal is here but they're mismatched (in the way you're using them). Next, you don't need to use the "this" pointer in your method.

Remember that memory management (in C++) is YOUR responsibility. If you need / want to call the destructor, then do it through the "delete" process.

Human *pLucifer = new Human (20,10,"Lucifer");

cout << pLucifer->GetHealth()<GetMagic()<

delete pLucifer;

Disregard the other answer posts you've received as the "Human" object was created on the stack and even though you've called the destructor explicitly, the memory management will not be processed until the function terminates (in this case... main). So the reason your data is still accessible after you've explicitly called the destructor (naughty! Don't do that) is because the stack is still in tact and however you reference it (the stack), you'll be able to get what data resides there. Hope it helps

  • Thank you! And welcome to Stackoverflow - the awesome community of programmers. About your answer, I'd like to ask you: - Why is it a good practice to create a pointer like you did Human *pLucifer = new Human(20,10,"Lucifer"); instead of the way that I did Human lucifer(20,10,"Lucifer"); - Is it right that if I initialize your way, the "lucifer" instance will be put on the heap in stead of the stack and vice versa? About my using "this" keyword: I know it's not necessary, I just read that some consider it to be good coding style. – ttriet204 Jun 04 '15 at 06:45
  • 1
    Thanks for your welcome!! Allocating objects off the heap (in lieu of the stack) allows you to manage your objects beyond the lifetime of a function. If your requirements aren't such that you need an instantiation beyond the scope of a function, it is still a good referencing scheme (instead of stack instantiated objects). The reason is that the constructor for your objects will be called directly after the stack frame is created and the destructor called directly before "unwinding" the stack. – Thomas Vincent Jun 05 '15 at 04:05
1

As @R and @Rupesh mentioned, you are seeing the behavior that's undefined.

However, if I may provide you with a little more explanation about what's going on in your very specific code in that specific environment, this might be it.

  1. After I killed instance "lucife" the first time, why did the 2 methods GetHealth() and GetMagic() still worked?

First of all, do not call your object's destructor explicitly like that. It will automatically be fired as soon as you get out of the scope main. That's never a good practice.

The reason you can still invoke GetHealth() and GetMagic() is that they are just functions with its first hidden argument to this.

You might be surprised if you see

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

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

able to be compiled and running okay. (depending on your environment).

So even if you invoked a destructor explicitly in the middle of scope hoping that it will literally destruct everything inside, you might still be able to reach this without dereferencing nullptr and successfully give this to the intended functions with any luck.

  1. The second time I called ~Human() on instance "lucife", why didn't it print out "Lucife is killed!" like the first time? What exactly happened here? Is the name value deleted?

It's because when Human::~Human() is fired, the destructor of std::string gets also fired, which ends up tidying.

  1. Does return Human::health and return this->health mean the same thing? I tried it and see that there is no difference. I think both of them return the health of the instance on which the method was called ("lucife" in this case).

No, they are different. However in your code, Human::health is just converted to this->health because you used that inside the class Human.

Dean Seo
  • 5,486
  • 3
  • 30
  • 49
  • Thanks! Regarding your answer, I learned that it is never a good thing to explicitly call the destructor. But then, what if I wanted to delete an instance? Should I use delete instead? Also, thank you all for helping me. Since this answer address all my questions in a very clear and detail manner, I will mark this as the answer, but again, thank you all! – ttriet204 Jun 04 '15 at 13:17
  • 1
    @ttriet204 C/C++, unlike any other managed languages such as Java, there are 2 ways of creating an object. One is `Human lucife;` the other is `Human * lucife = new Human();`. You need to `delete` the latter to call the destructor. You must NOT `delete` the first one because the destructor is automatically called after all. – Dean Seo Jun 04 '15 at 22:16