【问题标题】:"delete this" to an object that's allocated with std::shared_ptr?将其“删除”到使用 std::shared_ptr 分配的对象?
【发布时间】:2012-04-22 19:12:47
【问题描述】:

我知道,每当您使用传统指针分配 new 的东西时,都可以在 C++ 中说 delete this。事实上,我也知道如果你小心处理它是一个很好的做法。如果对象由std::shared_ptr 持有,我可以说delete this 吗?那应该调用析构函数,对吧?为了给你一个想法,我正在制作一个游戏,其中一艘船可以发射导弹,我想让导弹自行删除。

【问题讨论】:

  • 使用delete this 删除对象是不是的好习惯。您必须小心处理的事实足以证明这一点。这是安排生命周期管理的一种容易出错的方式。

标签: c++ memory-management c++11 shared-ptr


【解决方案1】:

不,这不安全,对象的生命周期是由shared_ptr的持有者决定的,所以对象本身无法决定它是否想死。如果你这样做,你会得到双倍的 当最后一个 shared_ptr 死时删除。我能提供的唯一解决方案是“重新考虑你的设计”(你可能一开始就不需要shared_ptr,而且导弹可能是值或池对象)。

【讨论】:

    【解决方案2】:

    导弹要删除自己,它必须拥有自己,或者至少与他人分享自己的所有权。既然你说导弹有一个shared_ptr,我假设你已经有多个对象共享导弹的所有权。

    导弹可以自己持有shared_ptr,从而分享自己的所有权。然而,这总是会创建一个循环所有权模式:只要导弹的 shared_ptr 数据成员引用它自己,引用计数就永远不会降为零,因此导弹就会泄露。

    你可以让一个外部对象或事件告诉导弹删除自己,但我不确定重点是什么。为了告诉导弹删除自己,应该通过shared_ptr 进行通信,然后直到shared_ptr 放开导弹,删除才会真正发生。

    是的,这是可能的。不,我认为这不是一个好主意。对我来说,它看起来很容易发生内存泄漏,实际上并没有增加价值。但是对于好奇的人,您可以这样做:

    #include <iostream>
    #include <memory>
    
    class missile
        : public std::enable_shared_from_this<missile>
    {
        std::shared_ptr<missile> self_;
    public:
        missile()
          {} 
    
        ~missile() {std::cout << "~missile()\n";}
    
        void set_yourself()
        {
            self_ = shared_from_this();
        }
        void delete_yourself()
        {
            if (self_)
                self_.reset();
        }
    };
    
    int main()
    {
        try
        {
            std::shared_ptr<missile> m = std::make_shared<missile>();
            m->set_yourself();
            std::weak_ptr<missile> wp = m;
            std::cout << "before first reset()\n";
            m.reset();
            std::cout << "after first reset()\n";
            // missile leaked here
            m = wp.lock();
            m->delete_yourself();
            std::cout << "before second reset()\n";
            m.reset();  // missile deleted here
            std::cout << "after second reset()\n";
        }
        catch (const std::exception& e)
        {
            std::cout << e.what() << '\n';
        }
    }
    

    【讨论】:

    • 我不确定我是否应该投票。解决方案是正确的和新颖的,但问题是错误的。
    • 内存泄漏怎么办? wp 没有过期。您在这里的职责与处理原始指针时的职责完全相同。如果您碰巧在导弹自行删除时使用了导弹,那么您就不会将地毯从脚下拉出来。
    • @EmilyL.:我说过容易内存泄漏。如果有什么东西没有告诉导弹删除自己,那么它就会泄漏。此示例演示了通过删除所有外部所有者而不调用 delete_yourself。该示例然后重新建立外部所有者,告诉导弹现在删除自己,然后在外部所有者放弃所有权时被删除。 IE。为什么不只是有外部所有者呢?一个更常见的习语是导弹只为自己维护一个weak_ptr,并使用该weak_ptr 将强大的所有权交给外部实体。
    • @deft_code:我的意思是它是可能的,方法如下,但这个解决方案很糟糕。它很容易出错,因为它创建了一个必须在运行时打破的所有权周期,否则就会出现泄漏。
    【解决方案3】:

    我知道我迟到了,但我刚刚想自己做这件事,并意识到这是“可能的”,但你需要注意一些事情。

    霍华德的答案在正确的轨道上,但由于您不应该将原始 shared_ptr 的构造留给客户,因此没有达到目的。这就是造成内存泄漏风险的原因。相反,您应该封装构造并且只允许弱指针。

    这是一个例子:

    class Missile{
    private:
        Missile(...){ }; // No external construction allowed
        Missile(const Missile&) = delete; // Copying not allowed
        void operator = (const Missile&) = delete; // -||-
    
        std::shared_ptr<Missile> m_self;
    public:
        template<typename... Args>
        static MissilePtr makeMissile(Args... args){ 
            auto that = std::make_shared<Misile>(args...);
            that.m_self = that; // that holds a reference to itself (ref count = 2)
            return that; // 'that' is destroyed and ref-count reaches 1.
        }
    
        void die(){
            m_self.reset();
        }
    
        ...
    };
    
    typedef std::weak_ptr<Missile> MissilePtr;
    
    void useMissile(MissilePtr ptr){
        auto missile = ptr.lock(); // Now ptr cannot be deleted until missile goes out of scope
        missile->die(); // m_self looses the reference but 'missile' still holds a reference
        missile->whatever(); // Completely valid. Will not invoke UB
    } // Exiting the scope will make the data in missile be deleted.
    

    调用die() 将在语义上产生与delete this 相同的效果,另外还有一个好处是所有引用已删除对象的MissilePtr 都将过期。此外,如果任何MissilePtr 用于访问this,则删除将被延迟,直到用于访问的临时std::shared_ptr 被销毁,从而让您终生头痛。

    但是,您必须确保始终至少保留一个MissilePtr,并在某些时候调用die(),否则最终会导致内存泄漏。就像使用普通指针一样。

    【讨论】:

      【解决方案4】:

      这个问题很老了,但我有一个类似的问题(在这种情况下,一个“侦听器”对象必须管理自己的生命周期,同时仍然能够共享弱指针),并且谷歌搜索并没有提供解决方案我,所以我分享我找到的解决方案,假设:

      • 对象管理自己的生命周期,因此永远不会共享 share_ptr,但是一个weak_ptr(如果你需要shared_ptr的类似 解决方案 + use_shared_from_this 可以做到)。
      • 打破 RAII 是个坏主意,因此我们不会这样做:我们的 这里的地址是对象拥有 shared_ptr 的问题 本身,因为包含成员 share_ptr 导致双重调用 对象破坏,通常是崩溃(或至少未定义 行为),因为析构函数被调用两次(正常一次 对象销毁和第二个销毁自我时 包含 shared_ptr 成员)。

      代码:

      #include <memory>
      #include <stdio.h>
      
      using std::shared_ptr;
      using std::weak_ptr;
      
      class A {
          struct D {
                  bool deleted = false;
                  void operator()(A *p) {
                      printf("[deleter (%s)]\n", p, deleted ? "ignored":"deleted");
                      if(!deleted) delete p;
              }
          };
      
          public: shared_ptr<A> $ptr = shared_ptr<A>(this, D());
      
          public: ~A() {
              std::get_deleter<A::D>($ptr)->deleted = true;
          }
      
          public: weak_ptr<A> ptr() { return $ptr; }
      };
      
      void test() {
          A a;
      
          printf("count: %d\n", a.ptr().lock().use_count());
          printf("count: %d\n", a.ptr().use_count());
      }
      
      int main(int argc, char *argv[]) {
          puts("+++ main");
      
          test();
      
          puts("--- main");
      }
      

      输出:

      $ g++ -std=c++11 -o test test.cpp && ./test
      +++ main
      count: 2
      count: 1
      [deleter (ignored)]
      --- main
      

      shared_ptr 删除器永远不应该被分配在堆栈中的对象调用,所以当它在正常的对象销毁时,它只是绕过删除(我们到了这一点,因为已经调用了默认的对象析构函数)。

      【讨论】:

        猜你喜欢
        • 2020-11-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-17
        • 1970-01-01
        • 1970-01-01
        • 2015-08-30
        相关资源
        最近更新 更多