草庐IT

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

coder 2024-02-10 原文

我有以下代码:

#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/

有关c++ - Qt 信号 lambda 导致 shared_ptr 泄漏?的更多相关文章

  1. ruby-on-rails - rails : keeping DRY with ActiveRecord models that share similar complex attributes - 2

    这似乎应该有一个直截了当的答案,但在Google上花了很多时间,所以我找不到它。这可能是缺少正确关键字的情况。在我的RoR应用程序中,我有几个模型共享一种特定类型的字符串属性,该属性具有特殊验证和其他功能。我能想到的最接近的类似示例是表示URL的字符串。这会导致模型中出现大量重复(甚至单元测试中会出现更多重复),但我不确定如何让它更DRY。我能想到几个可能的方向...按照“validates_url_format_of”插件,但这只会让验证干给这个特殊的字符串它自己的模型,但这看起来很像重溶液为这个特殊的字符串创建一个ruby​​类,但是我如何得到ActiveRecord关联这个类模型

  2. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  3. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  4. ruby-on-rails - 如何重构 "shared"方法? - 2

    我正在使用RubyonRails3.2.2,我想从我的模型/类中“提取”一些方法。也就是说,在不止一个类/模型中,我有一些方法(注意:方法与用户授权相关,并被命名为“CRUD方式”),这些方法实际上是相同的;所以我认为DRY方法是将这些方法放在“共享”模块或类似的东西中。实现该目标的常见且正确的方法是什么?例如,我应该将“共享”代码放在哪里(在哪些目录和文件中)?如何在我的类/模型中包含提到的方法?你有什么建议?注意:我正在寻找“RubyonRails制作东西的方式”。 最佳答案 一种流行的方法是使用ActiveSupport关注点

  5. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  6. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

  7. ruby - 如何计算 Liquid 中的变量 +1 - 2

    我对如何计算通过{%assignvar=0%}赋值的变量加一完全感到困惑。这应该是最简单的任务。到目前为止,这是我尝试过的:{%assignamount=0%}{%forvariantinproduct.variants%}{%assignamount=amount+1%}{%endfor%}Amount:{{amount}}结果总是0。也许我忽略了一些明显的东西。也许有更好的方法。我想要存档的只是获取运行的迭代次数。 最佳答案 因为{{incrementamount}}将输出您的变量值并且不会影响{%assign%}定义的变量,我

  8. Ruby 守护进程导致 ActiveRecord 记录器 IOError - 2

    我目前正在用Ruby编写一个项目,它使用ActiveRecordgem进行数据库交互,我正在尝试使用ActiveRecord::Base.logger记录所有数据库事件具有以下代码的属性ActiveRecord::Base.logger=Logger.new(File.open('logs/database.log','a'))这适用于迁移等(出于某种原因似乎需要启用日志记录,因为它在禁用时会出现NilClass错误)但是当我尝试运行包含调用ActiveRecord对象的线程守护程序的项目时脚本失败并出现以下错误/System/Library/Frameworks/Ruby.frame

  9. 键删除后 ruby​​ 哈希内存泄漏 - 2

    你好,我无法成功如何在散列中删除key后释放内存。当我从哈希中删除键时,内存不会释放,也不会在手动调用GC.start后释放。当从Hash中删除键并且这些对象在某处泄漏时,这是预期的行为还是GC不释放内存?如何在Ruby中删除Hash中的键并在内存中取消分配它?例子:irb(main):001:0>`ps-orss=-p#{Process.pid}`.to_i=>4748irb(main):002:0>a={}=>{}irb(main):003:0>1000000.times{|i|a[i]="test#{i}"}=>1000000irb(main):004:0>`ps-orss=-p

  10. ruby - 从另一个私有(private)方法中使用 self.xxx() 调用私有(private)方法 xxx,导致错误 "private method ` xxx' called” - 2

    我正在尝试获得良好的Ruby编码风格。为防止意外调用具有相同名称的局部变量,我总是在适当的地方使用self.。但是现在我偶然发现了这个:classMyClass上面的代码导致错误privatemethodsanitize_namecalled但是当删除self.并仅使用sanitize_name时,它会起作用。这是为什么? 最佳答案 发生这种情况是因为无法使用显式接收器调用私有(private)方法,并且说self.sanitize_name是显式指定应该接收sanitize_name的对象(self),而不是依赖于隐式接收器(也是

随机推荐