使用std :: function进行回调的替代方法

时间:2014-12-23 20:14:49

标签: c++ c++11 callback std-function stdbind

目前我正在尝试基本上执行以下操作的代码:

void f(int x) { cout << "f("<<x<<")" << endl; }

class C
{
public:
   void m(int x) { cout << "C::m("<<x<<")" << endl; }
};

class C2
{
public:
   void registerCallback(function<void(int)> f)
   {
      v.push_back(f);
   }

private:
   vector<function<void(int)>> v;

   void callThem()
   {
      for (int i=0; i<v.size(); i++)
      {
         v[i](i);
      }
   }
};

int main()
{
   C2 registrar;

   C c;
   registrar.registerCallback(&f); // Static function
   registrar.registerCallback(bind(&C::m, &c, placeholders::_1)); // Method

   return 0;
}

这非常有效。但是我遇到了这种模式。我想检查一个回调是否已经注册,我希望能够通过从向量中删除它来取消注册回调。我刚刚得知std::function个对象无法比较,这意味着无法在容器中搜索它们的存在。

所以我需要另一种选择。当然我想保留编译时类型检查和注册任意类方法的能力。

如何实现类似的解决方案,允许取消注册回调并检查双重注册?我需要接受任何权衡吗?

3 个答案:

答案 0 :(得分:3)

潜在的问题是大多数功能对象无法比较。虽然可以比较普通函数指针和具有相等运算符的用户定义函数对象,但lambdas,std::bind()的结果等不能。使用函数对象的地址来识别它们通常不是合适的方法,因为对象往往被复制。 可能可以使用std::reference_wrapper<Fun>来避免复制它们,但存储在std::function<F>中的对象仍然会有不同的地址。

使用C ++ 11可变参数模板,可以非常轻松地创建提供比较工具的std::function<...>自定义版本。甚至可能有两个版本:

  • 一个版本接受任意函数对象,但显然只能比较可比较的函数对象:取决于构造函数参数是否提供了相等运算符,使用了合适的基类。
  • 一个版本总是提供工作比较,显然不能与不等同的对象一起使用。

后者稍微容易定义,看起来像这样:

template <typename...> class comparable_function;
template <typename RC, typename... Args>
class comparable_function<RC(Args...)> {
    struct base {
        virtual ~base() {}
        virtual RC    call(Args... args) = 0;
        virtual base* clone() const = 0;
        virtual bool  compare(base const*) const = 0;
    };
    template <typename Fun>
    struct concrete: base {
        Fun fun;
        concrete(Fun const& fun): fun(fun) {}
        RC call(Args... args) { return this->fun(args...); }
        base* clone() const { return new concrete<Fun>(this->fun); }
        bool compare(base const* other) const {
             concrete const* o = dynamic_cast<concrete<Fun>>(other);
             return o && this->fun == o->fun;
        }
    };
    std::unique_ptr<base> fun;
public:
    template <typename Fun>
    comparable_function(Fun fun): fun(new concrete<Fun>(fun)) {}
    comparable_function(comparable_function const& other): fun(other.fun->clone()) {}
    RC operator()(Args... args) { return this->fun->call(args); }
    bool operator== (comparable_function const& other) const {
        return this->fun->compare(other.fun.get());
    }
    bool operator!= (comparable_function const& other) { return !(this == other); }
};

我想,我已经忘记(和/或错误)某些东西,而不是那些需要的东西。对于可选的可比较版本,您有concrete的两个版本:一个是按上述方式实现的,另一个是始终返回false的版本。根据构造函数中==是否有运算符Fun,您可以创建一个或另一个。

答案 1 :(得分:1)

那么,如果你做了类似的事情呢?

class C2
{
public:
   void registerCallback(function<void(int)>* f)
   {
      v.push_back(f);
   }

private:
   vector<function<void(int)>*> v;

   void callThem()
   {
      for (int i=0; i<v.size(); i++)
      {
         v[i][0](i);
      }
   }
};

功能不具有可比性,但指针却是如此。函数不具有可比性的原因是无法确定函数是否相等(不仅仅是在C ++中,在计算机科学中)。即,无法确定函数是否具有相同的值。但是,通过使用指针,我们至少可以看到它们是否在内存中占据相同的空间。

我不太熟悉bind和其他std高阶函数如何在幕后工作。使用它时要小心,在注册回调时或在调用bind之前,您可能必须执行自己的检查,以确保您没有两个相同功能的重复绑定但占用不同的位置存储器中。

答案 2 :(得分:1)

我认为自动检测双重注册存在根本问题。你什么时候认为两个功能是相同的?对于普通函数指针,您可以使用地址,但使用std::bind,特别是lambda函数,您将遇到问题:

class C2
{
public:
   void registerCallback(??? f)
   {
      if (v.find(f, ???) == v.end())
          v.push_back(f);
   }
private:
   vector<function<void(int)>> v;
};

void f1(int);
void f3(int, int);
void f2(int)
{
   C2 c;
   c.registerCallback(f1);
   c.registerCallback(f1); // could be detected by address
   c.registerCallback([](int x) {});
   c.registerCallback([](int x) {}); // detected ?!? 
   c.registerCallback(std::bind(f3, _1, 1);
   c.registerCallback([](int x) {f3(x,1);}) ; // detected? 
}

编译器不可能检测到两个lambda函数在语义上是相同的。

我会更改register以返回ID(或Boost.Signal2中的连接对象),客户端可以使用它来取消注册回调。但这并不妨碍双重注册。

class C2
{
public:
   typedef ??? ID;
   ID registerCallback(??? f)
   {
      ?? id = new_id();
      v[id] = f;
   }
private:
   map<???, function<void(int)>> v;
};