Qt信号能返回一个值吗?

59

Boost.Signals 允许使用插槽的返回值来形成信号的返回值的各种策略。例如,将它们相加、将它们组合成一个 vector,或者返回最后一个。

通常的智慧(在 Qt 文档中表达)是,在 Qt 信号中不可能做到这样的事情。

然而,当我在以下类定义上运行 moc 时:

class Object : public QObject {
    Q_OBJECT
public:
    explicit Object( QObject * parent=0 )
        : QObject( parent ) {}

public Q_SLOTS:
    void voidSlot();
    int intSlot();

Q_SIGNALS:
    void voidSignal();
    int intSignal();
};

不仅moc对于非空返回类型的信号没有抱怨,而且似乎还积极地实现了它,以便允许返回值通过。
// SIGNAL 1
int Object::intSignal()
{
    int _t0;
    void *_a[] = { const_cast<void*>(reinterpret_cast<const void*>(&_t0)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
    return _t0;
}

所以:根据文档,这个事情是不可能的。那么 mok 在这里做什么?

插槽可以有返回值,所以现在我们是否可以将具有返回值的插槽连接到具有返回值的信号上?毕竟,这可能是可能的吗?如果是这样,那么它有用吗?

我不是在寻求变通方法。

很明显,在 Qt::QueuedConnection 模式下是没有用的(尽管 QPrintPreviewWidget API 也是如此,但它仍然存在且有用)。但是对于 Qt::DirectConnectionQt::BlockingQueuedConnection(或者当它解析为 Qt::DirectConnection 时的 Qt::AutoConnection),情况如何?

5个回答

46
看起来这是可能的。我能够发出一个信号,并从连接到该信号的槽中接收值。但问题是它只返回了多个连接的槽中的最后一个返回值。
这是一个简单的类定义(main.cpp):
#include <QObject>
#include <QDebug>

class TestClass : public QObject
{
    Q_OBJECT
public:
    TestClass();

Q_SIGNALS:
    QString testSignal();

public Q_SLOTS:
    QString testSlot1() {
        return QLatin1String("testSlot1");
    }
    QString testSlot2() {
        return QLatin1String("testSlot2");
    }
};

TestClass::TestClass() {
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot1()));
    connect(this, SIGNAL(testSignal()), this, SLOT(testSlot2()));

    QString a = emit testSignal();
    qDebug() << a;
}

int main() {
    TestClass a;
}

#include "main.moc"

当主函数运行时,它会构造其中一个测试类。构造函数将两个槽与testSignal信号连接,并发射该信号。它捕获槽调用的返回值。

不幸的是,你只得到最后一个返回值。如果你评估上面的代码,你将得到:"testSlot2",即与信号连接的槽的最后一个返回值。

原因如下:Qt信号是一种语法糖化的接口,用于实现信号传递模式。槽是信号的接收者。在直接连接的信号-槽关系中,你可以将其类比为(伪代码):

foreach slot in connectedSlotsForSignal(signal):
    value = invoke slot with parameters from signal
return value

显然,模拟器在这个过程中做了更多的工作(基本类型检查等),但这有助于形成一个完整的画面。

2
谢谢你的尝试 :) 我已经编辑了你的代码,使其更简单、更短。然而,问题仍然存在:如果它(指代码)能够正常工作(使用“最后调用”语义),为什么文档中说它不能? - Marc Mutz - mmutz
一个好问题。我会认为文档中说它不起作用是因为它只是部分返回值。信号发射的真正返回值应该是基于某种聚合器(如boost)的所有结果的聚合体。但是,没有这个,它就是一个部分和未定义的结果(特别是在并发信号调用的上下文中)。也许还有一些编译器的差异? - jsherer
1
未记录的行为意味着不能保证其在例如Qt 5.0中仍然有效 :) - Torp
我认为我们可以说信号/槽是Qt对观察者模式的实现。因此,即使您成功解决了问题,我仍然想知道这是否是一个好的想法/设计,因为它允许观察者修改“被观察者”(即主题)的行为... 有人对此有什么想法吗? - gpalex
5
如果所有操作都在同一线程上,那么它可以工作,因为插槽将会是同步函数调用。但如果你的应用程序是多线程的,那么这个调用将会是异步的,并且没有返回值。 - Vincent
显示剩余2条评论

9
不行,它们不能。
Boost::signals与Qt中的信号机制有很大的区别。前者提供了一种高级回调机制,而后者则实现了信号机制。在多线程环境下,Qt的(跨线程)信号依赖于消息队列,因此它们会在某个(对发射器线程未知的)时间点异步地被调用。

1
当你使用连接时,如何依赖于在运行时发生的连接类型而编写代码?这些不是模板,Qt 主要是运行时库 :) - vines
所以你也认为 QPrintPreviewWidget::paintRequested() 是一个特别糟糕的API。我也是这么认为的。不过,它确实存在并且能够工作。 - Marc Mutz - mmutz
我认为这是唯一正确的答案,因为Qt信号/槽机制也被设计成可以异步工作。 - Emerald Weapon

1
你可以使用以下代码从 Qt 信号 中获取返回值:
我的示例展示了如何使用 Qt 信号 来读取 QLineEdit 的文本。 我只是在扩展 @jordan 提出的想法: 可以通过修改代码,使用作为“返回”的“out”参数。
#include <QtCore>
#include <QtGui>

class SignalsRet : public QObject
{
    Q_OBJECT

public:
    SignalsRet()
    {
        connect(this, SIGNAL(Get(QString*)), SLOT(GetCurrentThread(QString*)), Qt::DirectConnection);
        connect(this, SIGNAL(GetFromAnotherThread(QString*)), SLOT(ReadObject(QString*)), Qt::BlockingQueuedConnection);
        edit.setText("This is a test");
    }

public slots:
    QString call()
    {
        QString text;
        emit Get(&text);
        return text;
    }

signals:
    void Get(QString *value);
    void GetFromAnotherThread(QString *value);

private slots:
    void GetCurrentThread(QString *value)
    {
        QThread *thread = QThread::currentThread();
        QThread *mainthread = this->thread();
        if(thread == mainthread) //Signal called from the same thread that SignalsRet class was living
            ReadObject(value);
        else //Signal called from another thread
            emit GetFromAnotherThread(value);
    }

    void ReadObject(QString *value)
    {
        QString text = edit.text();
        *value = text;
    }

private:
    QLineEdit edit;

};

使用方法很简单,只需请求call();

1

Qt的qt_metacall函数返回一个整数状态码。因此,我认为这使得实际返回值不可能(除非您在预编译后使用元对象系统和moc文件进行欺骗)。

但是,您可以使用普通函数参数。应该可以修改您的代码,以便使用“out”参数作为您的“返回值”。

void ClassObj::method(return_type * return_)
{
    ...

    if(return_) *return_ = ...;
}

// somewhere else in the code...

return_type ret;
emit this->method(&ret);

我认为这需要一个非异步连接,除非你以某种方式使用具有更多信号的“future”对象。 - jsherer

-1

您可以尝试以下方法来解决这个问题:

  1. 所有连接的插槽都必须将其结果保存在某个地方(容器)中,该容器可从信号对象访问
  2. 最后一个连接的插槽应以某种方式(选择最大值或最后一个值)处理收集到的值并公开仅一个值
  3. 发射对象可以尝试访问此结果

这只是一个想法。


不幸的是,你无法确定最后连接的插槽,因为库不会告诉你,并且仔细控制连接顺序有点违背了使用信号的初衷。 - Jan Hudec

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接