gpt4 book ai didi

c++ - Qt 信号 lambda 导致 shared_ptr 泄漏?

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:14:47 26 4
gpt4 key购买 nike

我有以下代码:

#include <QApplication>
#include <memory>
#include <QUndoCommand>
#include <QWidget>

class Document
{
public:
Document()
{
qDebug("Document");
}

~Document()
{
qDebug("~Document");
}

QUndoStack mUndostack;
};

class DocumentRepository
{
public:
DocumentRepository()
{
qDebug("DocumentRepository");
}

~DocumentRepository()
{
qDebug("~DocumentRepository");
}


void AddDoc(std::shared_ptr<Document> doc)
{
mDocs.emplace_back(doc);
}

std::vector<std::shared_ptr<Document>> mDocs;
};

class Gui : public QWidget
{
public:
Gui(DocumentRepository& repo)
: mRepo(repo)
{
qDebug("+Gui");

for(int i=0; i<3; i++)
{
CreateDoc();
}

mRepo.mDocs.clear();

qDebug("-Gui");
}

~Gui()
{
qDebug("~Gui");
}

void CreateDoc()
{
auto docPtr = std::make_shared<Document>();
connect(&docPtr->mUndostack, &QUndoStack::cleanChanged, this, [=](bool clean)
{
// Using docPtr here causes a memory leak on the shared_ptr's, the destruct after ~Gui
// but without using docPtr here they destruct before ~Gui as exepected.
QString msg = "cleanChanged doc undo count " + QString::number(docPtr->mUndostack.count());
qDebug(msg.toLatin1());
}, Qt::QueuedConnection);
mRepo.AddDoc(docPtr);
}

DocumentRepository& mRepo;
};

int main(int argc, char *argv[])
{
QApplication a(argc, argv);

DocumentRepository repo;

Gui g(repo);
g.show();

return 0;
}

哪些输出:

DocumentRepository
+Gui
Document
Document
Document
-Gui
~Gui
~Document
~Document
~Document
~DocumentRepository

但是在这里您可以看到 Document 实例在 Gui 实例之后被破坏时被泄漏了。如果您查看评论,您会发现我使用 shared_ptr 将这个问题缩小到信号的 lambda。我想知道为什么会导致泄漏,如何解决?

作为引用,在 lambda 中不使用 shared_ptr 时的“正确”/非泄漏输出是:

DocumentRepository
+Gui
Document
Document
Document
~Document
~Document
~Document
-Gui
~Gui
~DocumentRepository

最佳答案

这是一个有趣的问题,让我们揭开它的神秘面纱:

来自official connect documentation :

The connection will automatically disconnect if the sender is destroyed. However, you should take care that any objects used within the functor are still alive when the signal is emitted.

在您的示例中,您正在复制在 lambda 内部使用时创建的共享指针,否则不会为共享指针创建拷贝。该拷贝自然会增加共享指针内对象的引用计数器。这是来自 shared_ptr 的相应文档:

The ownership of an object can only be shared with another shared_ptr by copy constructing or copy assigning its value to another shared_ptr

现在,让我们区分这两种情况:

  • 当您不复制共享指针时,只有一个对该对象的引用,因此当您的文档存储库完成清除后,不再有对它的引用,因此该对象可以被破坏,假设您在 lambda 函数内没有做任何有用的事情,因此可以被优化掉。

  • 当您复制共享指针时,在 lambad 外部有一个对对象的引用,并且由于共享指针复制,内部也会有一个。现在,Qt 连接语义可确保对象按照上述文档保持事件状态。

因此,当您的 Gui 对象被析构时,它也会进行所有断开连接,并且在此期间,它可以确保不再有对该对象的引用,因此在您的 gui destructor print 语句之后调用析构函数.

您可以通过在此处添加一个额外的打印语句来改进测试代码:

qDebug("+Gui");
for(int i=0; i<3; i++) CreateDoc();
qDebug("+/-Gui");
mRepo.mDocs.clear();
qDebug("-Gui");

这也将明确向您表明文档对象在存储库清除后被破坏,而不是在您创建它们时在方法终止时被破坏。输出将使其更加清晰:

主程序

TEMPLATE = app
TARGET = main
QT += widgets
CONFIG += c++11
SOURCES += main.cpp

main.cpp

#include <QApplication>
#include <memory>
#include <QUndoCommand>
#include <QWidget>
#include <QDebug>

struct Document
{
Document() { qDebug("Document"); }
~Document() { qDebug("~Document"); }
QUndoStack mUndostack;
};

struct DocumentRepository
{
DocumentRepository() { qDebug("DocumentRepository"); }
~DocumentRepository() { qDebug("~DocumentRepository"); }
void AddDoc(std::shared_ptr<Document> doc) { mDocs.emplace_back(doc); }
std::vector<std::shared_ptr<Document>> mDocs;
};

struct Gui : public QWidget
{
Gui(DocumentRepository& repo)
: mRepo(repo)
{
qDebug("+Gui");
for(int i=0; i<3; i++) CreateDoc();
qDebug("+/-Gui");
mRepo.mDocs.clear();
qDebug("-Gui");
}

~Gui() { qDebug("~Gui"); }

void CreateDoc()
{
auto docPtr = std::make_shared<Document>();
connect(&docPtr->mUndostack, &QUndoStack::cleanChanged, this, [=](bool) { /* qDebug() << docPtr->mUndostack.count(); */ }, Qt::QueuedConnection);
mRepo.AddDoc(docPtr);
}

DocumentRepository& mRepo;
};

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
DocumentRepository repo;
Gui g(repo);
g.show();

return 0;
}

输出

DocumentRepository
+Gui
Document
Document
Document
+/-Gui
~Document
~Document
~Document
-Gui
~Gui
~DocumentRepository

关于c++ - Qt 信号 lambda 导致 shared_ptr 泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26574960/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com