C++中的回调函数,模板成员?(2)

3
以下回调类是通用的“可调用物”的包装器。我非常喜欢它的API,它没有模板而且非常干净,但在底层有一些我无法避免的动态分配。
是否有任何方法可以在保持回调类的语义和API的同时消除下面代码中的new和delete?我真的希望我能做到。
所需内容:
// base class for something we can "call"
class callable {
  public:
  virtual void operator()() = 0;
  virtual ~callable() {}
};

// wraps pointer-to-members
template<class C>
class callable_from_object : public callable {
  public:
  callable_from_object(C& object, void (C::*method)())
         : o(object), m(method) {}

  void operator()() {
    (&o ->* m) ();
  }
  private:
  C& o;
  void (C::*m)();
};

// wraps pointer-to-functions or pointer-to-static-members
class callable_from_function : public callable {
   public:
   callable_from_function(void (*function)())
         : f(function) {}

   void operator()() {
      f();
   };
   private:
   void (*f)();
};

回调类:

回调类:


// generic wrapper for any callable
// this is the only class which is exposed to the user
class callback : public callable {
   public:
   template<class C>
   callback(C& object, void (C::*method)())
         : c(*new callable_from_object<C>(object, method)) {}
   explicit callback(void (*function)())
         : c(*new callable_from_function(function)) {}
   void operator()() {
      c();
   }
   ~callback() {
      std::cout << "dtor\n"; // check for mem leak
      delete &c;
   }
   private:
   callable& c;
};

一个API示例:

struct X {
  void y() { std::cout << "y\n"; }
  static void z() { std::cout << "z\n"; }
} x;

void w() { std::cout << "w\n"; }

int main(int, char*[]) {
   callback c1(x, &X::y);
   callback c2(X::z);
   callback c3(w);
   c1();
   c2();
   c3();
   return 0;
}

非常感谢!!:-)

抱歉要说它仍然使用模板。模板有什么问题吗? - Lodle
它在内部使用模板,但是看一下“main”中的API。没有模板。总的来说,模板并没有什么问题,我只是觉得它们不需要用于实现回调函数。例如,看一下回调函数c1,我不需要写callback<X>等等... - Giovanni Funchal
6
你只是在重新实现 boost::function。建议直接使用它,如果你想了解如何使用它,可以查看它的代码。这是由顶尖的C++程序员编写的代码 :) - Johannes Schaub - litb
你能使用boost::function来实现我的API并接近我所需的功能吗? - Giovanni Funchal
如果你认为“没有模板”是应用于代码的一个积极属性,那么请尝试重新实现STL而不使用模板。我遇到过太多的代码,作者试图像避开瘟疫一样避免使用模板,从而创建了臃肿和冗余的代码--与简洁相反。 - sbi
显示剩余2条评论
5个回答

5
您可以使用定位new。例如,设置一个最大限制大小,比如允许callback的大小为16字节。然后,在您的callback类中放置一个恰好宽度为此的unsigned char缓冲区,并确保它的对齐方式正确(GCC具有该属性,如果您很幸运,Microsoft也有该属性)。
如果您使用union,那么除了char缓冲区之外还可以放置要放入其中的类型的虚拟对象 - 这也将确保正确的对齐方式。
然后,不要使用普通的new,而是使用定位new,例如:
if(placement_allocated< callable_from_object<C> >::value) {
  new ((void*)buffer.p) // union member p is the unsigned char buffer
    callable_from_object<C>(object, method);
  c = (callable*)buffer.p;
} else {
  c = new callable_from_object<C>(object, method);
}

然后,将 c 成员变为指针。您还需要设置一个标志,以便记住是否必须在析构函数中调用delete,或者通过显式调用析构函数来保留放置缓冲区。这基本上就是 boost::function 的工作原理。它还执行了大量其他优化分配的操作。它使用自己的vtable机制来优化空间,并且当然经过了很好的测试。当然,这并不容易做到。但这似乎是唯一可行的方案。

是的,看起来联合体(union)是更好的解决方案。https://dev59.com/z0jSa4cB1Zd3GeqPJem0#1284431 - Giovanni Funchal

3

太棒了!!!

我的最佳解决方案,不使用模板、动态分配或继承(只使用联合):

#include <iostream>
#include <stdexcept>

class callback {

   public:

   callback() :
         type(not_a_callback) {}

   template<class C>
   callback(C& object, void (C::*method)()) :
         type(from_object),
         object_ptr(reinterpret_cast<generic*>(&object)),
         method_ptr(reinterpret_cast<void (generic::*) ()>(method)) {}

   template<typename T>
   explicit callback(T function) :
         type(from_function),
         function_ptr((void (*)()) function) {}

