Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ How to know when an object has been destroyed

Tags:

c++

c++98

I don't understand how I can prevent accessing a dead object when it was created from a different scope and placed into some kind of container in another scope. Example

#include <iostream>
#include <string>
#include <vector>

class Foo {
    public:
        void Speak(){
            std::cout << "ruff";    
        }
};

int main()
{
    std::vector<Foo*> foos;
    
    {
        Foo foo;
        foos.push_back(&foo);
        // foo is now dead 
    }
    
    for(size_t i = 0; i < foos.size(); i++){
        // uh oh
        foos[i]->Speak();   
    }
}

I have been trying for about 2 days to figure out some kind of "shared" pointer system that would wrap Foo* with another object, but no matter what, it always comes down to the fact that even that "shared" pointer won't know when Foo has died.. It's as if I am looking for a Foo destructor callback.

Notes: C++ 98, no boost.

If this has been solved 1000 times already, I would just love to know the idea behind it so I can make an interpretation of it.


Edit: To add some more context

Essentially I have this recurring problem in my designs. I really like "loose coupling" and keeping modules separate. But in order for that to happen, there must be at least one place to connect them. So I go for the pub/sub or event based systems. Therefore there must be emitters and handlers. This actually just re-wraps the problem, though. If ModuleA can emit events and ModuleB can listen to events, than ModuleA will have to have some kind of reference to ModuleB. That's not so bad, but now we have to consider all the funkiness of scope, copy ctors, = operators, etc. We have no choice but to go full out.

Example

#include <iostream>
#include <string>

class Handler {
    void HandleEvent();
};

class Emitter {
public:
    // add handler to some kind of container
    void AttachHandler(Handler *handler);
    // loop through all handlers and call HandleEvent
    void EmitEvent();
};

int main()
{
    // scope A
    Emitter emitter;
    
    // scope B
    {
        Handler handler;
        emitter.AttachHandler(handler);
    }
    
    // rats..
    emitter.EmitEvent();
}

Things get worse if we had something called MyObject that contains a component with an EventEmitter of its own that we want to listen to (maybe a socket). If MyObject is copied around, now we have this internal EventEmitter with handlers that reference MyObject that may no longer exist!

So maybe I dump it all and switch to callbacks.. But even then, some object is still owning ptrs or references to some other object that may no longer exist! We can be as careful as we want but we never know what's going to happen...

You know, I think what I need is to say this

  1. No object should ever have a reference or ptr to another object ever, unless it created that object on its own.

Now linking objects together must be done through some higher level object that manages both of those objects...

...I should have stuck to graphic design.

like image 670
ony_pox232 Avatar asked Dec 06 '25 04:12

ony_pox232


1 Answers

If you want an object's lifetime to extend beyond the scope in which it's created, then that object has to be created with dynamic lifetime. You use new to do this:

std::vector<Foo*> foos;

{
    Foo* foo = new Foo;
    foos.push_back(foo);
}

The Foo object that new returns a pointer to will live until it is explicitly deleted. Note that std::vector will not do this for you. You must explicitly delete the objects pointed to by the pointers stored in your vector:

for (std::size_t i = 0; i < foos.size(); ++i) {
    delete foos[i];
}

Ideally you would use a smart pointer of some kind to manage your dynamic-lifetime objects, but the standard smart pointers std::unique_ptr and std::shared_ptr were not in C++98. std::auto_ptr was available, but that class is very easy to use incorrectly. It may be worth writing your own simple shared_ptr-like class to do this for you. It's not too complicated if you don't need to support things like weak pointers and atomic operations. Here's a very basic implementation:

template <typename T>
class shared_ptr
{
private:
    struct control_block
    {
        control_block(T* ptr)
            : ref_count_(1),
              ptr_(ptr)
        {}
        
        ~control_block()
        {
            delete ptr_;
        }
        
        size_t ref_count_;
        T* ptr_;
    };

    control_block* control_block_;
    
public:
    shared_ptr()
        : control_block_(NULL)
    {}

    shared_ptr(T* ptr)
        : control_block_(new control_block(ptr))
    {}
    
    shared_ptr(const shared_ptr& other)
        : control_block_(other.control_block_)
    {
        ++control_block_->ref_count_;
    }
    
    shared_ptr& operator=(shared_ptr other)
    {
        std::swap(control_block_, other.control_block_);
        return *this;
    }
    
    ~shared_ptr()
    {
        if (control_block_) {
            --control_block_->ref_count_;
            if (control_block_->ref_count_ == 0) {
                delete control_block_;
            }
        }
    }
    
    T& operator*() { return *control_block_->ptr_; }
    T* operator->() { return control_block_->ptr_; }
    
    bool operator==(const shared_ptr& other)
    {
        return control_block_ == other.control_block_;
    }
};

Live Demo

The basic premise is that all shared_ptrs for a given object hold a pointer to the same control_block. Whenever the shared_ptr is copied its control_block's reference count is incremented and whenever a shared_ptr is destroyed its control_block's reference count is decremented. If the reference count ever reaches zero it deletes the control block along with the pointed-to object.

like image 193
Miles Budnek Avatar answered Dec 07 '25 20:12

Miles Budnek



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!