You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Gtkmm中添加/移除Widget后内存泄漏的原因探究

Gtkmm Widget移除后内存泄漏问题解析

问题背景

将Widget添加到容器后执行移除操作,发现Widget出现内存泄漏:自定义MyWidget的析构函数未触发(原生Gtk::Label也存在相同问题),测试代码在两个Linux发行版(gtkmm30-3.24.5-1.el9.x86_64、gtkmm3 3.24.7-1)验证,打印ref_count结果为1,预期析构函数执行但未触发。

测试代码

#include <iostream>
// gtkmm30-3.24.5-1.el9.x86_64
// or gtkmm3 3.24.7-1 (arch)
#include <gtkmm/main.h>
#include <gtkmm/builder.h>
#include <gtkmm/label.h>
#include <gtkmm/window.h>
#include <gtkmm/box.h>

#include <cassert>

using namespace std;

class MyWidget : public Gtk::Label{
public:
    MyWidget(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder);
    virtual ~MyWidget();
};

MyWidget::~MyWidget(){
    cout << "MyWidget::~MyWidget()" << endl;
}

MyWidget::MyWidget(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
 : Gtk::Label(cobject)
{
    assert(builder);
}

int main()
{
    Gtk::Main main;
    
    // Create widget
    auto builder = Gtk::Builder::create_from_file("widget.glade");
    MyWidget* widget = nullptr;
    builder->get_widget_derived("widget", widget);

    {
        Gtk::Window window;
        window.add(*widget);
        // Use window ....
        window.remove(); // No leak if this line is commented
    }
    
    builder.reset();
    cout << G_OBJECT(widget->gobj())->ref_count << endl; // Print => 1
    // Expected to see "MyWidget::~MyWidget()" but No ! Why ?
}

Glade文件内容

<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.40.0 -->
<interface>
  <requires lib="gtk+" version="3.24"/>
  <object class="GtkLabel" id="widget">
    <property name="visible">True</property>
    <property name="can-focus">False</property>
    <property name="label" translatable="yes">widget</property>
  </object>
</interface>

原因分析

  1. 容器add()/remove()的引用计数逻辑:调用window.add(*widget)时,Gtk内部会给Widget的GObject实例增加一次引用;而window.remove()仅将Widget从容器的子控件列表中移除,不会减少这次添加时增加的引用。
  2. Builder的引用管理:Gtk::Builder创建Widget时会持有其引用,调用builder.reset()后,Builder的引用被释放,但容器add()带来的引用仍存在,导致Widget的引用计数维持为1,无法触发析构。
  3. 注释remove()无泄漏的原因:如果不调用remove(),窗口销毁时会自动递归销毁所有子控件,同时减少Widget的引用计数,结合Builder引用的释放,最终引用计数降为0,Widget被正确销毁。

解决方案

方案1:手动释放引用

在调用remove()后,手动调用unreference()释放容器add()增加的引用:

// ... 原代码 ...
    {
        Gtk::Window window;
        window.add(*widget);
        window.remove();
        widget->unreference(); // 释放add操作增加的引用
    }
// ... 原代码 ...

注意:需确保不会重复调用unreference(),否则会触发GObject引用计数错误导致崩溃。

方案2:使用Glib::RefPtr自动管理生命周期

将裸指针替换为Glib::RefPtr,利用其自动引用计数管理功能,避免手动操作引用:

#include <iostream>
#include <gtkmm/main.h>
#include <gtkmm/builder.h>
#include <gtkmm/label.h>
#include <gtkmm/window.h>
#include <gtkmm/box.h>
#include <cassert>

using namespace std;

class MyWidget : public Gtk::Label{
public:
    MyWidget(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder);
    virtual ~MyWidget();
};

MyWidget::~MyWidget(){
    cout << "MyWidget::~MyWidget()" << endl;
}

MyWidget::MyWidget(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
 : Gtk::Label(cobject)
{
    assert(builder);
}

int main()
{
    Gtk::Main main;
    
    auto builder = Gtk::Builder::create_from_file("widget.glade");
    Glib::RefPtr<MyWidget> widget;
    builder->get_widget_derived("widget", widget);

    {
        Gtk::Window window;
        window.add(*widget);
        window.remove();
    }
    
    builder.reset();
    // main函数结束时,widget的RefPtr被销毁,引用计数降为0,析构函数触发
}

内容的提问来源于stack exchange,提问作者SVA522

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 06:10:36