使用 QSignalMapper 的最佳方式

Best way to use 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?

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

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

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

下面是一个在 Qt 4 和 5 中都有效的完整 C++11 示例。

// https://github.com/KubaO/Whosebugn/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{""},
            &model);
   QObject::connect(&worker, SIGNAL(got(QNetworkReply*)), &app, SLOT(quit()));
   return app.exec();
}
#include "main.moc"

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

如果您真的很关心分配计数,那么使用 属性 系统会有更多的开销:在对象上设置第一个 属性 至少执行两次分配 - 一个内部 class,以及 QMap 的数据段。这就是 2N 分配。相比之下,将映射添加到 QSignalMapper 会执行摊销的 log(N) 分配。否则QSignalMapper没用

在 Qt 5 中使用它完全是一种反模式,因为您可以连接到 std::bind 或 lambda。无论如何,如果 QSignalMapper 映射到 QVariant.

会更好

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

#include <QtNetwork>
#include <QStringListModel> // needed for Qt 4
using DataModel = QStringListModel;

class Worker : public QObject {
   Q_OBJECT
   QNetworkAccessManager m_manager;
   QSignalMapper m_mapper;
   // QObject::connect is not clever enough to know that QNetworkReply* is-a QObject*
   Q_SLOT void map(QNetworkReply* reply) { m_mapper.map(reply); }
   Q_SLOT void onFeedRetrieved(QObject * dataModelObject) {
      auto dataModel = qobject_cast<DataModel*>(dataModelObject);
      auto reply = qobject_cast<QNetworkReply*>(m_mapper.mapping(dataModelObject));
      qDebug() << dataModel << reply;
      emit got(reply);
   }
public:
   Worker(QObject * parent = nullptr) : QObject{parent} {
      connect(&m_manager, SIGNAL(finished(QNetworkReply*)), SLOT(map(QNetworkReply*)));
      connect(&m_mapper, SIGNAL(mapped(QObject*)), SLOT(onFeedRetrieved(QObject*)));
   }
   void newRequest(const QUrl & url, DataModel * dataModel) {
      QNetworkRequest request{url};
      auto reply = m_manager.get(request);
      // Ensure a unique mapping
      Q_ASSERT(m_mapper.mapping(dataModel) == nullptr);
      m_mapper.setMapping(reply, dataModel);
   }
   Q_SIGNAL void got(QNetworkReply*);
};

int main(int argc, char ** argv) {
   QCoreApplication app{argc, argv};
   DataModel model;
   Worker worker;
   QObject::connect(&worker, SIGNAL(got(QNetworkReply*)), &app, SLOT(quit()));
   worker.newRequest(
            QUrl{""},
            &model);
   return app.exec();
}
#include "main.moc"