问题
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