我知道,每当您使用传统指针delete this
分配一些东西时,都可以在 C++中说。new
事实上,我也知道如果你小心处理它是一个很好的做法。我可以让一个对象说它delete this
是否被一个 持有std::shared_ptr
?那应该调用析构函数,对吧?为了给你一个想法,我正在制作一个游戏,其中一艘船可以发射导弹,我想让导弹自行删除。
4 回答
不,这不安全,对象的生命周期是由 的持有者决定的shared_ptr
,所以对象本身无法决定它是否想死。如果你这样做,你会在最后一次shared_ptr
死亡时得到双重删除。我能提供的唯一解决方案是“重新考虑你的设计”(你可能一开始就不需要shared_ptr
,导弹可能是值或池对象)。
导弹要删除自己,它必须拥有自己,或者至少与他人分享自己的所有权。既然你说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';
}
}
我知道我参加节目迟到了,但我刚刚自己也想这样做,并意识到这是“可能的”,但你需要注意一些事情。
霍华德的回答是正确的,但没有抓住重点,因为您不应该将原件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: %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 删除器永远不应该为分配在堆栈中的对象调用,所以当它在正常的对象销毁时,它只是绕过删除(我们到了这一点,因为已经调用了默认的对象析构函数)。