【问题标题】:Callback in C++, template member? (2)C++中的回调,模板成员? (2)
【发布时间】:2009-08-16 12:55:42
【问题描述】:

以下回调类是“可调用事物”的通用包装器。我真的很喜欢它的 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;
}

非常感谢!! :-)

【问题讨论】:

  • 很抱歉,它仍然使用模板。模板有什么问题?
  • 它在内部使用模板,但在“main”中查看 API。没有模板。一般来说,模板没有什么问题,我只是觉得它们不需要实现回调。以回调 c1 为例,我不需要说 callback 等...
  • 你只是在重新实现boost::function。改用那个。如果你想知道怎么做,请阅读它的代码。这是一段由顶级 C++ 程序员编写的代码。 :)
  • 你能回答使用 boost::function 来实现我想要的 API 吗?

标签: c++ templates memory-management callback pointer-to-member


【解决方案1】:

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

如果您使用联合,您也可能会相当安全,并且在 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 机制来优化空间,当然也经过了很好的测试。

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

【讨论】:

【解决方案2】:

耶!!!

我最好的解决方案,不使用模板,不使用动态分配,不继承(就像联合一样):

#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.8s,我使用动态分配需要 9.8s,而使用 union 需要 4.2s。它比我使用动态分配的小 60%,比 boost 小 130%。

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

【讨论】:

  • 如果你用了 2000 万次就失去了 6 秒。我想我会选择boost版本。这还不足以抵消可维护性成本。
  • 是的,你的意思是在生产代码中?但关键是知道如何去做。还要考虑一个事实,即 boost 所做的比我想要的要多得多。我想保持简单(这对我来说 = 可维护性)。
  • 对于我作为维护者的我来说,没有什么比使用在网络上经过充分测试、批准和记录的库更简单的了。 OTOH,您的代码需要一个大的“为什么我自己这样做”评论部分,以便我了解它的全部内容。
  • 请注意,您使用的是匿名结构,它是某些编译器的非标准扩展。此外,您的代码会导致未定义的行为 - 有关解释,请参见 Adam Rosenfield 撰写的出色的 answer
【解决方案3】:

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

不过,这种糖很好吃,你可能会想要保留它。此外,您可能还必须将对象放在堆上的其他可调用对象。

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

为什么不使用这些?

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

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

【讨论】:

【解决方案4】:

使用 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。

编辑:

this

【讨论】:

  • @Helltone,启用优化,然后使用 &x 或 ref(x) 推送绑定(否则,它会复制 struct x),然后使用 (...) 初始化而不是 = ...。如果您已经这样做了,请忽略 :) 此外,您的课程不再接近完成。它缺少工作复制机制(如果您尝试复制它,您的回调类会崩溃)。
  • 你的课可能更小,但那是因为它还没有完成。如果您有一个拥有的 RAW 指针,则需要定义复制构造和赋值运算符(否则由于浅拷贝问题可能会导致崩溃)。
  • 没有拥有的原始指针,无需重新定义复制和赋值。
【解决方案5】:

在执行多态性时,您将无法摆脱 new delete,因此最终会尝试将子类复制到父类中并失去子类功能。

【讨论】:

  • 是的,我知道。 callable_from_object 和 callable_from_function 有不同的大小。更糟糕的是,callable_from_object 的大小取决于指向成员的指针的大小,如果对象具有虚拟表,则可能会有所不同。然而我希望使用“联合”的东西可以解决问题......那里有任何 C++ 专家吗?
  • 上帝不要开始使用工会。只需使用 boost 解决方案或其他模板解决方案,删除 new 和模板没有任何好处。
  • 你能回答使用 boost 来实现我想要的 API 吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-17
  • 1970-01-01
  • 2011-08-23
  • 1970-01-01
  • 1970-01-01
  • 2021-04-21
相关资源
最近更新 更多