Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Leaving "zombie" flag upon object destruction

Tags:

c++

I want to explicitly destroy an object (call the destructor on it and all its fields), but it may happen that I still hold some pointers to the object in question. Thus, I don't want to yet free the memory; instead I would like to leave a sort of a flag "I am a destroyed object".

I came with an idea of the following approach:

class BaseClass { //all objects in question derive from this class
public:
    BaseClass() : destroyed(false) {}
    virtual ~BaseClass() {}
private:
    bool destroyed;
public:
    bool isDestroyed() { return destroyed; }
    void destroy() {
        this->~BaseClass(); //this will call the virtual destructor of a derivative class
        new(this) BaseClass();
        destroyed=true;
    }
};

When destroy is called, I basically destroy the whatever object I had (perhaps a derivative one) and create a new "zombie" one in that very same place. As a result I hope to achieve:

  • Any other pointer ptr previously pointing to this object can still call ptr->isDestroyed() to verify its existence.
  • I am aware that if I don't check the flag of the zombie and try to access fields belonging to any derived object, bad things may happen
  • I am aware that the zombie object still consumes as much memory as the destroyed object (as it may be a derivative of BaseClass)
  • I still have to free memory of the destroyed object. I hope however, that calling delete is still correct?

Questions:

Are there any other problems which I should consider when using the above pattern?

Will calling delete on the zombie object correctly free whole memory consumed by the previous (normal) object?


While I appreciate your input on how to do it differently, and I may be inclined to do it your way - I would still like to understand all the risks that the above code poses.

like image 670
CygnusX1 Avatar asked Oct 21 '25 08:10

CygnusX1


2 Answers

You got some nasty comments to your question. Now I don't think they are deserved although there may be better ways to do what you want. I understand where you are coming from but actually you are using the destructor the same way you would use the reset function you refuse to write. Actually you gain nothing from calling a destructor since calling a distructor has nothing to do with actually deleting or resetting anything unless you actually write the code to do it within the destructor.

As to your question about the placement new:

As you may know already the placement new doesn't allocate any memory so calling it will just create the object in the same place. I understand that is exactly what you want but it's just not ncessary. Since you don't call delete on your object just destroy, you can set destroyed to true without initializing the class.

To sum it up:

  1. If you use the destructor as a regular virtual function you gain nothing. Don't do it since you can get into trouble if a destructor is called twice
  2. A call to a placement new will not allocate memory and just perform needless initialization. You can just set destroyed to true.

To do what you want to do correctly and gain the benefits of destructors, you should overload the new and delete operators of your classes and use the normal destruction mechanism. You can then opt not to release the memory but mark it as invalid or maybe release most of the memory but leave the pointer pointing to some flags.

EDIT

Following the comments I decided to sum up all the risks I see and the risks that others have pointed out:

  1. Accessing invalid pointer in a multi-threaded environment: Using your method a class may be accessed after the destructor has run but before the destroyed flag is set (As to your question in one of the comments - shared_ptr is for most purposes thread safe)
  2. Relaying on a behavior you don't totally control: Your method relies on the way destructors auto call the destructors of other members which are not dynamically allocated: This means that you still have to release dynamically allocates memory specifically, You have no control on how exactly this is implemented, You have no control on the order in which other destructors are called.
  3. Relaying on a behavior you don't totally control (Point 2): You are relaying on the way a compiler implements the part of the destructor which calls other destructors you have no way in telling whether your code will be portable or even how will it handle calling it twice.
  4. Destructors may be called twice: Depending on your implementation this may cause memory leaks or heap corruption unless you guard against releasing the same memory twice. You claim you guard against that case by calling the placement new - However in a multi-threading environment this is not guaranteed further more you assume that all memory allocations are done by the default constructor - depending on your specific implementation this may or may not be true.
  5. You are going against the better judgment of everyone that answered your question or commented on it - You may be onto something genius but most probably you are just shooting yourself in the leg by limiting your implementation to a small subset of situations where it will work correctly. It is like when you use the wrong screwdriver you will eventually end up damaging the screw. In the same way using a language construct in a way it was not intended to be used may end up with a buggy program - Destructors are intended to be called from delete and from the code generated by the compiler to clear the stack. Using it directly is not wise.

And I repeat my suggestion - overload delete and new for what you want

like image 197
Sebastian Cabot Avatar answered Oct 22 '25 21:10

Sebastian Cabot


There is a suggestion of using smart pointers. In fact - I am doing that, but my references are circular. I could use some fully-fledged garbage collectors, but since I know myself where (and when!) circle chains can be broken, I want to take advantage of that myself.

Then you can explicitly null-ify (reset if you are using shared_ptr) one of the circular smart pointers and break the cycle.

Alternatively, if you know where the cycles will be in advance, you should also be able to avoid them in advance using weak_ptr in place of some of the shared_ptrs.

--- EDIT ---

If an object referenced by weak pointers only would merely get flagged as "invalid" and release control over all its contained pointers (is this sentence clear?), I would be happy.

Then weak_ptr::expired should make you happy :)

like image 26
Branko Dimitrijevic Avatar answered Oct 22 '25 23:10

Branko Dimitrijevic



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!