目前我正在尝试基本上执行以下操作的代码:
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
个对象无法比较,这意味着无法在容器中搜索它们的存在。
所以我需要另一种选择。当然我想保留编译时类型检查和注册任意类方法的能力。
如何实现类似的解决方案,允许取消注册回调并检查双重注册?我需要接受任何权衡吗?
答案 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;
};