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>
原因分析
- 容器
add()/remove()的引用计数逻辑:调用window.add(*widget)时,Gtk内部会给Widget的GObject实例增加一次引用;而window.remove()仅将Widget从容器的子控件列表中移除,不会减少这次添加时增加的引用。 - Builder的引用管理:Gtk::Builder创建Widget时会持有其引用,调用
builder.reset()后,Builder的引用被释放,但容器add()带来的引用仍存在,导致Widget的引用计数维持为1,无法触发析构。 - 注释
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
相关产品推荐
相关产品推荐

