简体   繁体   中英

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

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.

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 delete d. 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_ptr s 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.

In C++ you are responsible of life cycle of object. If you use

Foo foo;

foo will be destroyed at out of scope. So, you shouldn't use local variable. Use dynamic objects (for example):

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

It will work. And you are responsible for object destroying.

You could use give the vector to the foo itself and remove the pointer in the destructor. Basically the object itself does the bookkeeping of pointers.

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

class Foo {
    std::vector <Foo *> *v;
    public:
    Foo(std::vector <Foo *> *v):  v(v){}
    void Speak(){
        std::cout << "ruff";    
    }
    ~Foo() {
        auto it = std::find(v->begin(), v->end(), this);
        if (it != v->end())
        {
            v->erase(it);
        }
    }
};

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

OUTPUT

➜  test ./a.out
length is 0

I would like to put my own answer here to present the realization of what I was unknowingly trying to write: Garbage Collection. And more importantly, on memory that is already automatically managed.

That took about 3 days to understand. I was creating a system of nodes and references in order to keep objects alive. Literally garbage collection...

Woopsies.

Why don't you use std::vector<Foo> instead of std::vector<Foo*> ? In the former way, i think there's no way to prevent Foo object destroyed when it came out of scope - but when you push to vector , it will sit there until program terminate or we explicit remove.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM