如何防止由shared_ptr破坏捕获的“ this”的悬空指针?

时间:2018-08-23 17:32:02

标签: c++ c++11

#include <iostream>
#include <functional>
#include <memory>

class Event
{
public:
    void RegisterHandler(std::function<void(bool)> handler)
    {
        mHandler = handler;
    }  
    void Fire(bool value)
    {
        if (mHandler)
        {
            mHandler(value);
        }
    }
private:
    std::function<void(bool)> mHandler;
};

class EventListener
{
public:
    explicit EventListener(const std::string& value) : mValue{value} 
    {
        std::cout << mValue << " constructor" << std::endl;
    }
    ~EventListener()
    {
        std::cout << mValue << " destructor" << std::endl;
    }
    void Listen(Event& event)
    {
        event.RegisterHandler(std::bind(&EventListener::Handler, this, std::placeholders::_1));
    }
private:
    void Handler(bool value)
    {
        std::cout << mValue << " event " << value << std::endl;
    }
    std::string mValue;
};

int main()
{
    {
        Event event{};

        auto handler {std::make_shared<EventListener>("first")};
        handler->Listen(event);

        event.Fire(true);
    }
    {
        Event event{};

        {
            auto handler {std::make_shared<EventListener>("second")};
            handler->Listen(event);
        }
        std::make_shared<std::string>("Hello from dangling pointer");

        event.Fire(false);
    }
}

输出:

first constructor
first event 1
first destructor
second constructor
second destructor
om dangling pointer event 0

Test this code online


std::enable_shared_from_this似乎是一种解决方案。

class EventListener : public std::enable_shared_from_this<EventListener>
{
public:
    void Listen(Event& event)
    {
        event.RegisterHandler([sharedThis{shared_from_this()}](bool value)
        {
            sharedThis->Handler(value);
        });
    }

输出:

first constructor
first event 1
first destructor
second constructor
second event 0
second destructor

Test this code online


我知道的两个规则:

  1. 捕获this后,请确保您在析构函数中删除/取消注册/清理捕获。
  2. 使用shared_from_this()不必担心生命周期。

有更好的解决方案吗?有什么建议如何预防/处理这种情况?

2 个答案:

答案 0 :(得分:2)

在广播台上安装侦听器时,需要安排注销。让广播公司存储指向侦听器的共享指针是一个坏计划,因为这意味着广播员突然控制了侦听器的生命周期。这种代码会导致资源泄漏,在没有人关心的情况下,未使用的资源会留下来。

相反,我通常让广播公司存储弱指针。广播时,它首先丢弃所有过时的弱指针,复制目标列表,然后(在未锁定的上下文中)向每个侦听器发送消息。

这可以通过允许侦听器传递共享指针资源或让广播者返回共享指针令牌来实现。

using token=std::shared_ptr<void>;
template<class...Args>
struct broadcaster {
  token listen( std::function<void(Args...)> );
  void shared_listen( std::shared_ptr<std::function<void(Args...)>> );
  std::size_t operator()(Args...) const;
private:
  mutable std::mutex m;
  mutable std::vector< std::weak_ptr<std::function<void(Args...)>> > listeners;
};

充实您的声音,您将获得一个易于使用的不错的广播公司。

侦听类要么存储拥有其侦听权限的std::vector<token>,要么构建一个std::shared_ptr<std::function<...>>(可能通过共享ptr的别名ctor)并将其传入。

在两种情况下,都会使用RAII进行注销,并且监听者对象的生存期完全不受广播公司控制(可能是,实际广播期间的短窗口除外)。另外,注销的取消并不取决于广播公司的收听者人数。

可能有一些内存资源的使用时间比理想的还要长(因为引用计数块由weak_ptr保持活动状态,并且如果您使用的make_shared包括对象的直接内存占用量),则这不适用于框架小部件中有1000多个广播机构,其利用率在0.1%范围内,但在极端情况下效果很好。

答案 1 :(得分:0)

问题中提到的这两个规则仅适用于该主题。

the comments中所述,该代码示例在体系结构上不正确。