   void operator()() {
      switch(type) {
         case from_object:
            (object_ptr ->* method_ptr) ();
            break;
         case from_function:
            function_ptr();
            break;
         default:
            throw std::runtime_error("invalid callback");
      };
   }

   private:

   enum { not_a_callback, from_object, from_function } type;

   class generic;

   union {
      void (*function_ptr)();
      struct {
         generic* object_ptr;
         void (generic::*method_ptr)();
      };
   };

};

好的,虽然有点丑陋,但速度很快。对于2000万次迭代,使用Boost版本需要11.8秒,使用动态分配的版本需要9.8秒,而使用联合体只需要4.2秒。并且它比使用动态分配的版本小60%,比Boost小130%。

编辑:更新了默认构造函数。


如果你使用2000萬次需要花費6秒,我認為我會選擇boost版本。這種改進並不足以彌補可維護性成本的損失。 - Martin York
1
是的,你的意思是在生产代码中吗?但重点是知道如何做。还要考虑到boost做的比我想要的多得多。我想保持简单(这对我来说意味着可维护性)。 - Giovanni Funchal
作为一个维护者,如果你使用一个经过充分测试、批准并在网络上有文档支持的库,那么对我来说,没有什么比这更简单的了。另一方面,如果你的代码需要一个大的“我为什么要这样做”的注释部分,那么我才能理解它的全部含义。 - sbi
请注意,您正在使用匿名结构体,这是某些编译器的非标准扩展。此外,您的代码会导致未定义的行为 - 有关说明,请参见Adam Rosenfield的这个答案 - wmamrak

1

使用 boost::function 和 boost::bind。

typedef boost::function<void ()> callback;

int main(int, char*[]) {
   callback d1 = boost::bind(&X::y, &x);
   callback d2 = &X::z;
   callback d3 = w;
   d1();
   d2();
   d3();
   return 0;
}

是的,它能工作,但比我的实现(使用动态分配)慢20%,代码也大80%。

附言:我重复了主要代码2000万次。Boost版本需要11.8秒,而我的只需要9.8秒。

编辑:

请参见this


@Helltone,启用优化,然后使用&x或ref(x)来推送绑定(否则,它会复制结构体x),然后使用(...)初始化而不是=....如果您已经这样做了,请忽略 :) 另外,您的类不再完整。它缺少一个工作复制机制(如果尝试复制它,则回调类会崩溃)。 - Johannes Schaub - litb
1
你的类可能会更小,但这是因为它还没有完成。如果你有一个拥有原始指针的类(否则由于浅复制问题可能导致崩溃),需要定义复制构造函数和赋值运算符。 - Martin York
没有拥有的原始指针,不需要重新定义复制和赋值。 - Giovanni Funchal

1
在你的示例中,通过删除回调类,可以删除new和delete。这只是对callable_from_object的一个修饰器,提供了一些语法糖:自动选择正确的可调用对象进行委派。
然而,这个语法糖很好,你可能会想保留它。此外,您可能还需要将其他可调用对象放在堆上。
对我来说,更重要的问题是为什么要创建一个回调库?如果只是为了练习c++,那就没事了,但已经有很多这样的例子: 为什么不使用其中一个呢?

根据您的示例,如果您继续这样做,您的解决方案将会趋于 boost::function 而且没有它的灵活性。那为什么不使用它呢?虽然我不认为 boost 的开发人员是神,但他们是非常有才华的工程师,并拥有出色的同行审查流程,从而产生非常强大的库。我认为大多数个人或组织无法重新发明更好的库。

如果您关心过度的内存分配和释放,在这种情况下的解决方案可能是各种可调用子类型的自定义分配器。但再次,我更喜欢让其他人研究这些技术并使用他们的库。


我不同意你的第一段。当你说“为什么不使用其中之一”时,我的回答是:为什么不向我解释如何使用? - Giovanni Funchal
https://dev59.com/z0jSa4cB1Zd3GeqPJem0#1284374 - Giovanni Funchal

0

由于你正在进行多态操作,因此无法摆脱 new delete 操作符。否则,它会试图将子类复制到父类中,并失去子类的功能。


是的,我知道。callable_from_object和callable_from_function有不同的大小。更糟糕的是,callable_from_object的大小取决于成员指针的大小,如果对象具有虚表,那么这个大小可能会有所不同。然而,我希望使用“union”可以解决这个问题...有没有C++专家? - Giovanni Funchal
上帝不要开始使用联合体。只需使用boost解决方案或其他模板解决方案,去除new和templates没有任何好处。 - Lodle
你可以使用Boost来实现我想要的功能,能否用接近于我的API的方式回答一下? - Giovanni Funchal
https://dev59.com/z0jSa4cB1Zd3GeqPJem0#1284374 - Giovanni Funchal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接