Stroustrup 的《编程原理与实践》第 3 版图解:为什么将回调函数传递给 Window::timer_wait 会得到意想不到的结果?

问题描述 投票:0回答:1

Stroustrup 的《使用 C++ 编程原理与实践》第三版中的 14.6 节(简单动画)使用了他的 PPP 图形库,而该库又使用了 QT。我正在尝试了解其中涉及的内容。 以下程序按预期工作:最初它显示两个黑色圆圈。 2 秒后,第一个圆圈的颜色变为红色。再过四秒后,第二个圆圈的颜色变为蓝色。 Stroustrup 写道:“您还可以添加一个操作(“回调”)作为参数。该操作在延迟后被调用”。因此,我尝试用执行此操作的代码替换指示的代码行。我期望输出完全相同,但事实并非如此。两秒时没有任何反应,但四秒时两个圆圈同时改变颜色。

#include "PPP/Simple_window.h"
#include "PPP/Graph.h"

int main(int /*argc*/, char * /*argv*/[])
{
    using namespace Graph_lib;
    Application app;

    // Window initially displays 2 black circles
    Simple_window w {Point{0,0}, 600, 400, "Callback problem"};
    Circle c1{{175, 200}, 100};
    Circle c2{{425, 200}, 100};
    c1.set_fill_color(Color::black);
    c2.set_fill_color(Color::black);
    w.attach(c1);
    w.attach(c2);

    // Code to be replaced:
    w.timer_wait(2000);    //2000 milliseconds
    c1.set_fill_color(Color::red);
    w.timer_wait(4000);
    c2.set_fill_color(Color::blue);
    //////////////////////////////

    // // I expected this code to have the same result:
    // w.timer_wait(2000, [&]{c1.set_fill_color(Color::red);});
    // w.timer_wait(4000, [&]{c2.set_fill_color(Color::blue);});
    // //////////////////////////////////////////////

    w.wait_for_button();
 }

为什么输出不一样?有什么办法可以成功使用回调方法吗?

这是timer_wait的代码:

void WindowPrivate::timer_wait(int milliseconds, std::function<void()> cb)
{
    if (!accept_waits)
        return;
    auto conn = std::make_shared<QMetaObject::Connection>();
    *conn = QObject::connect(&user_timer, &QTimer::timeout,
                     [conn, func = std::move(cb)] {
        QObject::disconnect(*conn);
        func();
    });
    user_timer.start(milliseconds);
}
c++ qt graphics callback
1个回答
0
投票

该问题是由于

WindowPrivate
始终使用相同的 QTimer 对象引起的;参见
GUI_Private.h

    QTimer user_timer{&nested_loop};

当顺序调用多个

timer_wait(timeout, callback)
函数时,它们实际上调用了计时器的
start(msec)
:

以 msec 毫秒的超时间隔启动或重新启动计时器。 如果计时器已经在运行,它将被停止并重新启动。

结果是第二次调用

timer_wait(timeout, callback)
时,计时器会立即 重置 并以新的超时时间 重新启动

这对于一般用途来说显然不能令人满意,但是由于 PPP 库的简单性质和目的,这也是可以理解的:它是一个用于教育目的的基础库,因此它的功能在某种程度上是有限的。我不知道以前版本的库(在切换到 Qt 之前)中的实现是否有不同的结果,但也许应该写信给 Stroustrups 并让他知道这个问题,至少应该记录下来,如果没有以某种方式修复。

在真实的程序中,您可能会使用两个不同的计时器,每个回调一个。

我认为尝试修复 PPP 实现没有多大意义,但您可以在这种情况下使用现有的 Qt 功能。
具体来说,您可以使用

QTimer::singleShot()
静态函数,它也可以与 lambda 一起使用:

#include <QTimer>

...

    QTimer::singleShot(2000, [&]{c1.set_fill_color(Color::red);});
    QTimer::singleShot(4000, [&]{c2.set_fill_color(Color::blue);});

请注意一个重要事实:QTimer 的上述用法期望函子中访问的任何内容在最终调用时仍然存在。

singleShot
静态函数的更合适用法是使用目标(QObject)的context,并在函数被销毁时自动断开连接。

或者,可以使用父对象(可以是“发送者”或“接收者”,具体取决于情况)构造实际的 QTimer 对象,然后将其

timeout
与相关函数连接。

© www.soinside.com 2019 - 2024. All rights reserved.