Using std::function and std::bind to store callback and handle object deletion.

人走茶凉 提交于 2019-11-30 05:10:16

Template setFunction so that you can accept pointer-to-member-of-derived, and don't have to write 12 overloads for the combinations of cv/ref qualifiers.

template<class D, class D2, class F>
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) {
    // optionally static_assert that D2 is a base of D.
    m_function.first  = sp;
    m_function.second = std::bind(member, sp.get(), std::placeholders::_1);
}

Obviously you need to make sure you lock() m_function.first before calling m_function.second.

Alternatively, just use a lambda that captures both the weak_ptr and the member function pointer:

std::function<void(double)> m_function;

template<class D, class D2, class F>
void setFunction(const std::shared_ptr<D> &sp, F D2::* member) {
    std::weak_ptr<D> wp = sp;
    m_function = [wp, member](double d) {
        if(auto sp = wp.lock()){
             ((*sp).*member)(d);
        }
        else {
             // handle pointer no longer valid case.
        }
    };
}

I like decoupling my listener/broadcaster from the implementation of the listener.

This means I cannot place requirements on the listener. It cannot require the listener be allocated in a particular way.

The easiest method I have found is to have the broadcaster return a token whose lifetime determines the lifetime of the connection.

using token = std::shared_ptr<void>;

template<class...Args>
struct broadcaster {
  using target = std::function<void(Args...)>;
  using wp_target = std::weak_ptr<target>;
  using sp_target = std::shared_ptr<target>;
  static sp_target wrap_target( target t ) {
    return std::make_shared<target>(std::move(t));
  };

  token start_to_listen( target f ) {
    auto t = wrap_target(std::move(f));
    targets.push_back(t);
    return t;
  }
  void broadcast( Args... args ) {
    targets.erase(
      std::remove_if( targets.begin(), targets.end(),
        [&]( wp_target t )->bool { return t.lock(); }
      ),
      targets.end()
    );
    auto targets_copy = targets; // in case targets is modified by listeners
    for (auto wp : targets_copy) {
      if (auto sp = wp.lock()) {
        (*sp)(args...);
      }
    }
  }
  std::vector<wp_target> targets;
};

this forces people who register listeners to keep std::shared_ptr<void> around.

We can even make it fancier, where the destruction of the last shared_ptr<void> actually removes the listener from the list immediately. But the above lazy deregistration seems to work reasonably well in my experience, and it is relatively easy to make it multi-thread friendly. (one serious problem is what happens when a broadcast event removes or adds things to the list of listeners: adapting the above for it to work is nice and easy with the rule that listeners added when broadcasting do not get the broadcast, and listeners removed during broadcasting do not get the broadcast. Listeners removed concurrently during broadcast can get the broadcast in most of my implementations... That gets expensive to avoid.)


We could instead decouple it differently. The listener could pass a std::function and a std::weak_ptr separately to the broadcaster, who stores both and only calls the std::function if the std::weak_ptr is valid.

I like Yakk's approach. Here's an updated version that fixes a few compile issues (e.g. cannot name function 'register'). It also adds a rm_callback method for clients to easily remove their registration without forcing their registration token to go out of scope or knowing the internals. I didn't like scanning the list every time an event was broadcast so I added a deleter on the shared pointer which does the cleanup task. All new bugs introduced or inefficiencies are mine. The alert reader should be aware of threading issues when modifying the list while broadcasting...

using token = std::shared_ptr<void>;
template<class...Args>
struct broadcaster {
    using target = std::function<void(Args...)>;
    using wp_target = std::weak_ptr<target>;
    using sp_target = std::shared_ptr<target>;

    token add_callback(target f) {
        sp_target t(new target(std::move(f)), [&](target*obj) { delete obj; cleanup(); });
        targets.push_back(t);
        return t;
    }

    static void rm_callback(token& t)
    {
        t.reset();
    }

    void cleanup()
    {
        targets.erase(
            std::remove_if(targets.begin(), targets.end(),
                [](wp_target t) { return t.expired(); }
            ),
            targets.end()
        );
    }

    void broadcast(Args... args) {
        for (auto wp : targets) {
            if (auto sp = wp.lock()) {
                (*sp)(args...);
            }
        }
    }

    std::vector<wp_target> targets;
};

// declare event taking a string arg
broadcaster<std::string> myEvent;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!