C ++如何知道对象何时被销毁

问题描述

我不明白当从另一个作用域创建一个死对象并将其放入另一个作用域的某种容器中时,如何防止访问该死对象。例子

#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();   
    }
}

我已经尝试了大约2天,以找出某种“共享”指针系统,该系统将Foo*与另一个对象包装在一起,但是无论如何,它总是归结为这样一个事实,即使是“共享”指针将不知道Foo何时死亡。好像我在寻找Foo析构函数回调一样。

注意:C ++ 98,没有增强。

如果这个问题已经解决了1000次,我只想知道它背后的想法,以便对它进行解释。


编辑:添加更多上下文

基本上,我的设计中经常遇到这个问题。我真的很喜欢“松散耦合”并保持模块分离。但是,为了做到这一点,必须至少有一个地方将它们连接起来。因此,我选择了基于发布/订阅或事件的系统。因此,必须有发射器和处理程序。但是,这实际上只是解决了问题。如果ModuleA可以发出事件并且ModuleB可以侦听事件,则ModuleA将必须具有对ModuleB的某种引用。并不是很糟糕,但是现在我们必须考虑范围,复制ctor,=运算符等的所有时髦之处。我们除了全力以赴之外,别无选择。

示例

#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();
}

如果我们有一个名为MyObject的东西,它包含一个我们自己想听的EventEmitter的组件(可能是一个套接字),情况会变得更糟。如果复制了MyObject,现在我们将拥有内部EventEmitter,其中包含引用了MyObject的处理程序,该处理程序可能不再存在!

因此,也许我将其全部转储并切换到回调。。但是即使那样,某些对象仍然拥有ptrs或对可能不再存在的其他对象的引用!我们可以根据自己的意愿小心谨慎,但我们永远不知道会发生什么...

你知道,我想我需要这样说

  1. 除非对象自己创建了该对象,否则任何对象都不得引用或引用该对象。

现在将对象链接在一起必须通过管理这两个对象的更高级别的对象来完成...

...我应该坚持平面设计。

解决方法

如果要使对象的生存期超出创建对象的范围,则必须使用动态生存期来创建该对象。您使用new执行此操作:

std::vector<Foo*> foos;

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

Foo返回指针的new对象将一直存在,直到明确delete d为止。请注意,std::vector不会为您执行此操作。您必须明确删除存储在向量中的指针所指向的对象:

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

理想情况下,您将使用某种智能指针来管理动态生命周期对象,但是标准智能指针std::unique_ptrstd::shared_ptr不在C ++ 98中。 std::auto_ptr可用,但是该类非常容易错误使用。可能需要编写自己的类似shared_ptr的简单类来为您完成此操作。如果您不需要支持弱指针和原子操作之类的工具,它并不太复杂。这是一个非常基本的实现:

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

基本前提是,给定对象的所有shared_ptr都拥有指向同一control_block的指针。每当复制shared_ptr时,其control_block的参考计数就会增加,而无论何时shared_ptr被破坏时,其control_block的参考计数都会减少。如果引用计数曾经达到零,它将与指向的对象一起删除控制块。

,

在C ++中,您负责对象的生命周期。如果您使用

Foo foo;

foo将被破坏而超出范围。 因此,您不应使用局部变量。 使用动态对象(例如):

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

它将起作用。 您负责破坏对象。

,

您可以使用将向量赋给foo本身,并删除析构函数中的指针。基本上,对象本身负责指针的簿记。

#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();   
    }
}

输出

➜  test ./a.out
length is 0
,

我想在这里提出自己的答案,以表达我在不知不觉中试图写的内容:垃圾收集。而且更重要的是,在已经自动管理的内存上。

花了大约三天的时间才能理解。我正在创建一个由节点和引用组成的系统,以使对象保持活动状态。从字面上看是垃圾收集...

糟糕。

,

为什么不使用std::vector<Foo>而不是std::vector<Foo*>?以以前的方式,我认为无法防止Foo对象超出范围时被破坏-但是当您推动vector时,它将一直坐在那里直到程序终止或我们显式删除。 / p>

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...