views:

316

answers:

6

I have a piece of code where I can call destructor multiple times and access member functions even the destructor was called with member variables' values preserved. I was still able to access member functions after I called delete but the member variables were nullified (all to 0). And I can't double delete. Please kindly explain this. Thanks.

#include <iostream>
using namespace std;

template <typename T>
void destroy(T* ptr)
{
    ptr->~T();
}

class Testing
{
public:
    Testing() : test(20)
    {

    }

    ~Testing()
    {
        printf("Testing is being killed!\n");
    }

    int getTest() const
    {
        return test;
    }

private:
    int test;
};

int main()
{
    Testing *t = new Testing();
    cout << "t->getTest() = " << t->getTest() << endl;

    destroy(t);
    cout << "t->getTest() = " << t->getTest() << endl;

    t->~Testing();
    cout << "t->getTest() = " << t->getTest() << endl;

    delete t;
    cout << "t->getTest() = " << t->getTest() << endl;

    destroy(t);
    cout << "t->getTest() = " << t->getTest() << endl;

    t->~Testing();
    cout << "t->getTest() = " << t->getTest() << endl;

    //delete t; // <======== Don't do it! Double free/delete!
    cout << "t->getTest() = " << t->getTest() << endl;

    return 0;
}
+15  A: 

This is an exhibit of undefined behavior. Call a member function through a pointer that's been deleted and anything goes - the compiler and runtime aren't required to check for this error, but you certainly can't count on this working.

This falls into a similar category as using memory that's been freed - you might find your old data there (or you might not). You might cause the program to crash (or not). You might even be able to change the data without anything complaining.

But in any case, it's a programming error.

Michael Burr
May I ask why delete can be called once while the destructor can be called multiple times?
Viet
@Viet, the delete operator invokes the destructor and then, in addition to that, releases memory. The destructor, itself, does not free the memory in which the object resides (which is why, for example, a destructor works even when you use automatic storage allocation for creating objects). Technically speaking, you should not invoke a destructor more than once either (you should not invoke destructors directly and should use the scoping or the delete operator to cause it to be invoked), but your implementation of your destructor doesn't check for multiple invocations, while delete may.
Michael Aaron Safyan
Thanks Billy. You got me right. I'm actually writing a memory pool with the destroy(T* ptr) template function. Not sure if delete can be templated like destroy?
Viet
Calling the destructor explicitly as you are doing doesn't involve freeing the memory. It's still a programming error, just nothing caught it for you. Freeing the memory twice (by calling delete) twice probably resulted in the heap manager crashing or otherwise complaining. Note that while calling the destructor explicitly is permitted, it's not normally done since the desctructor will be called via `delete` or when the object goes out of scope (or other RAII scenarios). The only time I've seen valid instances of calling the dtor explicitly is when using placement `new`.
Michael Burr
@Viet, you can't - it just looks like you can. Undefined behavior means anything is possible, including looking like it worked.
Mark Ransom
@Michael Burr: Please elaborate more on the placement new and destructor: The good, the bad and the ugly. Thanks.
Viet
There are already good answers on placement new on SO. See also: C++ FAQ (http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10)
Georg Fritzsche
+6  A: 

Just because you are accessing a not longer valid object doesn't mean your program has to explode, it just means your program can explode.

It is undefined behavior, which means anything can happen, it might even appear to do the correct thing.

sth
And demons will fly from your nose, don't forget.
greyfade
@greyfade - no, demons *could* fly from your nose. There are no guarantees either way.
Mark Ransom
@Mark: Good point.
greyfade
+5  A: 

"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"

C++ draft standard §12.4.12

As noted by others, this doesn't mean the implementation will always do something obviously unwanted (like a segmentation fault). It means it can do whatever is most convenient.

Matthew Flaschen
+1 thanks Matthew!
Viet
+2  A: 

You've invoked undefined behaviour, all bets are off.

Joe Gauterin
+2  A: 

You really shouldn't call a class' destructor (unless you used placement new) however to answer your question, once memory has been deleted accessing pointers to that memory results in undefined behavior. In your case it appears as though the memory you are using has been freed for future use but it hasn't yet been overwritten. So you are still able to access it but there are no guarantees as to when that memory is going to be used by something else.

Niki Yoshiuchi
+1 Thanks Niki! Yes, you are right. I'm writing a memory pool with placement new and delete. Not sure if I can write placement delete inside template like the destroy(T* ptr). Please advise.
Viet
+2  A: 

Expressly calling the destructor, as you do in destroy(), and directly in main() doesn't actually cause an object to be destroyed in C++. Only the delete statement in this code does that. Since T's destructor is benign (it just prints), this has almost no effect.

Since none of the member functions are virtual, calling them after the destruction will still get to the right code to execute. Once there, the this pointer may be invalid (after your call to delete), but that doesn't stop the code from dereferencing the pointer and returning the value of the int member value.

MtnViewMark
+1 Thanks for pointing out the virtual.
Viet