使用QSignalMapper的最佳方式

时间:2016-08-04 19:13:50

标签: c++ qt qsignalmapper

我在Qt 4.8中使用QSignalMapper。现在我正在制作如下网络请求:

// start the request
QNetworkRequest request(url);
QNetworkReply* reply = networkManager->get(request);

// connect signals using QSignalMapper
QSignalMapper* signalMapper = new QSignalMapper(reply);
connect(signalMapper, SIGNAL(mapped(QObject*)),this, SLOT(onFeedRetrieved(QObject*)), Qt::UniqueConnection); 
connect(reply,SIGNAL(finished()),signalMapper, SLOT(map())); // connect to the signal mapper

signalMapper->setMapping(reply, dataModel); // set the mapping (the mapping should be automatically removed when reply is destroyed)

我为每个请求执行此操作,每次都将QSignalMapper与我的插槽连接。是否有一个更优雅的解决方案可以做同样的事情,也许使用一个QSignalMapper?

2 个答案:

答案 0 :(得分:2)

一种简单的方法是将dataModel设置为回复中的属性。

保持网络管理器和其他对象的值,而不是指针 - 指针是一个额外的间接,在大多数情况下完全没有必要。

下面是一个完整的C ++ 11示例,适用于Qt 4和5。

// https://github.com/KubaO/stackoverflown/tree/master/questions/netreply-property-38775573
#include <QtNetwork>
#include <QStringListModel> // needed for Qt 4
using DataModel = QStringListModel;
const char kDataModel[] = "dataModel";

class Worker : public QObject {
   Q_OBJECT
   QNetworkAccessManager m_manager;
   Q_SLOT void onFeedRetrieved(QNetworkReply* reply) {
      auto dataModelObject = qvariant_cast<QObject*>(reply->property(kDataModel));
      auto dataModel = qobject_cast<DataModel*>(dataModelObject);
      qDebug() << dataModel;
      emit got(reply);
   }
public:
   Worker(QObject * parent = nullptr) : QObject{parent} {
      connect(&m_manager, SIGNAL(finished(QNetworkReply*)),
              SLOT(onFeedRetrieved(QNetworkReply*)));
   }
   void newRequest(const QUrl & url, DataModel * dataModel) {
      QNetworkRequest request{url};
      auto reply = m_manager.get(request);
      reply->setProperty(kDataModel, QVariant::fromValue((QObject*)dataModel));
   }
   Q_SIGNAL void got(QNetworkReply*);
};

int main(int argc, char ** argv) {
   QCoreApplication app{argc, argv};
   DataModel model;
   Worker worker;
   worker.newRequest(
            QUrl{"http://stackoverflow.com/questions/38775573/best-way-to-use-qsignalmapper"},
            &model);
   QObject::connect(&worker, SIGNAL(got(QNetworkReply*)), &app, SLOT(quit()));
   return app.exec();
}
#include "main.moc"

如果QSignalMapper实例与回复之间存在1:1映射,则只能使用dataModel。如果一个数据模型用于多个回复,则不起作用。

如果您真的关心分配计数,那么使用属性系统会有更多的开销:设置对象的第一个属性至少执行两个分配 - 内部类和{{1}的数据段}。这就是2N的分配。相比之下,向QMap添加映射会执行分摊的日志(N)分配。否则,QSignalMapper无用。

在Qt 5中使用它将完全是反模式,因为你可以连接到QSignalMapper或lambda。无论如何,如果std::bind映射到QSignalMapper,情况就会好得多。

向对象添加第一个连接也会分配(不同的)内部类。为避免此潜在成本,应避免向经常创建的对象添加连接。最好将一次连接到QVariant信号,而不是连接到每个QNetworkManager::finished(QNetworkReply*)。唉,一旦你使用排队连接,这种节省就消失了:他们目前要为传递给插槽的每个参数花费额外的分配。这只是当前实现的缺点,而不是架构限制;它可以在随后的小Qt版本中删除(如果我自己或其他人到达它)。

QNetworkReply::finished()

答案 1 :(得分:1)

This answer提供了一种优雅而通用的解决方案:将sender()qobject_cast结合使用,而不是QSignalMapper

您的代码可能如下所示:

 connect(reply,SIGNAL(finished()), this, SLOT(onFeedRetrieved()));

然后:

void Foo::onFeedRetrieved()
{
    QNetworkReply *reply = qobject_cast<QNetworkReply>(sender());
    if (reply) { //check if the cast worked
         //check which QNetworkReply invoked the slot and do stuff here
    }
}