Search code examples
c++gtkmm

Gtkmm add/Remove widget leaks Why?


When I add a widget to container, then I remove it. Widget is leaked, why ? I used a "MyWidget" to spy widget deletion but I get same result from a classic Gtk::Label. Code below have been tested on two distro.

#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 ?
}

I expected to see ~MyWidget destructor executed.

This is my glade file content

<?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>

Solution

  • The incorrect assumption we made here is that Gtk::Window::remove() destroys the widget it removes, but in reality, it only removes its reference from the parent container. I instrumented your code with extra couts to see what was happening and when this line is executed:

    builder->get_widget_derived("widget", widget);
    

    A MyWidget instance is created by the builder. At this point, the builder owns a reference to it and the ref count is one. Then, when the following line is executed:

    window.add(*widget);
    

    the window, which is a one element container as you pointed out, takes a (owning) reference on the MyWidget instance. So the reference count on the MyWidget instance is now 2: one for the builder, one for the window. This is where it gets tricky. When executing:

    window.remove();
    

    the window "removes" its reference to the MyWidget instance, in the sense that it will no longer show it, but the reference count on it is not decremented. At this point, there are still two references on the MyWidget instance: one for the builder, and one that is not owned by anyone (it is leaked, unless someone explicitly calls delete on it). When this line is executed:

    builder.reset();
    

    The builder is destroyed, and its reference to the MyWidget instance is removed, bringing the reference count on it to one. This is why printing the reference count always prints "1" in your case.

    Now if we comment out:

    window.remove();
    

    What happens it that at the end of main, the window destroys itself and the widget it references (if any). In your case, this is the MyWidget instance, so the destructor is called and the reference count is brought to 0. You don't see it printed because it happens after the cout call, at the } (end of scope for main).