有时课程需要获得shared_ptr
。实现此目的的一种方法(当您知道该类的对象将始终存在于shared_ptr
s中)时,可以从std::enable_shared_from_this<SomeClass>
派生并根据需要调用shared_from_this()
。
但是这种方法的一个小问题是你无法从析构函数中调用shared_from_this()
- 因为enable_shared_from_this
在引擎盖下使用weak_ptr
并且因为在析构函数中意味着对象的引用计数为0,将抛出bad_weak_ptr
异常。
现在,我经常发现自己正在编写具有非平凡析构函数的类,这些析构函数会调用其他尝试使用shared_from_this()
的函数。例如:
~SomeClass::SomeClass()
{
f();
}
// f can also be called outside the context of the destructor
/* public */ void SomeClass::f()
{
g();
m_anotherObject.h(shared_from_this()); // want to skip this if shared_from_this() fails
i();
}
如前所述,写入的f
将在从析构函数调用时抛出bad_weak_ptr
异常。我可以简单地吞下该异常并转到i()
,但这违反了只在特殊情况下抛出异常的想法,并且当IDE配置为在抛出异常时向您发出警告时,调试会变得更加困难。我还可以用任何数量的不同方法重构代码,以防止从析构函数中调用h
,但这会增加复杂性,损害可读性,并且似乎有很多不必要的麻烦。或者我可以添加某种bool m_inDestructor
成员变量,但这将是可怕的。
/* public */ void SomeClass::f()
{
g();
try
{
m_anotherObject.h(shared_from_this());
}
catch(std::bad_weak_ptr&) {} // don't like; exceptions should be exceptional
i();
}
/////////////////////
SomeClass::~SomeClass()
{
f(true);
}
/* public */ void SomeClass::f()
{
f(false);
}
// works, but adds complexity I feel like I shouldn't need
/* private */ void SomeClass::f(bool inDestructor)
{
g();
if(!inDestructor)
m_anotherObject.h(shared_from_this());
i();
}
所以我的问题是:是否有人提出了一般适用的非投掷机制来确定enable_shared_from_this
是否实际上有shared_ptr
提供?
答案 0 :(得分:2)
我正在回答这个问题,因为我不认为我可以在评论中解释清楚。
这通常是您过度使用共享指针的好兆头!当您需要管理对象的生命周期时,您应该只使用共享指针。创建共享指针的实例意味着您说“我需要对此对象拥有一些所有权”。这非常适合交错数据结构。
你不应该在函数调用中使用共享指针(通常)。函数调用通常是输入输出。函数调用的参数的生命周期必须通过函数调用持续,但调用者通常负责确保函数调用,而不是函数。
请考虑以下代码:
class SomeClass;//implements enable_shared_from_this
int foo(shared_ptr<SomeClass> a);
int bar(shared_ptr<SomeClass> a);
int main()
{
SomeClass c;
foo(c.shared_from_this());
bar(c.shared_from_this());
}
我们知道整个电话中都存在c:它不能超出范围。要求共享指针输入限制谁可以使用该类并增加调用的开销,并且不会给我们提供比我们已有的更多保护。更糟糕的是它增加了一个错误条件(shared_ptrs仍然可以为空!)。
替代方案更快,更灵活:
class SomeClass;//implements enable_shared_from_this
int foo(SomeClass& a);
int bar(SomeClass& a);
int main()
{
SomeClass c;
foo(c);
bar(c);
}
通过引用,我们已经确定实例在传入时必须存在。并且让调用者确保引用持续足够长的时间。如果c可以处于销毁/默认状态,那么foo和bar无论如何都需要检测到它。
根据您的具体示例,我们应该致电
int SomeClass::foo()
{
foo(*this);
}
this
必须存在,因为我们正在调用该函数。如果SomeClass可以拥有你想要处理的被破坏状态,那么你应该有一个可以在通话中检查的方法(可以是私有的或公共的)bool is_destroyed() const
(虽然老实说我会让调用者负责好吧,只是说“你试图使用被破坏的物体?你是SOL”。
编辑1:使用shared_ptr的例外情况是,如果该函数的副作用是获取该类的所有权(如启动线程,设置全局或返回具有所有权的类),但是如果您实现enabel_shared_from_this我仍然会参考。
编辑2:SharedFromThis是一个拐杖(imho),因为以下内容会创建一个错误状态:
shared_ptr<SomeClass> ptr;
{
SomeClass a;
ptr = a.shared_from_this();
}
ptr->foo();//because the parent went out of scope, it is destroyed anyway!
这就是调用者应该只管理自己的共享指针的原因。
答案 1 :(得分:1)
如果你真的想这样做,你可以自己重新实现shared_from_this
成语,除非它在适当时返回null shared_ptr
。
要执行此操作,请将weak_ptr<SomeClass>
添加到SomeClass
作为数据成员:
class SomeClass
{
std::weak_ptr<SomeClass> weak_ptr_;
在工厂功能中,初始化weak_ptr
:
static std::shared_ptr<SomeClass> create()
{
std::shared_ptr<SomeClass> p(new SomeClass);
p->weak_ptr_ = p;
return p;
}
然后写shared_from_this
来做你想做的事:
std::shared_ptr<SomeClass> shared_from_this() {return weak_ptr_.lock();}
现在,只要f()
可以处理空shared_ptr
(并且只要您通过工厂功能控制所有构造),您就会变得金黄。