0

What I want to do is overload the delete operator for my class to automatically set the pointer to null after deleting it so I can safe delete it inside a member function, like that:

# include <iostream>

class Buddy
{
    public:

    Buddy(void) : _hp(100) { }
    ~Buddy(void) { std::cout << "Buddy is dead!" << std::endl; }

    void operator delete(void * ptr)
    {
        ::delete (Buddy*)ptr;
        ptr = nullptr;
    }

    void getDamage(int amount)
    {
        _hp -= amount;
        if (_hp <= 0)
            delete this;
    }


    private:

    int _hp;
};

void hitBuddy(Buddy *buddy)
{
    if (!buddy)
        std::cout << "Buddy is already dead!" << std::endl;
    else
    {
        std::cout << "Hitting buddy!" << std::endl;
        buddy->getDamage(70);
    }
}

int main(void)
{
    Buddy *dude = new Buddy;

    hitBuddy(dude);
    hitBuddy(dude);
    hitBuddy(dude);
    return (0);
}

But it doesn't set the pointer to null and it calls two times the destructor, is there a proper way to set the pointer to null inside the class like I want to do? (OUTPUT)

Hitting buddy!
Hitting buddy!
Buddy is dead!
Buddy is dead!
Hitting buddy!
Buddy is dead!
Buddy is dead!
a.out(79480,0x10d3cbdc0) malloc: *** error for object 0x7fe345c05790: pointer being freed was not allocated
a.out(79480,0x10d3cbdc0) malloc: *** set a breakpoint in malloc_error_break to debug
[1]    79480 abort      ./a.out
Fayeure
  • 1,181
  • 9
  • 21
  • 1
    You'd need a `void operator delete(void*&);` for that. Nope ... – Ted Lyngmo Jan 22 '21 at 00:49
  • I suspect that there's an [XY problem](https://en.wikipedia.org/wiki/XY_problem) hidden here. Have you perhaps started to paint yourself into a corner and try to get out by rebuilding the skyscraper bottom-up? There's nothing wrong with redesigning as Eugene implies. It's just part of the process. – Ted Lyngmo Jan 22 '21 at 01:00
  • Have a read of https://stackoverflow.com/questions/3150942/is-delete-this-allowed I think the code also has undefined behaviour after the 1st call to `delets this;` – Richard Critten Jan 22 '21 at 01:04
  • You also leak a `dude`... – Ted Lyngmo Jan 22 '21 at 01:05
  • @RichardCritten `delete this` is well defined. – Ted Lyngmo Jan 22 '21 at 01:06
  • @TedLyngmo it is but subsequent access to the deleted object is not. – Richard Critten Jan 22 '21 at 01:07
  • @RichardCritten That does indeed sound undefined :-) – Ted Lyngmo Jan 22 '21 at 01:08
  • No.The standard permits an `operator delete()` that accepts a `void *` argument (passed by value), not one that accepts a `void *&` (which, if permitted, would allow what ou want). The real solution is to use standard smart pointers (or containers if you need to manage collection of objects rather than one) and - wherever possible - avoid explicit use of any form of operators `new` or `delete` directly. Doing that tends avoids a need to set pointers to null after deleting them, because the smart pointer destroys the object it manages when it, itself, ceases to exist. – Peter Jan 22 '21 at 01:10

3 Answers3

4

No. It is not possible to overload operator delete so that it sets the pointer to null.

This is because the standard requires the first parameter to operator delete (which is the pointer to be deleted) to have type void* and thus the operator delete only receives a copy of the pointer and cannot modify the original argument. It cannot have type void*&. (In C++20, "destroying delete" is supported, in which case the parameter shall have type C* but still no references are allowed.)

The implementation is allowed to zero out pointers after a delete-expression as a technique to help the programmer catch use-after-free bugs but there is no way to do this yourself.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
2

is there a proper way to set the pointer to null inside the class like I want to do?

No, it is neither possible nor helpful.

This

void operator delete(void * ptr)
    {
        ::delete (Buddy*)ptr;
        ptr = nullptr;
    }

does not work because the function receives ptr by value, so ptr = nullptr; only modifies the local copy.

Even if it was possible, it would not help if Buddy is not created by new. And would be a terrible design in any case.

Eugene
  • 6,194
  • 1
  • 20
  • 31
  • In my program I was assuming that Buddy is created only with new, so the best way to add a "death state" to Buddy is to check it's life in main and delete it like this? `if (dude->getLife() <= 0) { delete dude; dude = nullptr; } ` or using smart ptrs? – Fayeure Jan 22 '21 at 08:43
2

With smart ptr abuse, you might do:

class Buddy
{
    Buddy() = default;
    Buddy(const Buddy&) = delete;
    Buddy& operator=(const Buddy&) = delete;
public:

    ~Buddy() { std::cout << "Buddy is dead!" << std::endl; }

    void getDamage(int amount)
    {
        _hp -= amount;
        if (_hp <= 0)
            self.reset();
    }

    static std::weak_ptr<Buddy> make() { auto p = new Buddy; return p->self; }

private:
    int _hp = 100;
    std::shared_ptr<Buddy> self{this};
};

void hitBuddy(std::weak_ptr<Buddy> buddy_weak)
{
    auto buddy = buddy_weak.lock();

    if (!buddy) {
        std::cout << "Buddy is already dead!" << std::endl;
    } else {
        std::cout << "Hitting buddy!" << std::endl;
        buddy->getDamage(70);
    }
}

int main()
{
    std::weak_ptr<Buddy> dude = Buddy::make();

    hitBuddy(dude);
    hitBuddy(dude);
    hitBuddy(dude);
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302