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

发布于 2024-08-02 10:13:50 字数 1908 浏览 2 评论 0原文

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

非常感谢! :-)

The following callback class is a generic wrapper to "callable things". I really like its API, which has no templates and is very clean, but under the hood there is some dynamic allocation which I was not able to avoid.

Is there any way to get rid of the new and delete in the code below while maintaining the semantics and API of the callback class? I really wish I could.


Needed stuff:

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

The callback class:

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

An API example:

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

Thanks a lot !! :-)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

笑饮青盏花 2024-08-09 10:13:50

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

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

然后,不要使用普通的 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 成员改为指针。您还需要设置一个标志,以便记住是否必须在析构函数中调用删除,或者保留放置缓冲区并显式调用析构函数。

boost::function基本上就是这样做的。然而,它还做了很多其他事情来优化分配。它使用自己的vtable机制来优化空间,当然也经过了很好的测试。

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

You can use placement new. Like, set a maximal limit of size you want to allow callback to have, like 16 bytes. Then, put an unsigned char buffer into your callback class that's exactly that wide, and make sure it's aligned correctly (GCC has an attribute for that, and if you are lucky, microsoft has an attribute for that too).

You may also go fairly safe if you use an union, and beside the char buffer put dummies of the types you want to stuff into it - that will ensure proper alignment too.

Then, instead of using normal new, use placement new, like

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

Then, make the c member a pointer instead. You also need to set a flag so you remember whether you have to call delete in the destructor, or leave the placement buffer alone with explicitly calling the destructor.

That's basically how boost::function does it. It, however, does a whole lot of other stuff to optimize allocation. It uses its own vtable mechanism to optimize space, and of course is very well tested.

Of course, this is not exactly easy to do. But that seems to be the only thing to do about it.

入怼 2024-08-09 10:13:50

YAY!!!

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

#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 秒。而且比我使用动态alloc的小60%,比boost小130%。

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

YAY!!!

My best solution, using no templates, no dynamic allocation, no inheritance (just as union):

#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)();
      };
   };

};

Ok, its a big ugly, but it is FAST. For 20 million iterations, Boost version takes 11.8s, mine using dynamic alloc takes 9.8s and this using union takes 4.2s. And it is 60% smaller than mine using dynamic alloc, and 130% smaller than boost.

Edit: updated default constructor.

你是我的挚爱i 2024-08-09 10:13:50

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

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

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

为什么不使用其中一个呢?

从你的例子来看,如果你继续走你要走的路,你的解决方案将收敛到 boost::function ,而没有它的灵活性。那么为什么不使用它呢?虽然我不相信 boost 开发人员是上帝,但他们是非常有才华的工程师,拥有出色的同行评审流程,从而产生了非常强大的库。我不认为大多数个人或组织能够重新发明更好的图书馆。

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

In your example you could remove the new and delete by removing the callback class. This is just a Decorator on callable_from_object and callable_from_object that provides some syntactic sugar: automatically choosing the correct callable object to delegate to.

However this sugar is nice and you will probably want to keep it. Also you will probably have to place the objects other callable objects on the heap anyway.

To me the bigger question is why are you creating a callback library? If it is just to practice c++ then that is fine, but there are loads of exaples of these already:

Why not use one these?

From looking at your example if you keep on the path you are going your solution will converge towards boost::function without its flexibility. So why not use it? While I don't beleive that the boost developers are Gods they are very tallented engineers with an excellent peer review process that results in very stong libraries. I don't think most individuals or organisations can reinvent better libraries.

If your concern is excessive memeory allocation and deallocation the solution is this case would probably be a custom allocator for the various callable subtypes. But again I prefer to allow other people to research these tecniques and use their libraries.

苏璃陌 2024-08-09 10:13:50

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

编辑:

请参阅

Using boost::function and 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;
}

Yeah it works, but it is 20% slower than my implementation (with dynamic allocation) and code is 80% bigger.

PS. I repeated the main code 20 million times. Boost version takes 11.8s, mine takes 9.8s.

Edit:

See this

剑心龙吟 2024-08-09 10:13:50

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

you wont be able to get rid of new delete as your doing polymorphism and thus it will end up trying to copy the child class into a parent class and loose the child class functionality.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文