cgtk

Clarification on GTK memory management for removed widgets


I need a clarification on GTK's memory management when I remove a widget from a container.

/* Create a label widget with a floating reference. */
GtkWidget *lbl_text = gtk_label_new ("Text");

/* Create a grid container widget. */
GtkWidget *grid_text = gtk_grid_new ();

/* Add the label to the grid, which replaces the floating reference with a standard reference. */
gtk_grid_attach ( GTK_GRID(grid_text), lbl_text, 0, 0, 3, 1);

When I destroy the grid grid_text, GTK decrements the reference count on the label to zero and the label's memory is released. All good.

What happens to the label's reference count if I remove the label from the grid?

gtk_grid_remove (GTK_GRID(grid_text), lbl_text);

Does lbl_text now have a reference count of zero and is destroyed, or does it have a floating reference of one and I need to destroy it manually using g_object_unref()?


Solution

  • We can look at the source code:

    gtk_grid_remove --> gtk_widget_unparent --> g_object_unref

    So, the quick answer is that the cleanup is done. But, some of the code is a bit complex ...


    Here is the gtk_grid_remove code:

    static void
    gtk_grid_remove (GtkContainer *container,
                     GtkWidget    *child)
    {
      GtkGrid *grid = GTK_GRID (container);
      GtkGridPrivate *priv = grid->priv;
      GtkGridChild *grid_child;
      GList *list;
    
      for (list = priv->children; list; list = list->next)
        {
          grid_child = list->data;
    
          if (grid_child->widget == child)
            {
              gboolean was_visible = _gtk_widget_get_visible (child);
    
              gtk_widget_unparent (child);
    
              priv->children = g_list_remove (priv->children, grid_child);
    
              g_slice_free (GtkGridChild, grid_child);
    
              if (was_visible && _gtk_widget_get_visible (GTK_WIDGET (grid)))
                gtk_widget_queue_resize (GTK_WIDGET (grid));
    
              break;
            }
        }
    }
    

    AFAICT, the only call that could bump down the refcount would be: gtk_widget_unparent.


    Here is the gtk_widget_unparent code:

    /**
     * gtk_widget_unparent:
     * @widget: a #GtkWidget
     *
     * This function is only for use in widget implementations.
     * Should be called by implementations of the remove method
     * on #GtkContainer, to dissociate a child from the container.
     **/
    void
    gtk_widget_unparent (GtkWidget *widget)
    {
      GtkWidgetPrivate *priv;
      GObjectNotifyQueue *nqueue;
      GtkWidget *toplevel;
      GtkWidget *old_parent;
    
      g_return_if_fail (GTK_IS_WIDGET (widget));
    
      priv = widget->priv;
    
      if (priv->parent == NULL)
        return;
    
      /* keep this function in sync with gtk_menu_detach() */
    
      gtk_widget_push_verify_invariants (widget);
    
      g_object_freeze_notify (G_OBJECT (widget));
      nqueue = g_object_notify_queue_freeze (G_OBJECT (widget), _gtk_widget_child_property_notify_context);
    
      toplevel = _gtk_widget_get_toplevel (widget);
      if (_gtk_widget_is_toplevel (toplevel))
        _gtk_window_unset_focus_and_default (GTK_WINDOW (toplevel), widget);
    
      if (gtk_container_get_focus_child (GTK_CONTAINER (priv->parent)) == widget)
        gtk_container_set_focus_child (GTK_CONTAINER (priv->parent), NULL);
    
      gtk_widget_queue_draw_child (widget);
    
      /* Reset the width and height here, to force reallocation if we
       * get added back to a new parent. This won't work if our new
       * allocation is smaller than 1x1 and we actually want a size of 1x1...
       * (would 0x0 be OK here?)
       */
      priv->allocation.width = 1;
      priv->allocation.height = 1;
    
      if (_gtk_widget_get_realized (widget))
        {
          if (priv->in_reparent)
        gtk_widget_unmap (widget);
          else
        gtk_widget_unrealize (widget);
        }
    
      /* If we are unanchoring the child, we save around the toplevel
       * to emit hierarchy changed
       */
      if (priv->parent->priv->anchored)
        g_object_ref (toplevel);
      else
        toplevel = NULL;
    
      /* Removing a widget from a container restores the child visible
       * flag to the default state, so it doesn't affect the child
       * in the next parent.
       */
      priv->child_visible = TRUE;
    
      old_parent = priv->parent;
      priv->parent = NULL;
    
      /* parent may no longer expand if the removed
       * child was expand=TRUE and could therefore
       * be forcing it to.
       */
      if (_gtk_widget_get_visible (widget) &&
          (priv->need_compute_expand ||
           priv->computed_hexpand ||
           priv->computed_vexpand))
        {
          gtk_widget_queue_compute_expand (old_parent);
        }
    
      /* Unset BACKDROP since we are no longer inside a toplevel window */
      gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_BACKDROP);
      if (priv->context)
        gtk_style_context_set_parent (priv->context, NULL);
      gtk_css_node_set_parent (widget->priv->cssnode, NULL);
    
      _gtk_widget_update_parent_muxer (widget);
    
      g_signal_emit (widget, widget_signals[PARENT_SET], 0, old_parent);
      if (toplevel)
        {
          _gtk_widget_propagate_hierarchy_changed (widget, toplevel);
          g_object_unref (toplevel);
        }
    
      /* Now that the parent pointer is nullified and the hierarchy-changed
       * already passed, go ahead and unset the parent window, if we are unparenting
       * an embedded GtkWindow the window will become toplevel again and hierarchy-changed
       * will fire again for the new subhierarchy.
       */
      gtk_widget_set_parent_window (widget, NULL);
    
      g_object_notify_by_pspec (G_OBJECT (widget), widget_props[PROP_PARENT]);
      g_object_thaw_notify (G_OBJECT (widget));
      if (!priv->parent)
        g_object_notify_queue_clear (G_OBJECT (widget), nqueue);
      g_object_notify_queue_thaw (G_OBJECT (widget), nqueue);
    
      gtk_widget_pop_verify_invariants (widget);
      g_object_unref (widget);
    }
    

    Notice that the last thing it does is g_object_unref

    AFAICT, if the widget is top level, the function does an internal g_object_ref and then g_object_unref before doing the final g_object_unref. These are balanced calls, so probably no change.

    So, the cursory conclusion would be that the function does clean things up.

    But, this function is [a bit] complex, calling many gtk_* functions on the widget, so I can't tell if that last call is merely to compensate for one of those gtk_* calls bumping up the refcount.

    However, that would seem strange to me, so my [naive/simple] conclusion is that the final call to g_object_unref does what you want.

    Thus, without examining all the various calls made, caveat emptor ...


    The best way may be for you do some trickery to get access to the refcount yourself and see the before and after values. This may be UB (use after free), but okay for a one off test.

    To prevent UB, you could wrap the grid destroy call in your own/artificial g_object_ref and g_object_unref pairs. This will prevent the object being reclaimed until your g_object_unref call is made.