c++ 轮询事件API的设计

d7v8vwbk  于 2023-07-01  发布在  其他
关注(0)|答案(1)|浏览(148)

假设你正在设计一个C++窗口库。它可能提供也可能不提供回调API,但需要提供轮询API以促进函数式编程风格。
轮询API是什么样子的?

部分选项

SDL风格

  1. struct Event {
  2. enum { MousePress, KeyPress } type;
  3. union {
  4. struct { Point pos; MouseButton b; } mousePress;
  5. struct { Modifiers mods; char key; } keyPress;
  6. };
  7. };
  8. void userCode() {
  9. for(;;) {
  10. Event e; if(pollEvent(&e)) {
  11. switch(e.type) {
  12. case MousePress: cout<<event.mousePress.pos.x; break; // not typesafe
  13. case KeyPress: cout<<event.keyPress.key; break;
  14. }
  15. }
  16. }
  17. }

状态样式

  1. struct Input {
  2. enum { Mouse, Keyboard, Nothing } whatChanged;
  3. MouseButtonsBitfield pressedButtons;
  4. bool keysPressed[keyCount];
  5. };
  6. void userCode() {
  7. for(;;) {
  8. Input in = pollInput();
  9. switch(in.whatChanged) {
  10. // typesafe yay
  11. case Mouse: cout << "is LMB pressed? " << bool(in.pressedButtons&LeftButton); break;
  12. case Keyboard: cout << "is A pressed? " << in.keysPressed['A']; break;
  13. }
  14. }
  15. }

有趣的函数式伪C++风格

  1. struct Event {
  2. // transforms listener by notifying it of event,
  3. // returns transormed listener. nondestructive.
  4. template<class Listener> // sadly invalid, templates can't be virtual.
  5. // a solution is to make Listener the base
  6. // of a hierarchy and make Listener::handle virtual
  7. // but then we're forced to use imperative style
  8. virtual Listener transform(Listener const&) =0;
  9. };
  10. struct MousePress : Event { // yay we're extensible via inheritance
  11. template<class Listener>
  12. virtual Listener transform(Listener const& listener) {
  13. return listener.handle(*this); // calls the MousePress overload
  14. }
  15. Point pos; MouseButton b;
  16. };
  17. struct KeyPress : Event {
  18. template<class Listener>
  19. virtual Listener transform(Listener const& listener) {
  20. return listener.handle(*this); // calls the KeyPress overload
  21. }
  22. Modifiers mods; char key;
  23. };
  24. struct NoEvent : Event {
  25. template<class Listener>
  26. virtual Listener transform(Listener const& listener) {
  27. return listener.handle(*this);
  28. }
  29. };
  30. struct UserWidget {
  31. UserWidget handle(NoEvent) {
  32. return UserWidget();
  33. }
  34. UserWidget handle(MousePress p) {
  35. return (UserWidget) { string("pressed at")+lex_cast<string>(p.pos)) };
  36. }
  37. UserWidget handle(KeyPress k) {
  38. return (UserWidget) { string("pressed key=")+lex_cast<string>(k.key)) };
  39. }
  40. string pendingOutput;
  41. };
  42. void userTick(UserWidget const& w) {
  43. cout<<w.pendingOutput;
  44. userTick(pollEvent().transform(w));
  45. }
  46. void userCode() {
  47. userTick(UserWidget());
  48. }

C++以外的其他语言的答案也可以,如果它们提供了有趣的见解。
请不要对封装做任何评论--是的,公共字段确实应该是访问器,为了清楚起见,我省略了这一点。

7rfyedvj

7rfyedvj1#

为了快速回答您的问题,我更喜欢“SDL风格代码”的简单性。主要是因为你稍微复杂一点的“状态风格”浪费内存,而且什么也买不到(见下文),而你折磨人的“函数式伪C++”风格中的递归将在几毫秒内溢出堆栈。

“状态样式”:您在“State Style”代码中的“typesafe yay”有点不合理。您仍然需要根据另一个成员上的switch来决定访问哪个成员,因此该代码具有“SDL样式”代码所具有的所有相同的弱点-对于您可能使用SDL样式代码所犯的任何错误,导致将内存解释为错误的类型,您将犯同样严重的错误,即使用State样式代码访问未初始化的成员。
“函数式伪C++风格”:现在,您已经取得了一些进展,从一个基本事件类型继承了不同的事件类型。显然,愚蠢的递归需要变成一个循环,还有一些小事情需要整理(我认为您在UserWidget中名为transform()的3个方法希望被称为handle();我猜您可以使用Boost.Function或类似的方法解决没有模板虚方法的问题)。我认为这种方法很有潜力,尽管我更喜欢SDL风格的简单性。

但更根本的是:我质疑轮询接口的必要性。为什么pollEvent()不能阻止?就目前而言,所有3个代码段都在消耗CPU时间,99.99%的时间都没有做任何事情。

相关问题