首页 文章

使用std :: function和std :: bind来存储回调并处理对象删除 .

提问于
浏览
10

我想实现一个管理器,使用C 11将回调存储到多态类的成员函数 . 问题是我不知道如何处理成员所属的对象被删除或应该被删除的情况我希望使界面尽可能简单 .

所以我想到了以下内容:将 std::weak_ptr 存储到该对象以及将 std::function 存储到该成员 .

以下似乎有效:

class MyBase {
public:
    MyBase() {}
    virtual ~MyBase() {}
};
//--------------------------------------------------

class MyClass : public MyBase {
public:
    MyClass() : MyBase() {}
    void myDouble(double val) const { std::cout << "Value is: " << val << std::endl; }
};
//--------------------------------------------------

Class Manager {
public:
    void setFunction(std::weak_ptr<MyBase> base, std::function<void(double)> func) {
        m_function.first  = base;
        m_function.second = func;
    }
private:
    std::pair<std::weak_ptr<MyBase>, std::function<void(double)>> m_function;
};

要使用它:

Manager db;
std::shared_ptr<MyClass> myClass = std::make_shared<MyClass>();
db.setFunction(myClass, std::bind(&MyClass::myDouble, myClass, std::placeholders::_1));

现在我想隐藏用户的 std::bind 部分,这样他只需要调用:

db.setFunction(myClass, &MyClass::myDouble);

所以我想在我的经理函数中得到几乎以下的工作:

void setFunc2(std::weak_ptr<MyBase> base, std::function<void(double)> func) {
    m_function.first  = base;
    m_function.second = std::bind(func, base, std::placeholders::_1);
}

但上面给出了错误:

error: no match for 'operator=' (operand types are 'std::function<void(double)>' and 
'std::_Bind_helper<false, std::function<void(double)>&, std::weak_ptr<MyBase>&, const std::_Placeholder<1>&>::type {aka std::_Bind<std::function<void(double)>(std::weak_ptr<MyBase>, std::_Placeholder<1>)>}')
         m_function.second = std::bind(func, base, std::placeholders::_1);

有没有更好的方法来做到这一点,或者可能是一种方法来实现这一点?

我注意到一些有趣的东西 . 如果我使用 std::shared_ptruse_count() 会在原始代码中调用 std::bind 时增加 . 因此我无法手动重置/销毁对象,除非我取消了我的经理上的成员 . 这种行为记录在哪里,我通常使用cppreference

我看了下面的问题,但似乎无法让它解决我的问题:How can I use polymorphism with std::function?

3 回答

  • 2

    模板 setFunction 使您可以接受指向成员的指针,而不必为cv / ref限定符的组合编写12次重载 .

    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);
    }
    

    显然你需要在调用 m_function.second 之前确保你 lock() m_function.first .

    或者,只需使用捕获 weak_ptr 和成员函数指针的lambda:

    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.
            }
        };
    }
    
  • 7

    我喜欢将我的监听器/广播器与侦听器的实现分离 .

    这意味着我无法对听众提出要求 . 它不能要求以特定方式分配侦听器 .

    我找到的最简单的方法是让广播公司返回一个令牌,其生命周期决定了连接的生命周期 .

    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;
    };
    

    这会迫使注册听众的人保持 std::shared_ptr<void> .

    我们甚至可以让它更漂亮,最后 shared_ptr<void> 的破坏实际上立即从列表中删除了监听器 . 但是上述懒惰的注销似乎在我的经验中运作得相当好,并且使其多线程友好相对容易 . (一个严重的问题是,当广播事件移除或添加到侦听器列表中时会发生什么:使用上述内容使其工作是很好的,并且使用侦听器在广播时不添加广播时添加的规则,以及在广播不接收广播 . 在广播期间同时删除的听众可以在我的大多数实现中获得广播......这样做会避免昂贵 . )


    我们可以改为以不同方式对它进行解耦 . 监听器可以分别将 std::functionstd::weak_ptr 传递给广播公司,广播公司将存储两者,并且只有在 std::weak_ptr 有效时才调用 std::function .

  • 1

    我喜欢Yakk的做法 . 这是一个更新版本,修复了一些编译问题(例如,无法命名函数'register') . 它还为客户端添加了一个rm_callback方法,可以轻松删除他们的注册,而不会强制他们的注册令牌超出范围或了解内部 . 每次广播事件时我都不喜欢扫描列表所以我在共享指针上添加了一个删除器来执行清理任务 . 引入的所有新错误或效率低下都是我的 . 在广播时修改列表时,警报阅读器应该知道线程问题...

    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;
    

相关问题