views:

247

answers:

5

I am experimenting with overloading operator delete, so that I can return a plain pointer to those who don't wish to work with smart pointers, and yet be able to control when the object is deleted.

I define a class Cat that is constructed with several souls, has an overloaded operator delete that does nothing, and destructor that decrements the number of souls (and also does some bragging). When souls reaches 0 the destructor calls the global ::delete, and the cat dies.

This sounds quite simple, but does not work as expected. Here's the code:

class Cat {
public:
    Cat(string n): name(n), souls(9)
    { cout << "Myaou... " << name << " is born\n"; }

    ~Cat();
    void operator delete(void *p) { cout << "!!! operator delete called\n"; }
    void report()
    { cout << name << "'s here, " << souls << " souls to spend\n"; }

    friend ostream& operator<< (const ostream& o, const Cat& cat);
private:
    void kill();
    const string name;
    int souls;
};

Cat::~Cat()
{
    cout << "!!! dtor called\n";
    kill();
}

void Cat::kill()
{
    if (--souls)
     cout << name << " is still alive! I have " << souls << " souls left.\n";
    else {
     cout << name << " is dying... good bye world!\n";
     ::delete((void*)this);
    }
}

ostream& operator<< (const ostream& o, const Cat& cat)
{
    return o << cat.name << "'s here, " << cat.souls << " souls to spend\n";
}

here's the main:

int main()
{
    Cat *p = new Cat("Mitzi");

    for (;;)
    {
     char c[100];
//   cout << *p;
     p->report();
     cout << "come on, hit me!";
     cin >> c;
     delete p;
    }
}

I'd expect that the loop would run for 9 times, and then something unpleasant (crash) would happen. However, this is the output:

Myaou... Mitzi is born
Mitzi's here, 9 souls to spend
come on, hit me!c
!!! dtor called
Mitzi is still alive! I have 8 souls left.
!!! operator delete called
's here, 8 souls to spend
come on, hit me!c
!!! dtor called
 is still alive! I have 7 souls left.
*** glibc detected *** /home/davidk/workspace/string_test/Debug/string_test: double free or corruption (fasttop): 0x080cd008 ***

Seems that after the first delete the name member is destroyed, and the next delete causes a crash. Any explanations? I am compiling with gcc on Linux, can be a compiler bug?

BTW, when I used the operator<<() as in cout << *p instead of repotr(), it was also weird: it entered an infinite loop of calling the constructor from within the operator<<(). What's going on here? :)

thanks!

+1  A: 

The destructor isn't responsible for freeing the memory and u've not prevented this from happening.

The first call is freeing the memory, the second call goes bang.

Dynite
first time I've seen "u've". I hope it's the last ;)
pavium
Abbreviations without being grammatically correct. I thought that would be seen in a positive light around here :).
Dynite
the overloaded operator delete should have prevented, and seems that it did, it's the member that went kaput, as people here are telling me.
davka
+2  A: 

Once the destructor for an object has been called, anything you do with the object is undefined. What you are trying to do is not possible.

anon
+14  A: 

You've misused just every C++ concept possible and this causes errors. When delete p; is called for the first time C++ calls the Cat::~Cat() destructor which implicitly destroys the std::string inside the entity. When delete p; is called the second time Cat::~Cat() is rerun and it reruns the destructor of the std::string and this causes undefined behavour crashing the program because I suppose std::string::~string() doesn't nullify the pointer to the buffer and so when std::string::~string() tries to release the buffer for the second time with the same address this leads to famous double-free.

sharptooth
yep, missed the std::string's destructor, correct. thanks!
davka
+1  A: 

You should know that you can't perform delete on a single address more than once.

Ashish
+2  A: 

operator delete calls the object destructor and after that you are in no man's land. As others pointed out, what you are trying to do is not possible.

What you are doing is also a bit dodgy in the way of inconsistent behaviour when the object is constructed on the heap and on the stack.

With your idea to override operator delete the object will stay alive depending on some internal logic (the number of lives has reached 9) if constructed using new.

When constructed on the stack (Cat cat("Chesire cat");) the object will always get destructed when it goes out of scope. In order to achive what you are trying to do you will also need to change the bahaviour of the destructor to "stop destructing". This is not possible, also for very good reasons.

So, if you want ref counting, just implement your own mechanism. After all, you can't call yourself a C++ programmer if you haven't done your own ref count memory management at least once :))

Igor Zevaka
That's a good point, thanks, but as I said I was experimenting, so I mostly wanted to play with delete and see how it works. I could have complemented this with an overloaded new operator which would set some flag that the object was created on the heap, for instance. I personally would have used shared_ptr rather than re-implement it.
davka