2016-08-04 30 views
1

我在Qt 4.8中使用QSignalMapper。现在我在做网络请求象下面这样:使用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

一个简单的方法来做到这将是设置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:一个dataModel实例和答复之间的一对一映射。如果一个数据模型用于多个回复,它将不起作用。

如果你真的关心分配计数,那么使用属性系统有一点开销:设置对象的第一个属性至少执行两个分配 - 一个内部类和一个数据段QMap。所以这是2N分配。相比之下,将映射添加到QSignalMapper将执行分期日志(N)分配。否则,QSignalMapper是无用的。

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

向对象添加第一个连接也会分配一个(不同的)内部类。为了避免这种潜在的成本,您应该避免将连接添加到经常创建的对象。最好连接一次QNetworkManager::finished(QNetworkReply*)信号,而不是连接到每个QNetworkReply::finished()。唉,一旦你使用排队连接,这种保存就会消失:目前,他们花费额外的时间分配给每个传递给插槽的参数。这只是当前实施的一个缺点,而不是架构限制;它可以在随后的一个小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{"http://stackoverflow.com/questions/38775573/best-way-to-use-qsignalmapper"}, 
      &model); 
    return app.exec(); 
} 
#include "main.moc" 
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 
    } 
} 
+0

我该如何检查哪个QNetworkReply调用了插槽?我必须使用QObject :: setProperty吗? –

+0

您可以将指向活动回复的指针存储在'QMap'或'QHash'中,并通过一个键检索它们,就像'QSignalMapper'一样。或者你可以在答复上设置一个对象名称并检查它。 –