Alterantive for callbacks using std::function

别来无恙 提交于 2020-01-04 03:59:20

问题


Currently I am trying out a code that does essentially the following:

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

This works pretty well. However I got stuck with this pattern. I would like to check if a callback already has been registered and I would like to be able to unregister a callback by removing it from the vector. I just learned that std::function objects can not be compared which means it is impossible to search for their existence in the container.

So I need an alternative. Of course I would like to keep compile-time type checks and the ability to register methods of arbitrary classes.

How can I achieve a similar solution that allows unregistering the callback and checking for double registration? Are there any trade-offs I need to accept?


回答1:


The underlying problem is that most function objects are not comparable. While plain function pointers and user-defined function objects with an equality operator can be compared, lambdas, the result of std::bind(), etc. cannot. Using the address of function objects to identify them is generally not a suitable approach because the objects tend to be copied. It may be possible to use std::reference_wrapper<Fun> to avoid having them copied but the objects stored in a std::function<F> will still have a different address.

With C++11 variadic templates it is reasonably easy to create a custom version of std::function<...> which provides comparison facilities. There may even be two versions thereof:

  • One version which accepts arbitrary function objects but can, obviously, only compare comparable function objects: depending on whether the constructor argument provides an equality operator or not a suitable base class is used.
  • One version which always provides a working comparison and, obviously, fails to be usable with non-equality-comparable objects.

The latter is slightly easier to define and would look something like this:

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

I guess, I have forgotten (and/or mistyped) something but rather that's what's needed. For the optionally comparable version you'd have two versions of concrete: one which is implemented as above and another one which always returns false. Depending on whether there is an operator == for the Fun in the constructor you'd create one or the other.




回答2:


Well, what if you did something like:

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

Functions aren't comparable, but pointers are. The reason functions aren't comparable is that there is no way to determine if functions are equal (not just in C++, in computer science). I.e., there is no way to determine if functions have the same value. However, by using pointers, we can at least see if they occupy the same space in memory.

I'm not very familiar with how bind and other std higher-order functions work under-the-hood. Take care when using this, and you may have to perform your own checks when a callback is registered or before you call bind, to make sure that you don't have two duplicate binds of the same function but that occupy different places in memory.




回答3:


I think there is a fundamental issue with the automatic detection of double-registration. When do you consider two functions to be identical? For normal function pointers, you could use the address, but with std::bind and especially lambda functions, you will be having a problem:

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

It is impossible for the compiler to detect that two lambda functions are semantically identical.

I would change register to return a ID (or connection object like in Boost.Signal2) which can be used by clients to deregister callbacks. This will not prevent double registrations, though.

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


来源:https://stackoverflow.com/questions/27627380/alterantive-for-callbacks-using-stdfunction

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