I have a QT application where several different subcomponents need to fetch data from the internet. I want to have one Network
class that will do all the network interaction and then notify the caller in some way. I couldn't figure out how to do this with slots, so I coded a callback solution:
void Network::downloadFile(QString& urlstr, const std::function<void (QString, QByteArray, QNetworkReply::NetworkError)>& callback) {
auto processDownload = [&]() {
QNetworkReply * reply = qobject_cast<QNetworkReply *>(sender());
QByteArray result;
QString filename = reply->url().fileName();
if (reply->error() == QNetworkReply::NoError) { // Success
result = reply->readAll();
}
callback(filename, result, reply->error());
reply->deleteLater();
};
QNetworkReply *reply = nam_->get(QNetworkRequest(QUrl(urlstr)));
connect(reply, &QNetworkReply::downloadProgress, this, &Network::downloadProgress);
connect(reply, &QNetworkReply::finished, this, processDownload);
}
However I get a segfault as soon as callback
is called. I tried several different ways to define the callback, and even having it empty, it still crashes (segfault mostly, sometimes bad function call). It is defined as:
std::function<void(QJsonObject, QNetworkReply::NetworkError)> f = std::bind(&MainWindow::onResult, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
I resorted to doing something like static_cast<parent_class*>(this->parent())->onResult(..)
but this is not really good decoupling. How can I do this in a way that I can pass (or signal to) any generic std::function?
EDIT: Downloadfile is usually called like this:
void MainWindow::DoNetworkRQ(QString url) {
std::function<void(QJsonObject, QNetworkReply::NetworkError)> f = std::bind(&MainWindow::onResult, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
network_.downloadFile(url, f);
}
Which results in a segfault. How do I do this in a QT way?
@chehrlic was right that you are creating a callback function that goes out of scope before it gets called. Your question does not need to be solved in a "Qt way". But signals and slots will work, so I will show you how.
First, connect your signal to your slot:
void MainWindow::DoNetworkRQ(QString url) {
connect(&network_, &Network::someSignal, this, &MainWindow::onResult);
network_.downloadFile(url, f);
}
Then, emit the signal when your network tasks are completed:
void Network::downloadFile(QString& urlstr, const std::function<void (QString, QByteArray, QNetworkReply::NetworkError)>& callback) {
auto processDownload = [&]() {
QNetworkReply * reply = qobject_cast<QNetworkReply *>(sender());
QByteArray result;
QString filename = reply->url().fileName();
if (reply->error() == QNetworkReply::NoError) { // Success
result = reply->readAll();
}
emit someSignal(filename, result, reply->error());
reply->deleteLater();
};
QNetworkReply *reply = nam_->get(QNetworkRequest(QUrl(urlstr)));
connect(reply, &QNetworkReply::downloadProgress, this, &Network::downloadProgress);
connect(reply, &QNetworkReply::finished, this, processDownload);
}