存储到QList中的QNetworkReply包装器使程序意外完成?

时间:2014-08-19 15:45:18

标签: c++ qt

为了学习Qt,我测试了一些Qt(5.3.1)课程;现在我正在测试网络包上提供的一些类,例如QNetworkAccessManagerQNetworkReply等......

我使用Qt creator(3.1.2)编译一个使用以下QNetworkReply包装器的简单程序:

struct Request {
    Request(QNetworkReply *const &a_reply) : m_reply(a_reply) {
        qDebug() << "Build  " << hex << int(this) << hex << int(m_reply);
    }
    ~Request() {
        if (m_reply) m_reply->deleteLater();
        qDebug() << "Destroy" << hex << int(this) << hex << int(m_reply);
    }
    QNetworkReply *m_reply;
};

所有包装的网络回复都存储在名为QList的{​​{1}}:

R

通过以下功能创建:

QList<Request> R;

void get(const QString &a_url) { QNetworkRequest request(a_url); R.push_back(NetworkAccessManager.get(request)); } NetworkAccessManager的一个实例,位于QNetworkAccessManager函数之外(列表main也是如此):

R

上面粘贴的程序产生以下输出:

QList<Request> R;

QNetworkAccessManager NetworkAccessManager;

void done(QNetworkReply *reply) {
    if (reply->error() == QNetworkReply::NoError) {
        qDebug() << "Reply!" << reply->readAll();
    } else {
        qDebug() << "Error!" << reply->errorString();
    }

    for (QList<Request>::iterator request = R.begin(), lastRequest = R.end(); request != lastRequest; ++request) {
        if (request->m_reply == reply) {
            R.erase(request);
        }
    }
}

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    // Default "hello world" qml file:
    engine.load(QUrl(QStringLiteral("qrc:///main.qml")));

    QObject::connect(&NetworkAccessManager, &QNetworkAccessManager::finished, done);

    get("http://us.battle.net/api/wow/achievement/2144");
    get("http://us.battle.net/api/wow/achievement/2145");
    get("http://us.battle.net/api/wow/achievement/2146");

    return app.exec();
}

因此,似乎Build 28fdc8 1be843b8 Destroy 28fdc8 1be843b8 Build 28fdc8 1be84558 Destroy 28fdc8 1be84558 Build 28fdc8 1be84638 Destroy 28fdc8 1be84638 的同一个实例正在管理不同的Request指针(QNetworkReply指针打印的内容与this更改时相同)且不{ {1}}也显示QNetworkReply;单击应用程序窗口的关闭按钮后,程序将在8或10秒后崩溃打印:

  

该程序意外完成。   C:\ Code \ build-test-Desktop_Qt_5_3_MinGW_32bit-Debug \ debug \ test.exe崩溃

删除"Reply!"上的"Error!"指令会产生以下输出:

deleteLater()

该程序不再崩溃,但似乎根据3个exttra ~Request消息,为每个Build 28fdc8 1be78078 Destroy 28fdc8 1be78078 Build 28fdc8 1be78218 Destroy 28fdc8 1be78218 Build 28fdc8 1be782e8 Destroy 28fdc8 1be782e8 Reply! "the expected json for http://us.battle.net/api/wow/achievement/2145" Destroy 1be782c8 1be78218 Reply! "the expected json for http://us.battle.net/api/wow/achievement/2146" Destroy 1be78398 1be782e8 Reply! "the expected json for http://us.battle.net/api/wow/achievement/2144" Destroy 1be781f8 1be78078 调用在某处创建了Request的副本。更糟糕的是,我不再使用get which seems to be mandatory了,所以我已将Destroy放在deleteLater()的末尾功能:

deleteLater()

取得了良好的效果(没有应用程序崩溃),但我很担心,因为我没有得到正在发生的事情,所以我想知道你们中是否有人会这么善意回答以下问题:

  • 如果done放在void done(QNetworkReply *reply) { // do stuf... reply->deleteLater(); } 中,为什么不调用函数done
  • 为什么在调用deleteLater()~Request输出)后完成对done"Reply!"输出)的调用?
  • 为什么~Request(构建时打印)的指针"Destroy"在每个调用中具有相同的值? (可能是由于一些优化?)。
  • this的副本(及其原因)在哪里?
  • 调用每个Request的{​​{1}}的最佳位置在哪里。

