Qt中从QJsonArray构造QString

Construct QString from QJsonArray in Qt

尝试从 QJsonArray 的值构造 QString 时,出现以下错误:

error: passing 'const QString' as 'this' argument discards qualifiers [-fpermissive]

不知道这段代码哪里出错了:

QString <CLASS_NAME>::getData(QString callerValue) {
    QString BASE_URL = "<URL>";

    QString stringToReturn = "";
    QObject::connect(manager, &QNetworkAccessManager::finished, this, [=](QNetworkReply *reply) {
      QByteArray barr = reply->readAll();
      QJsonParseError jpe;
      QJsonDocument jdoc = QJsonDocument::fromJson(barr, &jpe);
      QJsonArray synonymsArray = jdoc.array();
      foreach (const QJsonValue &jv, synonymsArray) {
        QJsonObject jo = jv.toObject();
        QString s = jo.value("<VALUE>").toString();
        stringToReturn.append(s + ", "); /* ERROR: The error above is from this line... */
      }
    }
    );
    request.setUrl(QUrl(BASE_URL + callerValue));
    manager->get(request);
    return stringToReturn;
  }

当然是错的:stringToReturngetData中声明为局部变量,如果用[=]就是const,函数执行时就死掉了对象的调用 - 通过 finished signal ,如果通过引用捕获。 Lambda 表达式将在 it.That 放置用作此函数对象输出的变量的错误位置有一个悬空引用,该变量从 getData.[=22 开始在 return 上停止存在=]

此处lambda表达式创建的函数对象在getDatareturned之后调用。当由于发送请求而触发信号时,它会被称为 sometime,它是一个异步处理程序。在这种情况下,getData 不是 lambda 的调用者。 lambda 的调用者是 signal-slot 系统。如果 getData 没有显式调用 lambda,我们无法保证在本地存储的生命周期结束之前 returned 函数对象。

这里可能的解决方法是使用this的字段,如果你能保证this<CLASS_NAME>实例)在[=19=时仍然是"alive" ] 被解雇。本质上,这个 "getData" 无法 return 该值,除非您暂停它直到请求完成(这会破坏异步方法)。

事实上,finished 会在 QNetworkReply::finished 被解雇时被解雇,QNetworkAccessManager::get 只是 post 请求和 return 立即回复。接收数据时触发信号(发出 readyRead)

这是另一个经典的"I wish the world was synchronous"问题。你不能那样编码。 getData 方法不能按照您希望的方式编写。 getData 那样做会阻塞,这非常浪费并且会导致有趣的问题——最后一个问题是可怕的用户体验。

根据您的应用程序,可能有几种修复方法:

  1. redo getData 隐式 continuation-passing 风格使用协程和 co_yield - 这是未来,只能在最新的编译器上完成,除非你使用破解此类 boost 协同程序。

  2. 以显式 continuation-passing 样式重做 getData

  3. 以懒惰的方式重做getData,并在数据可用时发出通知,

  4. 有一个处理代码进度的显式状态机。

continuation-passing 样式需要的更改最少。还要注意其他修复 - 最值得注意的是你不应该使用 QNetworkAccessManager 的信号:你只对这个查询的结果感兴趣,而不是每个查询!捕获 QNetworkAccessManager::finished 信号只有在你真正拥有一个可以处理所有或至少最频繁的请求的中心点时才有用。在这种情况下,这是一个明智的优化:将第一个连接添加到 hiterto connection-free 对象需要几个 malloc 的开销。

void Class::getData(const QString &urlSuffix, std::function<void(const QString &)> cont) {
  auto const urlString = QStringLiteral("URL%1").arg(urlSuffix);
  QNetworkRequest request(QUrl(urlString));
  auto *reply = m_manager.get(request);
  QObject::connect(reply, &QNetworkReply::finished, this, [=]{
    QString result;
    auto data = reply->readAll();
    QJsonParseError jpe;
    auto jdoc = QJsonDocument::fromJson(data, &jpe);
    auto const synonyms = jdoc.array();
    for (auto &value : synonyms) {
      auto object = value.toObject();
      auto s = object.value("<VALUE">).toString();
      if (!result.isEmpty())
        result.append(QLatin1String(", "))
      result.append(s);
    }
    reply->deleteLater();
    cont(result);
  });
}

惰性风格要求使用 getData 的代码可以重新启动,并且也允许 continuation-passing,只要延续连接到信号:

class Class : public QObject {
  Q_OBJECT
  QString m_cachedData;
  QNetworkAccessManager m_manager{this};
  Q_SIGNAL void dataAvailable(const QString &);
  ...
};

QString Class::getData(const QString &urlSuffix) {
  if (!m_cachedData.isEmpty())
    return m_cachedData;

  auto const urlString = QStringLiteral("URL%1").arg(urlSuffix);
  QNetworkRequest request(QUrl(urlString));
  auto *reply = m_manager.get(request);
  QObject::connect(reply, &QNetworkReply::finished, this, [=]{
    m_cachedData.clear();
    auto data = reply->readAll();
    QJsonParseError jpe;
    auto jdoc = QJsonDocument::fromJson(data, &jpe);
    auto const synonyms = jdoc.array();
    for (auto &value : synonyms) {
      auto object = value.toObject();
      auto s = object.value("<VALUE">).toString();
      if (!m_cachedData.isEmpty())
        m_cachedData.append(QLatin1String(", "))
      m_cachedData.append(s);
    }
    reply->deleteLater();
    emit dataAvailable(m_cachedData);
  });
  return {};
}

状态机形式化状态级数:

class Class : public QObject {
  Q_OBJECT
  QStateMachine m_sm{this};
  QNetworkAccessManager m_manager{this};
  QPointer<QNetworkReply> m_reply;
  QState s_idle{&m_sm}, s_busy{&m_sm}, s_done{&m_sm};
  Q_SIGNAL void to_busy();
  void getData(const QString &);
  ...

};

Class::Class(QObject * parent) : QObject(parent) {
  m_sm.setInitialState(&s_idle);
  s_idle.addTransition(this, &Class::to_busy, &s_busy);
  s_done.addTransition(&s_idle);
  m_sm.start();
}

void Class::getData(const QString &urlSuffix) {
  static char const kInit[] = "initialized";
  auto const urlString = QStringLiteral("URL%1").arg(urlSuffix);
  QNetworkRequest request(QUrl(urlString));
  m_reply = m_manager.get(request);
  s_busy.addTransition(reply, &QNetworkReply::finished, &s_done);
  to_busy();
  if (!s_done.property(kInit).toBool()) {
    QObject::connect(&s_done, &QState::entered, this, [=]{
      QString result;
      auto data = m_reply->readAll();
      QJsonParseError jpe;
      auto jdoc = QJsonDocument::fromJson(data, &jpe);
      auto const synonyms = jdoc.array();
      for (auto &value : synonyms) {
        auto object = value.toObject();
        auto s = object.value("<VALUE">).toString();
        if (!result.isEmpty())
          result.append(QLatin1String(", "))
        result.append(s);
      }
      m_reply->deleteLater();
    });
    s_done.setProperty(kInit, true);
  }
}