处理因方法内部动态分配而导致的内存泄漏,而不会影响信号/插槽机制

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了处理因方法内部动态分配而导致的内存泄漏,而不会影响信号/插槽机制相关的知识,希望对你有一定的参考价值。

在本地功能中使用动态分配而不影响信号/插槽机制时,如何处理内存泄漏。我正在使用向服务器发送请求的客户端应用程序

这是我的场景:

MyClientApplication::sendRequestToServer()
{
    QString url_string = http://.....  // a url request
    QUrl url = url_string;

    QNetworkAccessManager *qnam = new QNetworkAccessManager();

    connect(qnam, SIGNAL(finished(QNetworkReply *)), this, SLOT(handleResponseFromServer(QNetworkReply *)));
    QNetworkRequest request(url);
    QNetworkReply *reply = qnam->post(request);
}

我在客户端应用程序的成员函数中创建了许多此类请求,每次使用动态创建的QNetworkAccessManager对象将请求发送到服务器。

由于动态分配qnam,我如何处理内存泄漏?如果我只在堆栈上创建qnam对象,那么当sendRequestToServer()从调用返回时,信号/槽机制将如何工作?

我想在不影响信号/插槽机制的情况下解决内存泄漏问题。

答案

有几种方法可以解决这个问题,如果不更全面地了解您的用例,很难说在您的特定场景中哪一个最佳。

但是,如果您使用指针或堆栈对象,那么您的混淆可能是对connect机制很重要的事实。您需要确保的唯一事情是您的对象生命周期足够好。

这意味着,对象必须在需要时可用,因此例如在离开desidered的范围之后,对象仍然必须存在。它可以通过堆以及堆栈对象来实现。

Qt parent-child hierarchy

您可以简单地使用MyClientApplication QObject子类(直接或间接)作为网络访问管理器的父级。通过这种方式,可以保证仅在父节点被破坏时,即当MyClientApplication对象停止存在时。请注意,在这种情况下,当离开相应方法的范围时,对象不会被破坏,这就是它可以工作的原因。稍后将根据上述规则删除它。

以下是必要的代码更改:

MyClientApplication::sendRequestToServer()
{

    QString url_string = http://.....  // a url request
    QUrl url = url_string;

    QNetworkAccessManager *qnam = new QNetworkAccessManager(this); // This is how you set up the parent on the child    

    connect(qnam, SIGNAL(finished(QNetworkReply *)), this, SLOT(handleResponseFromServer(QNetworkReply *)));

    QNetworkRequest request(url);
    QNetworkReply *reply = qnam->post(request); // Naturally, you will need the stack object syntax here, respectively
}

This will probably be the easiest option for you to use based on the knowledge of your current code, so stick to this.

Member variable on the stack

您可以简单地使用堆栈对象,在构造期间初始化。这将具有上述必要的生命周期,因为它只会在你的MyClientApplication级别被毁坏时被破坏。

以下是必要的代码更改:

class MyClientApplication...
{
    ...
    QNetworkAccessManager qnam; // This is the new member variable
    ...
};

MyClientApplication::sendRequestToServer()
{

    QString url_string = http://.....  // a url request
    QUrl url = url_string;

    // Here you need to get the address of the object since the connect function expects a pointer
    connect(&qnam, SIGNAL(finished(QNetworkReply *)), this, SLOT(handleResponseFromServer(QNetworkReply *)));

    QNetworkRequest request(url);
    QNetworkReply *reply = qnam.post(request); // Naturally, you will need the stack object syntax here, respectively
}

This would probably be the easiest option when you cannot use the Qt parent-child hierarchy.

Member variable on the heap

您可以简单地使用在构造期间创建并在构造期间销毁的堆对象。这将具有上述必要的生命周期,因为它只会在你的MyClientApplication级别被毁坏时被破坏。

以下是必要的代码更改:

class MyClientApplication...
{
    ...
    QNetworkAccessManager *qnam; // This is the new member variable
    ...
};

MyClientApplication::MyClientApplication(...)
: qnam(new QNetworkAccessManager())
...
{
    ...
}

MyClientApplication::~MyClientApplication()
{
    delete qnam;
    qnam = 0;
    ...
}

MyClientApplication::sendRequestToServer()
{

    QString url_string = http://.....  // a url request
    QUrl url = url_string;

    // Construction removed here since that is done in the constructor

    connect(qnam, SIGNAL(finished(QNetworkReply *)), this, SLOT(handleResponseFromServer(QNetworkReply *)));

    QNetworkRequest request(url);
    QNetworkReply *reply = qnam->post(request);
}

This is a bad practice in C++ in general to deal with the raw pointer explicitly. In certain cases, it makes sense, but if you do not have extra memory and performance concerns, this is not the way to go for. It is also error-prone if you forget to delete the raw pointer yourself.

Member variable on the heap with smart pointers

您可以简单地使用在构造期间创建并在构造期间销毁的堆对象。这将具有上述必要的生命周期,因为它只会在你的MyClientApplication级别被毁坏时被破坏。您将使用智能指针来处理自动销毁。

以下是必要的代码更改:

class MyClientApplication...
{
    ...
    QPointer<QNetworkAccessManager> qnam; // This is the new member variable
    ...
};

MyClientApplication::MyClientApplication(...)
: qnam(new QNetworkAccessManager())
...
{
    ...
}

MyClientApplication::sendRequestToServer()
{

    QString url_string = http://.....  // a url request
    QUrl url = url_string;

    // Construction removed here since that is done in the constructor

    connect(qnam, SIGNAL(finished(QNetworkReply *)), this, SLOT(handleResponseFromServer(QNetworkReply *)));

    QNetworkRequest request(url);
    QNetworkReply *reply = qnam->post(request);
}

This is a good practice in C++ in general to deal with pointers due to the simplified maintenance. However, if you have a simple code as you pasted, this is probably an overengineered solution.

希望这个冗长的解释有助于避免混淆。

另一答案

使用QNetworkAccessManager指针(QNetworkAccessManager *qnam;)作为类中的成员变量,并且只在类构造函数中分配一次(调用qnam = new QNetworkAccessManager(this);它可以占用父项,因此在构造this时可以将QNetworkAccessManager作为父项传递,这样就不会需要在析构函数中删除调用)。

LE:另请参阅文档中的注释(它与QNetworkAccessManager无关,但如果不调用deleteLater(),则回复将泄漏):

注意:请求完成后,用户有责任在适当的时间删除QNetworkReply对象。不要在连接到finished()的插槽内直接删除它。您可以使用deleteLater()函数。

以上是关于处理因方法内部动态分配而导致的内存泄漏,而不会影响信号/插槽机制的主要内容,如果未能解决你的问题,请参考以下文章

Jvm内存泄漏

Android 常见内存泄漏的解决方式

内存泄露检测工具Valgrind

JVM系列之六:内存溢出内存泄漏 和 栈溢出

常见的内存泄漏原因及解决方法

(转载) Android常见的几种内存泄漏小结