【问题标题】:Design of a polling event API轮询事件 API 的设计
【发布时间】:2009-01-09 10:53:21
【问题描述】:

假设您正在设计一个 C++ 窗口库。它可能提供也可能不提供回调 API,但需要提供轮询 API 以促进函数式编程。

轮询 API 是什么样的?

一些选项

SDL 风格

struct Event {
    enum { MousePress, KeyPress } type;
    union {
        struct { Point pos; MouseButton b; } mousePress;
        struct { Modifiers mods; char key; } keyPress;
    };
};
void userCode() {
    for(;;) {
        Event e; if(pollEvent(&e)) {
            switch(e.type) {
                case MousePress: cout<<event.mousePress.pos.x; break; // not typesafe
                case KeyPress: cout<<event.keyPress.key; break;
            }
        }
    }
}

状态风格

struct Input {
    enum { Mouse, Keyboard, Nothing } whatChanged;
    MouseButtonsBitfield pressedButtons;
    bool keysPressed[keyCount];
};
void userCode() {
    for(;;) {
        Input in = pollInput();
        switch(in.whatChanged) {
            // typesafe yay
            case Mouse: cout << "is LMB pressed? " << bool(in.pressedButtons&LeftButton); break;
            case Keyboard: cout << "is A pressed? " << in.keysPressed['A']; break;
        }
    }
}

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

struct Event {
    // transforms listener by notifying it of event,
    // returns transormed listener. nondestructive.
    template<class Listener> // sadly invalid, templates can't be virtual.
                                              // a solution is to make Listener the base
                                              // of a hierarchy and make Listener::handle virtual
                                              // but then we're forced to use imperative style
    virtual Listener transform(Listener const&) =0;
};
struct MousePress : Event { // yay we're extensible via inheritance
    template<class Listener>
    virtual Listener transform(Listener const& listener) {
        return listener.handle(*this); // calls the MousePress overload
    }
    Point pos; MouseButton b;
};
struct KeyPress : Event {
    template<class Listener>
    virtual Listener transform(Listener const& listener) {
        return listener.handle(*this); // calls the KeyPress overload
    }
    Modifiers mods; char key;
};
struct NoEvent : Event {
    template<class Listener>
    virtual Listener transform(Listener const& listener) {
        return listener.handle(*this);
    }
};
struct UserWidget {
    UserWidget handle(NoEvent) {
        return UserWidget();
    }
    UserWidget handle(MousePress p) {
        return (UserWidget) { string("pressed at")+lex_cast<string>(p.pos)) };
    }
    UserWidget handle(KeyPress k) {
        return (UserWidget) { string("pressed key=")+lex_cast<string>(k.key)) };
    }
    string pendingOutput;
};
void userTick(UserWidget const& w) {
    cout<<w.pendingOutput;
    userTick(pollEvent().transform(w));
}
void userCode() {
    userTick(UserWidget());
}

对于 C++ 以外的其他语言的答案是可以的,如果它们提供有趣的见解的话。

请不要在封装方面使用 cmets - 是的,公共字段确实应该是访问器,为了清楚起见,我将其省略了。

【问题讨论】:

    标签: c++ api events polling


    【解决方案1】:

    为了快速回答您的问题,我更喜欢“SDL 样式代码”的简单性。主要是因为您稍微复杂一点的“状态样式”浪费了内存并且绝对不会给您买任何东西(见下文),并且您折磨的“功能性伪 C++”样式中的递归将在几毫秒内溢出堆栈。

    “State Style”:您在“State Style”代码中的“typesafe yay”有点没有根据。您仍在根据另一个成员上的 switch 来决定访问哪个成员,因此该代码具有与“SDL 样式”代码相同的所有弱点 - 对于您使用 SDL 样式代码可能犯的任何错误导致将内存解释为错误的类型,您将犯同样严重的错误,即使用 State 样式代码访问未初始化的成员。

    “函数式伪 C++ 风格”:现在您已经到了某个地方,从基本事件类型继承了不同的事件类型。显然愚蠢的递归需要变成一个循环,并且有一些小事情需要整理(我认为您在UserWidget 中名为transform() 的3 个方法希望被称为handle();我猜你可以解决没有使用 Boost.Function 或类似的模板虚方法的问题)。我认为这种方法有潜力,虽然我更喜欢 SDL 风格的简单性。

    但更根本的是:我质疑是否需要轮询界面。 pollEvent() 不能阻止是有原因的吗?就目前而言,所有 3 个代码段在 99.99% 的时间里都在消耗 CPU 时间。

    【讨论】:

    • StateStyle 类型安全的。 pollInput 返回完整的对象。例如,即使是鼠标点击,返回的事件也有正确的键盘数组。是的,我对递归很傻。现在在玩 Haskell - 它优化了尾递归到一个循环,所以那里没有堆栈溢出 :)
    • 我确实喜欢 FP 最小化可变状态的想法 - 因此是“转换”。使用像您建议的通知系统(“句柄”)确实解决了模板问题,因为我不再需要模板,但迫使我进行命令式编程。那好吧。猜猜我不能在 C++ 中做 haskell :)
    • 如果我放弃它,那么就不需要轮询界面,不。 IAC 这个东西是为类似游戏的应用程序设计的,无论如何我都会消耗 100% 的 CPU。
    • 关于类型安全:我的观点是类型安全不是最终目标,正确性才是,虽然您的“状态风格”方法在技术上是类型安全的,但它与非类型安全具有完全相同的弱点本例中为 SDL 样式。
    • 我对 UserWidget 中的 s/transform/handle/g 的建议仅仅是因为 transform() 调用方法“handle()”,而不是方法“transform()”。 (您的原始代码因此无法编译。)它并没有消除对模板的需求——正如您所说,您需要一个基类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-21
    • 2019-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多