"delete this"分配给使用 std::shared_ptr 的对象?

"delete this" to an object that's allocated with std::shared_ptr?

本文关键字:shared 对象 ptr std this delete 分配      更新时间:2023-10-16

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

不,这是不安全的,物体的寿命由shared_ptr持有者决定,所以物体本身不能决定是否要死。如果你这样做,你会得到双倍最后shared_ptr死亡时删除。我能提供的唯一解决方案是"重新考虑你的设计"(你可能一开始就不需要shared_ptr,导弹可能是值或汇集的物体)。

导弹要删除自己,它必须拥有自己,或者至少与他人分享自己的所有权。 既然你说导弹有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';
    }
}
我知道

我迟到了,但我现在碰到想自己做这件事,并意识到这是"可能的",但你需要照顾一些事情。

霍华德的答案是正确的,但错过了目标,因为你不应该把原始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()否则您最终会出现内存泄漏。就像使用普通指针一样。

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

  • 对象管理自己的生命周期,因此永远不会共享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: %dn", a.ptr().lock().use_count());
    printf("count: %dn", 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 删除器永远不应该为堆栈中分配的对象调用,所以当它在正常的对象销毁时,它只是绕过删除(我们走到这一步是因为已经调用了默认的对象析构函数)。