我在阅读Boost.Asio示例时遇到了enable_shared_from_this
,在阅读完文档后,我仍然因为如何正确使用它而迷失了方向。有人可以给我一个例子和/或说明何时使用这个课程是有意义的。
答案 0 :(得分:323)
当您拥有shared_ptr
时,它可以让您有效this
个实例到this
。没有它,你将无法获得shared_ptr
到this
,除非你已经有一个成员。这个例子来自boost documentation for enable_shared_from_this:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_from_this();
}
}
int main()
{
shared_ptr<Y> p(new Y);
shared_ptr<Y> q = p->f();
assert(p == q);
assert(!(p < q || q < p)); // p and q must share ownership
}
方法f()返回有效的shared_ptr
,即使它没有成员实例。请注意,您不能简单地执行此操作:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_ptr<Y>(this);
}
}
此返回的共享指针将具有与“正确”引用不同的引用计数,并且其中一个将在删除对象时最终丢失并持有悬空引用。
enable_shared_from_this
已成为C ++ 11标准的一部分。你也可以从那里以及从增强中获得它。
答案 1 :(得分:179)
...像这样的代码无法正常工作:
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
两个shared_ptr
对象都不知道另一个,因此两者都会在销毁资源时尝试释放资源。这通常会导致问题。
同样,如果一个成员函数需要一个拥有被调用对象的shared_ptr
对象,它就不能只是动态创建一个对象:
struct S
{
shared_ptr<S> dangerous()
{
return shared_ptr<S>(this); // don't do this!
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->dangerous();
return 0;
}
此代码与前面的示例具有相同的问题,尽管形式更为细微。构造时,shared_pt
r对象sp1
拥有新分配的资源。成员函数S::dangerous
内的代码不知道该shared_ptr
对象,因此它返回的shared_ptr
对象与sp1
不同。将新的shared_ptr
对象复制到sp2
无济于事;当sp2
超出范围时,它将释放资源,当sp1
超出范围时,它将再次释放资源。
避免此问题的方法是使用类模板enable_shared_from_this
。该模板采用一个模板类型参数,该参数是定义受管资源的类的名称。反过来,该类必须从模板中公开派生;像这样:
struct S : enable_shared_from_this<S>
{
shared_ptr<S> not_dangerous()
{
return shared_from_this();
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->not_dangerous();
return 0;
}
执行此操作时,请记住,您调用shared_from_this
的对象必须由shared_ptr
对象拥有。这不起作用:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
答案 2 :(得分:27)
这是我的解释,从一个具体的角度来看(最高的答案并没有'点击'跟我一起)。 *请注意,这是调查Visual Studio 2012附带的shared_ptr和enable_shared_from_this的源代码的结果。也许其他编译器以不同的方式实现enable_shared_from_this ... *
enable_shared_from_this<T>
向weak_ptr<T>
添加一个私有T
个实例,其中包含T
实例的“一个真正的引用次数”。 / p>
因此,当您首次在新T *上创建shared_ptr<T>
时,T *的内部weak_ptr会以refcount为1进行初始化。新的shared_ptr
基本上会回到此{{1} }}
weak_ptr
可以在其方法中调用T
来获取shared_from_this
的实例,返回到相同的内部存储引用计数。这样,您总是有一个存储shared_ptr<T>
引用计数的地方而不是多个T*
实例彼此不了解,并且每个实例都认为它们是{{1}负责重新计数shared_ptr
并在其引用计数到零时删除它。
答案 3 :(得分:3)
请注意,使用boost :: intrusive_ptr不会遇到此问题。 这通常是解决此问题的更方便的方法。
答案 4 :(得分:2)
在c ++ 11及更高版本中完全相同:它使得能够将this
作为共享指针返回,因为this
为您提供了原始指针。
class Node {
public:
Node* getParent const() {
if (m_parent) {
return m_parent;
} else {
return this;
}
}
private:
Node * m_parent = nullptr;
};
进入这个:
class Node : std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> getParent const() {
std::shared_ptr<Node> parent = m_parent.lock();
if (parent) {
return parent;
} else {
return shared_from_this();
}
}
private:
std::weak_ptr<Node> m_parent;
};
答案 5 :(得分:1)
在一种情况下,我发现enable_shared_from_this极为有用:使用异步回调时的线程安全性。
想象一下,客户类有一个AsynchronousPeriodicTimer类的成员:
struct AsynchronousPeriodicTimer
{
// call this periodically on some thread...
void SetCallback(std::function<void(void)> callback);
void ClearCallback(); // clears the callback
}
struct Client
{
Client(std::shared_ptr< AsynchronousPeriodicTimer> timer)
: _timer(timer)
{
_timer->SetCallback(
[this]
()
{
assert(this); // what if 'this' is already dead because ~Client() has been called?
std::cout << ++_counter << '\n';
}
);
}
~Client()
{
// clearing the callback is not in sync with the timer, and can actually occur while the callback code is running
_timer->ClearCallback();
}
int _counter = 0;
std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}
int main()
{
auto timer = std::make_shared<AsynchronousPeriodicTimer>();
{
auto client = std::make_shared<Client>(timer);
// .. some code
// client dies here, there is a race between the client callback and the client destructor
}
}
客户端类向定期计时器预订回调函数。一旦客户端对象超出范围,回调和析构函数之间便存在竞争条件。回调可以通过悬空的指针来调用!
解决方案:使用enable_shared_from_this:
struct Client : std::enable_shared_from_this<Client>
{
Client(std::shared_ptr< AsynchronousPeriodicTimer> timer)
: _timer(timer)
{
auto captured_self = weak_from_this(); // weak_ptr to avoid cyclic references with shared_ptr
_timer->SetCallback(
[captured_self]
()
{
if (auto self = captured_self.lock())
{
// this is guranteed to be non-nullptr. we managed to promote captured_self to shared_ptr
std::cout << ++self->_counter << '\n';
}
}
);
}
~Client()
{
_timer->ClearCallback();
}
int _counter = 0;
std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}
答案 6 :(得分:-3)
另一种方法是在weak_ptr<Y> m_stub
中添加class Y
成员。然后写:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
当你无法改变你所衍生的类时(例如扩展其他人的库),这是有用的。不要忘记初始化成员,例如通过m_stub = shared_ptr<Y>(this)
,即使在构造函数中它也是有效的。
如果在继承层次结构中有更多这样的存根,那就没关系,它不会阻止对象的破坏。
编辑:正如用户nobar正确指出的那样,代码将在分配完成后销毁Y对象并销毁临时变量。因此我的回答是不正确的。