简体   繁体   English

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

[英]QNetworkReply wrapper stored into QList makes program finish unexpectedly?

In order to learn Qt I'm testing some Qt (5.3.1) classes; 为了学习Qt,我正在测试一些Qt(5.3.1)类。 for now I'm testing some of the classes provided on the network package such as QNetworkAccessManager , QNetworkReply , etc... 目前,我正在测试网络程序包上提供的一些类,例如QNetworkAccessManagerQNetworkReply等。

I'm using Qt creator (3.1.2) to compile a simple program which uses the following QNetworkReply wrapper: 我正在使用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;
};

All the wrapped network replies are stored on a QList named R : 所有包装的网络答复都存储在名为RQList

QList<Request> R;

And are created through the following function: 并通过以下功能创建:

void get(const QString &a_url) {
    QNetworkRequest request(a_url);
    R.push_back(NetworkAccessManager.get(request));
}

The NetworkAccessManager is an instance of QNetworkAccessManager which lies outside the main function (the same goes for the list R ): NetworkAccessManagerQNetworkAccessManager一个实例,位于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();
}

The program pasted above, produces the following output: 上面粘贴的程序将产生以下输出:

Build   28fdc8 1be843b8
Destroy 28fdc8 1be843b8
Build   28fdc8 1be84558
Destroy 28fdc8 1be84558
Build   28fdc8 1be84638
Destroy 28fdc8 1be84638

So, it seems that the same instance of Request is managing different QNetworkReply pointers (the this pointer printed is the same while the QNetworkReply changes) and no "Reply!" 因此,似乎同一Request实例正在管理不同的QNetworkReply指针(当QNetworkReply更改时, this指针的输出是相同的),并且没有"Reply!" nor "Error!" 也不是"Error!" is shown; 显示; after click on the close button of the application window, the program crashes about 8 or 10 seconds later printing: 单击应用程序窗口的关闭按钮后,该程序在打印大约8或10秒后崩溃:

The program has unexpectedly finished. 该程序意外完成。 C:\\Code\\build-test-Desktop_Qt_5_3_MinGW_32bit-Debug\\debug\\test.exe crashed C:\\ Code \\ build-test-Desktop_Qt_5_3_MinGW_32bit-Debug \\ debug \\ test.exe崩溃

Removing the deleteLater() instruction on ~Request produces the following output: 卸下deleteLater()上指令~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

And the program does not crash anymore, but it seems that a copy of Request is made somewhere for each get call according to the 3 exttra Destroy messages. 该程序不会再崩溃,但是根据3个额外的Destroy消息,似乎在每个get调用的某处都复制了Request的副本。 And to make things worse I'm not doing the deleteLater() anymore ( which seems to be mandatory ), so I've placed the deleteLater() at the end of the done function: 更糟糕的是,我不再使用deleteLater()了( 这似乎是必需的 ),因此我将deleteLater()放在了done函数的末尾:

void done(QNetworkReply *reply) {
    // do stuf...

    reply->deleteLater();
}

With good results (no application crash), but I'm worried because I don't get what's happening, so I wonder if any of you would be so kind to answer the following questions: 结果不错(没有应用程序崩溃),但是我担心,因为我不了解发生了什么,所以我想知道你们中的任何一个人是否愿意回答以下问题:

  • Why the function done isn't called if the deleteLater() is placed in ~Request ? 为什么功能done ,如果不叫deleteLater()被放置在~Request
  • Why the call to done ( "Reply!" output) is done AFTER the call to ~Request ( "Destroy" output)? 为什么在调用~Request"Destroy"输出)之后调用done"Reply!" "Destroy"输出)?
  • Why the pointer this of Request (printed on construction) have the same value on each call? 为什么Request的指针this (打印在构造上)在每个调用中都具有相同的值? (maybe due to some optimization?). (也许是由于某些优化?)。
  • Where (and why) a copy of a Request is made? 在何处(为什么)复制Request
  • Where is the best location to call the deleteLater() of each QNetworkReply . 调用每个QNetworkReplydeleteLater()的最佳位置在哪里?

