我在阅读 Boost.Asio 的示例时遇到了 enable_shared_from_this
,但在阅读文档后仍然不知道该如何正确使用它。可以请问有人能给我一个例子和解释,在何时使用此类才是明智的吗。
我在阅读 Boost.Asio 的示例时遇到了 enable_shared_from_this
,但在阅读文档后仍然不知道该如何正确使用它。可以请问有人能给我一个例子和解释,在何时使用此类才是明智的吗。
this
的情况下获得有效的shared_ptr
实例。如果没有它,您将无法获得shared_ptr
到this
,除非您已经将其作为成员拥有。以下示例来自于boost文档中关于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 标准的一部分。您也可以从那里以及 boost 中获取它。std::enable_shared_from_this
,在原始指针上使用std::shared_ptr
构造函数是完全有效的。我不知道Boost的语义是否已更新以支持此功能。 - Matthewshared_ptr<Y> q = p
呢? - Dan M.q
而你需要从类内部获取 p
时,这将会很有用。 - Hatted Rooster根据Dr Dobbs关于弱引用的文章,我认为以下这个例子更容易理解(来源:http://drdobbs.com/cpp/184402026):
...像这样的代码将无法正确工作:
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_ptr
对象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
}
shared_ptr<S> sp2 = p->not_dangerous();
,因为这里的陷阱是在第一次调用shared_from_this()
之前必须按照普通方式创建shared_ptr! 这真的很容易出错!在C++17之前,如果没有按照正常方式创建shared_ptr:auto sptr = std::make_shared<S>();
或者 shared_ptr<S> sptr(new S());
,调用shared_from_this()
就会导致未定义的行为(UB)。值得庆幸的是,从C++17开始,这样做将抛出异常。 - AnorZakenS* s = new S(); shared_ptr<S> ptr = s->not_dangerous();
<-- 允许在之前共享的对象上调用shared_from_this,即由std::shared_ptr <T>管理的对象。否则,行为未定义(直到C++17),会抛出std::bad_weak_ptr(通过从默认构造的weak_this调用shared_ptr构造函数)(自C++17以来) 。(http://en.cppreference.com/w/cpp/memory/enable_shared_from_this)。所以实际上它应该被称为`always_dangerous()`,因为你需要知道它是否已经被共享。 - AnorZakenSo, when you first create...
,因为这是一个要求(就像你所说的,直到将对象指针传递到shared_ptr ctor中,weak_ptr才会初始化!),如果不小心处理这个要求,事情可能会变得非常糟糕。如果在调用shared_from_this
之前没有创建任何shared_ptr,则会出现UB-同样,如果创建了多个shared_ptr,则也会出现UB。您必须确保只创建一个shared_ptr。 - AnorZaken我发现enable_shared_from_this
非常有用的一个特定场景是:在使用异步回调时保证线程安全。
想象一下,类Client
拥有一个类型为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)
{
}
void Init()
{
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 guaranteed to be non-nullptr. we managed to promote captured_self to a shared_ptr
std::cout << ++self->_counter << '\n';
}
}
);
}
~Client()
{
// the destructor cannot be called while the callback is running. shared_ptr guarantees this
_timer->ClearCallback();
}
int _counter = 0;
std::shared_ptr< AsynchronousPeriodicTimer> _timer;
}
enable_shared_from_this
机制与std::shared_ptr
的内在线程安全性相结合,使我们能够确保在回调代码访问其内部成员时不能销毁Client
对象。请注意,Init
方法与构造函数分离,因为在构造函数退出之前enable_shared_from_this
的初始化过程尚未完成。因此需要额外的方法。从构造函数中订阅异步回调通常是不安全的,因为回调可能访问未初始化的字段。在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;
};
shared_ptr
管理时,此方法才能正常工作。您可能需要更改接口以确保这种情况。 - curiousguystd::shared_ptr<Node> getParent const()
公开为NodePtr getParent const()
。如果你确实需要访问内部原始指针(最好的例子:处理C库),那么可以使用std::shared_ptr<T>::get
,虽然我很讨厌提到它,因为我已经看到过太多错误的情况使用这个原始指针访问器。 - mchiassonenable_shared_from_this
允许您使用专门接受 shared_ptr<>
的 API 进行工作。在我看来,这样的 API 通常是 错误的(因为最好让堆栈中更高层次的东西拥有内存),但如果您被迫使用这样的 API,则这是一个不错的选择。 - cdunn2001