感谢。

1 个答案:

答案 0 :(得分:0)

Andrew Medico comment没有回答我的任何问题,但指出了正确的方向。

事实上,根据Andrew(强调我的)链接的文档:

  

存储在各种容器中的值可以是任何可分配的数据类型。要获得限定,类型 必须提供默认构造函数,复制构造函数和赋值运算符

所以它完成了我的Request结构(隐含地没有表达,但对我来说没问题),但为了完成QList包含类型的所有必须我决定提供缺少的部分:

struct Request {
    Request() :
    m_reply(nullptr) {
        qDebug() << "Default" << hex << int(this) << hex << int(m_reply);
    }

    Request(QNetworkReply *const &a_reply) :
    m_reply(a_reply) {
        qDebug() << "Build  " << hex << int(this) << hex << int(m_reply);
    }

    Request(const Request &a_request) :
    m_reply(a_request.m_reply) {
        qDebug() << "Copy   " << hex << int(this) << hex << int(m_reply);
    }

    Request &operator =(const Request &a_request) {
        qDebug() << "Assign " << hex << int(this) << hex << int(m_reply = a_request.m_reply); return *this;
    }

    ~Request() {
        qDebug() << "Destroy" << hex << int(this) << hex << int(m_reply);
    }
    QNetworkReply *m_reply;
};

之后,输出发生了变化,一切都变得清晰:

Build   28fdc8 1ba5bda0
Copy    1ba5bf20 1ba5bda0
Destroy 28fdc8 1ba5bda0
Build   28fdc8 1ba5bf40
Copy    1ba5bff0 1ba5bf40
Destroy 28fdc8 1ba5bf40
Build   28fdc8 1ba5c020
Copy    1ba5c0c0 1ba5c020
Destroy 28fdc8 1ba5c020
Reply! "the expected json for http://us.battle.net/api/wow/achievement/2145"
Destroy 1ba5bff0 1ba5bf40
Reply! "the expected json for http://us.battle.net/api/wow/achievement/2144"
Destroy 1ba5bf20 1ba5bda0
Reply! "the expected json for http://us.battle.net/api/wow/achievement/2146"
Destroy 1ba5c0c0 1ba5c020

所以,让我们回答一下问题:

  

如果done放在deleteLater()中,为什么不调用函数~Request

每次将新Request推入R容器(R.push_back(NetworkAccessManager.get(request)))时,都会发生以下情况:

  1. 创建了一个R值Request对象("Build"输出)。
  2. R值通过复制构造函数(R输出)插入"Copy"
  3. 我们复制的R值被销毁("Destroy"输出)。
  4. 在第3步执行的销毁会导致所有问题:因为由R值管理的QNetworkReply指针和复制的包含Request是相同的,所以调用deleteLater()放在~Request中导致包含的Request开始管理已删除(或将来删除)的资源!这就是为什么done没有被调用的原因,事实是没有QNetworkReply等待完成(它被删除了!)。

    令人遗憾的是QList没有提供emplace_back

      

    为什么在调用done"Reply!"输出)之后调用~Request"Destroy"输出)?

    "Destroy"输出对应于为了将Request插入R容器而创建的R值的销毁,插入后发生"Reply!"输出,所以它是在R值被破坏之后,然后,包含的Request被销毁("Destroy"输出后"Reply!"输出。

      

    为什么this(构建时打印)的指针Request在每个调用中具有相同的值? (可能是由于一些优化?)。

    重复地址对应于用于填充R容器的R值的地址,我的猜测是在每次调用时使用相同的地址构造此R值作为某种优化

      

    Request的副本在哪里(和为什么)?

    见第一个答案。

      

    调用每个deleteLater()的{​​{1}}的最佳位置在哪里。

    在函数QNetworkReply结束时应该没问题。