3

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

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对象无法比较,这意味着无法在容器中搜索它们的存在。

所以我需要一个替代方案。当然,我想保留编译时类型检查和注册任意类方法的能力。

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

4

3 回答 3

3

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

std::function<...>使用 C++11 可变参数模板,创建提供比较工具的自定义版本相当容易。甚至可能有两个版本:

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

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

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您将创建一个或另一个。

于 2014-12-23T21:09:29.217 回答
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 之前执行自己的检查,以确保您没有两个相同函数的重复绑定,但它们占据了内存中的不同位置。

于 2014-12-23T20:26:42.020 回答
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;
};
于 2014-12-23T21:50:58.240 回答