【问题标题】:Can Qt signals return a value?Qt 信号可以返回一个值吗?
【发布时间】:2011-08-16 01:47:20
【问题描述】:

Boost.Signals 允许various strategies 使用槽的返回值来形成信号的返回值。例如。添加它们,从中形成vector,或返回最后一个。

普遍的看法(在 Qt 文档 [EDIT: 以及对这个问题的一些答案 ] 中表达)是 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 不仅不抱怨返回类型为非 void 的信号,它似乎还积极地以 允许返回值传递的方式实现它:

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

所以:根据文档,这件事是不可能的。那么 moc 在这里做什么呢?

Slots can have return values,那么我们现在可以将一个有返回值的槽连接到一个有返回值的信号上吗?毕竟,这可能吗?如果有,有用吗?

编辑:我不是要求解决方法,所以请不要提供任何解决方法。

编辑:它显然在Qt::QueuedConnection 模式下没有用(虽然QPrintPreviewWidget API 也没有,但它仍然存在并且很有用)。但是Qt::DirectConnectionQt::BlockingQueuedConnection(或Qt::AutoConnection,当它解析为Qt::DirectConnection 时)呢?

【问题讨论】:

    标签: c++ qt return-value signals-slots boost-signals


    【解决方案1】:

    不,他们不能。

    Boost::signals 与 Qt 中的完全不同。前者提供了一种高级回调机制,而后者实现了信令惯用语。在多线程的上下文中,Qt 的(跨线程)信号依赖于消息队列,因此它们在某个(发射器的线程未知的)时间点被异步调用。

    【讨论】:

    • 在使用它们编写代码时,如何依赖将在运行时发生的连接类型?这些不是模板,Qt 主要是运行时库:)
    • 所以你也认为QPrintPreviewWidget::paintRequested() 是非常糟糕的API。我也是。尽管如此,它仍然存在,并且有效。
    • 我认为这是对这个问题的唯一正确答案,因为 Qt 信号/插槽机制也被设计为异步工作。
    【解决方案2】:

    您可以尝试使用以下方法解决此问题:

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

    只是一个想法。

    【讨论】:

    • 不幸的是,您无法分辨出最后一个连接的插槽,因为库不会告诉您,并且仔细控制连接类型的顺序首先会破坏使用信号的目的。
    【解决方案3】:

    您可以使用以下代码从Qt signal 获得返回值:

    我的示例展示了如何使用Qt signal 来读取QLineEdit 的文本。 我只是在扩展@jordan 的建议:

    应该可以通过使用“out”参数作为“return”的方式修改您的代码。

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

    【讨论】:

      【解决方案4】:

      好的。所以,我做了更多的调查。看来这是可能的。我能够发出信号,并从信号连接的插槽接收值。但是,问题是它只返回了多个连接槽的最后一个返回值:

      这是一个简单的类定义 (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"
      

      当 main 运行时,它会构造一个测试类。构造函数将两个插槽连接到 testSignal 信号,然后发出信号。它从调用的槽中捕获返回值。

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

      原因如下。 Qt Signals 是信号模式的语法糖接口。插槽是信号的接收者。在直接连接的信号槽关系中,你可以认为它类似于(伪代码):

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

      显然,moc 在此过程中提供了更多帮助(基本类型检查等),但这有助于绘制图片。

      【讨论】:

      • 感谢您的实际尝试:) 我已将您的代码编辑得更简单、更短。但是,问题仍然存在:如果它有效(使用“最后调用”语义),为什么文档说它无效?
      • 一个好问题。我会假设文档说它不起作用,因为它只是部分返回值。信号发射的真实返回值应该是基于某种聚合器(如 boost)的所有结果的聚合。但是,没有它,这是一个部分和未定义的结果(尤其是在并发信号调用的上下文中)。也许还有一些编译器差异?
      • 未记录的行为意味着您不能保证它仍然可以在 Qt 5.0 中工作:)
      • @jordan:我接受了你的回答,因为它是最接近的,而且一年来没有其他人比这更好了。显然,我自己尝试过,问题是返回值确实是返回的,但在连接时没有检查兼容性。因此,例如将double-returning 插槽连接到float-returning 信号很有可能在运行时崩溃。
      • 如果一切都在同一个线程上,它将工作,因为您将在插槽上进行同步函数调用。如果您有一个多线程应用程序,调用将是异步的,不会有返回值。
      【解决方案5】:

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

      但是,您确实可以使用正常的函数参数。应该可以修改代码以使用充当“返回”的“输出”参数。

      void ClassObj::method(return_type * return_)
      {
          ...
      
          if(return_) *return_ = ...;
      }
      
      // somewhere else in the code...
      
      return_type ret;
      emit this->method(&ret);
      

      【讨论】:

      • 我相信这需要一个非异步连接,除非你以某种方式设法在一个具有更多信号的“未来”对象中争吵。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-14
      • 2017-05-21
      • 1970-01-01
      • 2011-02-04
      • 2013-03-10
      相关资源
      最近更新 更多