5

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

4

4 回答 4

13

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

于 2012-04-22T19:16:50.020 回答
3

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

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

您可以让外部对象或事件告诉导弹删除自身,但我不确定重点是什么。为了告诉导弹删除自己,该通信应该通过 a 进行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';
    }
}
于 2012-04-22T20:45:28.107 回答
1

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

霍华德的回答是正确的,但没有抓住重点,因为您不应该将原件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 thisMissilePtrMissilePtrthisstd::shared_ptr

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

于 2014-10-04T12:27:47.083 回答
1

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

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

于 2015-09-15T10:53:04.940 回答