3

以下回调类是“可调用事物”的通用包装器。我真的很喜欢它的 API,它没有模板而且非常干净,但在底层有一些我无法避免的动态分配。

有没有什么办法可以去掉下面代码中的newdelete,同时保持回调类的语义和API?我真希望我能。


需要的东西:

// 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;
}

非常感谢 !!:-)

4

5 回答 5

5

您可以使用新的展示位置。例如,设置您希望允许的最大大小限制callback,例如16字节。然后,unsigned char在你的类中放入一个正好那么宽的缓冲区callback,并确保它正确对齐(GCC有一个属性,如果你幸运的话,微软也有一个属性)。

如果您使用联合,您也可能会相当安全,并且在 char 缓冲区旁边放置您想要填充的类型的假人 - 这也将确保正确对齐。

然后,不要使用普通的 new,而是使用placement 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 机制来优化空间,当然也经过了很好的测试。

当然,这并不容易做到。但这似乎是唯一要做的事情。

于 2009-08-16T13:27:43.550 回答
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%。

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

于 2009-08-16T14:35:52.593 回答
1

在您的示例中,您可以通过删除callback类来删除 new 和 delete。这只是一个装饰器callable_from_objectcallable_from_object它提供了一些语法糖:自动选择正确的可调用对象来委托。

然而,这种糖很好,你可能会想要保留它。此外,无论如何,您可能不得不将对象放在堆上的其他可调用对象。

对我来说,更大的问题是你为什么要创建一个回调库?如果只是为了练习 C++ 那很好,但是已经有很多这样的例子了:

为什么不使用这些?

通过查看您的示例,如果您继续前进,您的解决方案将趋向于 boost::function 而没有它的灵活性。那么为什么不使用它呢?虽然我不相信 boost 开发人员是神,但他们是非常有才华的工程师,具有出色的同行评审过程,从而产生了非常强大的库。我不认为大多数个人或组织可以改造更好的图书馆。

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

于 2009-08-16T13:25:28.700 回答
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%。

PS。我将主代码重复了 2000 万次。Boost版本需要11.8s,我需要9.8s。

编辑:

看到这个

于 2009-08-16T14:03:07.967 回答
0

当你做多态性时,你将无法摆脱新的删除,因此它最终会试图将子类复制到父类中并失去子类的功能。

于 2009-08-16T13:03:50.847 回答