Algorithm for callbacks using the std :: function

I am currently trying to execute code that does 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; } 

It works very well. However, I am stuck in this template. I would like to check if a callback has already been registered, and I would like to be able to cancel the callback by removing it from the vector. I just found out that std::function objects cannot be compared, which means the impossibility of finding their existence in the container.

So I need an alternative. Of course, I would like to save compilation type checks and the ability to register methods of arbitrary classes.

How can I achieve a similar solution that can unregister and double check registration? Are there any compromises I have to accept?

+3
source share
3 answers

The main problem is that most functional objects are not comparable. While simple function pointers and custom function objects with the equality operator can be compared, lambdas, the result of std::bind() , etc. Can not. Using the address of function objects to identify them is usually not an appropriate approach, since objects are usually copied. It is possible to use std::reference_wrapper<Fun> to avoid copying them, but the objects stored in std::function<F> will still have a different address.

With C ++ 11 variation templates, itโ€™s easy enough to create a custom version of std::function<...> that provides a means of comparison. There may be two versions:

  • One version that accepts arbitrary function objects, but can obviously only compare the compared function objects: depending on whether the constructor argument is used for the equality operator or not a suitable base class.
  • One version, which always provides a working comparison and, obviously, cannot be used with objects that do not correspond to equality.

The latter is a little easier to identify and will 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 think I forgot (and / or did not understand) something, but rather what I need. For an optionally comparable version, you have two concrete versions: one that is implemented above, and the other that always returns false . Depending on whether the == operator for Fun exists in the constructor, you must create one or the other.

+3
source

Well, 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 are not comparable, but pointers. The reason functions are not comparable, so it is impossible to determine whether the functions are equal (not only in C ++, in computer science). Ie, there is no way to determine if functions have the same meaning. However, using pointers, we can at least see if they occupy the same space in memory.

I am not very familiar with how bind and other higher order std functions work under the hood. Take care of this, and you may have to do your own checks when the callback is registered or before you call bind to make sure you don't have two duplicated bundles of the same function, but which take up different places in memory.

+1
source

I think that there is a fundamental problem with automatic double-registration detection. When do you think the two functions are identical? For regular function pointers, you can use an address, but with std::bind and especially lambda functions you will have 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 not possible for the compiler to detect that two lambda functions are semantically identical.

I would modify register to return an identifier (or a connection object, for example, in Boost.Signal2) that clients can use to unregister callbacks. However, this will not prevent double registration.

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

Source: https://habr.com/ru/post/1212154/


All Articles