Thanks. 谢谢。

The Andrew Medico comment doesn't answer any of my questions but pointed me on the right direction. Andrew Medico的 评论没有回答我的任何问题,但为我指明了正确的方向。

In fact, according to the documentation linked by Andrew (emphasis mine): 实际上,根据安德鲁(我的重点)链接的文档:

The values stored in the various containers can be of any assignable data type. 存储在各个容器中的值可以是任何可分配的数据类型。 To qualify, a type must provide a default constructor, a copy constructor, and an assignment operator . 要获得资格,类型必须提供默认构造函数,副本构造函数和赋值运算符

And so it did my Request struct (implicitly not explicity, that's Ok for me though), but in order to accomplish all the must of the QList contained type I've decided to provide the missing parts: 因此,它执行了我的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;
};

After that, the output changed and all becomes crystal clear: 在那之后,输出改变了,一切变得清晰起来:

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

So, let's answer the questions: 因此,让我们回答以下问题:

Why the function done isn't called if the deleteLater() is placed in ~Request ? 为什么功能done ,如果不叫deleteLater()被放置在~Request

Every time a new Request is pushed into the R container ( R.push_back(NetworkAccessManager.get(request)) ) happens the following: 每次将新Request推送到R容器( R.push_back(NetworkAccessManager.get(request)) )时,都会发生以下情况:

  1. A R-value Request object is created ( "Build" output). 创建一个R值Request对象( "Build"输出)。
  2. The R-value is inserted in R via copy constructor ( "Copy" output). R值通过复制构造函数插入到R"Copy"输出)。
  3. The R-value which we copied from is destroyed ( "Destroy" output). 从中复制的R值被销毁( "Destroy"输出)。

The destruction performed on the 3rd step causes all the problems: because the QNetworkReply pointer managed by the R-value and the copied contained Request are the same so, calling the deleteLater() placed in ~Request causes that the contained Request starts to manage a deleted (or future-deleted) resource! 在第三步骤中进行的破坏会导致所有的问题:因为QNetworkReply由R值和复制包含的管理指针Request是相同的,所以,在调用deleteLater()置于~Request使所包含的Request开始管理一个删除(或将来删除)的资源! That's why done isn't called, the fact is that there's no QNetworkReply to wait to complete (it was deleted!). 这就是为什么不调用done的原因,事实是没有等待完成的QNetworkReply (已删除!)。

It's kind of a shame that the QList doesn't provides an emplace_back . QList没有提供emplace_back

Why the call to done ( "Reply!" output) is done AFTER the call to ~Request ( "Destroy" output)? 为什么在调用~Request"Destroy"输出)之后调用done"Reply!" "Destroy"输出)?

The "Destroy" output corresponds to the destruction of the R-value created in order to insert a Request into the R container, the "Reply!" "Destroy"输出对应于为将Request插入R容器而创建的R值的销毁,即"Reply!" output happens after the insertion, so it's after the destruction of the R-value and then, the contained Request is destroyed ( "Destroy" output AFTER the "Reply!" output). 输出发生在插入之后,因此是在销毁R值之后,然后销毁所包含的Request"Destroy"输出之后的"Reply!" "Destroy" "Reply!"输出)。

Why the pointer this of Request (printed on construction) have the same value on each call? 为什么Request的指针this (打印在构造上)在每个调用中都具有相同的值? (maybe due to some optimization?). (也许是由于某些优化?)。

The repeated address corresponds to the address of the R-value used to populate the R container, my guess is that the same address is used to construct this R-value at each call as a some kind of optimization. 重复的地址对应于用于填充R容器的R值的地址,我猜是作为某种优化,每次调用时都使用相同的地址来构造此R值。

Where (and why) a copy of a Request is made? 在何处(为什么)复制Request

See the first answer. 请参阅第一个答案。

Where is the best location to call the deleteLater() of each QNetworkReply . 调用每个QNetworkReplydeleteLater()的最佳位置在哪里?

At the end of the function done should be Ok. 在功能done应该可以。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM