pau*_*ulm 5 c++ lambda qt shared-ptr qt-signals
我有以下代码:
#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;
}
Run Code Online (Sandbox Code Playgroud)
哪个输出:
DocumentRepository
+Gui
Document
Document
Document
-Gui
~Gui
~Document
~Document
~Document
~DocumentRepository
Run Code Online (Sandbox Code Playgroud)
但是在这里你可以看到Document实例在Gui实例之后被破坏了.如果你看看你会看到的评论,我将这个问题缩小到信号的lambda使用shared_ptr.我想知道为什么会导致泄漏,如何解决?
作为参考,不使用shared_ptrlambda 时的"正确"/非泄漏输出是:
DocumentRepository
+Gui
Document
Document
Document
~Document
~Document
~Document
-Gui
~Gui
~DocumentRepository
Run Code Online (Sandbox Code Playgroud)
这是一个有趣的问题,让我们揭开它的神秘面纱:
从官方连接文档:
如果发件人被销毁,连接将自动断开连接.但是,您应该注意,在发出信号时,仿函数中使用的任何对象仍然存在.
在您的示例中,您正在复制在lambda中使用时创建的共享指针,否则不会为共享指针创建副本.副本自然会增加共享指针内对象的引用计数器.以下是shared_ptr的相应文档:
只能通过复制构造或复制将其值分配给另一个shared_ptr,才能与另一个shared_ptr共享对象的所有权
现在,让我们区分这两种情况:
当你不复制共享指针时,只有一个对象的引用,所以当你的文档存储库完成清除时,就没有更多的引用,因此你可以破坏对象,因为你没有在lambda函数中做任何有用的事情,因此可以进行优化.
复制共享指针时,对lambad外部的对象有一个引用,由于共享指针复制,也会有一个引用.现在,Qt连接语义确保按照上述文档保持对象处于活动状态.
因此,当您的Gui对象被破坏时,它也将完成所有断开连接,并且在此期间,它可以确保不再有对该对象的引用,因此在您的gui析构函数print语句之后调用析构函数.
你可以在这里添加一个额外的print语句来改进测试代码:
qDebug("+Gui");
for(int i=0; i<3; i++) CreateDoc();
qDebug("+/-Gui");
mRepo.mDocs.clear();
qDebug("-Gui");
Run Code Online (Sandbox Code Playgroud)
这也将明确指出,在您创建存储库之后,文档对象在存储库清除后被销毁,而不是在方法终止时.输出将使其更清晰:
TEMPLATE = app
TARGET = main
QT += widgets
CONFIG += c++11
SOURCES += main.cpp
Run Code Online (Sandbox Code Playgroud)
#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;
}
Run Code Online (Sandbox Code Playgroud)
DocumentRepository
+Gui
Document
Document
Document
+/-Gui
~Document
~Document
~Document
-Gui
~Gui
~DocumentRepository
Run Code Online (Sandbox Code Playgroud)