DEMKO.CA

GTK+, GTKmm and their wacky reference counting scheme

Aleksander Demko May, 2007

This document attempts to make sense of the reference counting scheme in GTK+ and GTKmm. I've put this all in one place, since it doesn't seem to be in any central location already.

Overview

In general, I think GTK+ (with it's floating reference nonsense) has made a mess of a seemingly simple concept all in the name in order to make C programmer's jobs slightly less explicit.

They decided that all objects are born with a reference count of one, this means that you'll always have more g_object_unref calls than g_object_ref calls. To work around this, they've flagged this first reference as "floating" and added a g_object_ref_sink call, that will only add additional reference count if the existing count is not floating (and if it is floating, it removes the floating status, effectively giving pretending like the g_object_ref_sink caller added the first, at-birth, reference count).

I like to pair reference counting calls, it makes writing stuff like smart pointer classes much easier. So to get what I want, I simply call utilize this floating reference hack and simply remember to do all my reference counting calls as g_object_ref_sink and g_object_unref pairs. Simple enough.

Here is an example:

GtkLabel *w = gtk_label_new("blah"); //floating=true, refcount=1
g_object_ref_sink(w); //floating=false, refcount=1
g_object_ref_sink(w); //floating=false, refcount=2
g_object_unref(w); //floating=false, refcount=1
g_object_unref(w); //floating=false, refcount=0, destruction!

Enter GTKmm.

GTKmm is a wrapper library around GTK+ objects, and as such has a C++ class that "shadows" each GTK+ object. That is Gtk::Label has a GtkLabel heap instance within it.

The C++ class itself has no reference count, this is delegated to the C class. In "unmanaged" mode (which is the default on creation, the C++ class keeps one real reference count (any floating references are sunk) on the internal class.

count away this first reference. This is confusing and makes things like the RefPtr in Glibmm asymmetric 2) you now have to think about reference counting and floating references. Gah.

I'm not even sure where the RefPtr class is supposed to be used. Is it supposed to be used only from functions that return then (the many create() static methods), or or any GUI classes too. My source code analysis concludes: no mixing and matching. A shame for consistency and readability.

Finally, in RefPtr doesn't let you get a reference or normal pointer variable in any way. It does this in the name of purity, but makes it useless in normal use. Why? Because you have to, once in awhile, get that value, and the API should trust you then and assume you know what you're doing. boost's pointers do this and so does the STLs, come on gtkmm, catch up.

GTK+ (C)

G_OBJECT

g_object_ref
  increases ref by one
g_object_unref
  decreases ref by one, if ref then == 0, the instance is destroyed
g_object_ref_sink
  If (object has a floating reference)
    converts that reference to a normal ref (sinks it)
  else
    increases ref by one

All objects start with a ref count of 1, and it's floating.

GTK_WIDGET

gtk_widget_ref and gtk_widget_unref
are the same as g_object_ref and g_object_unref

GTKmm (C++)

Glib::ObjectBase

reference() and unreference() simply do g_object_ref and g_object_unref on the internal G_OBJECT (note, not the g_object_ref_sink version).

ctor() doesn't modify the counts (leaves it at 1, floating) dtor() does a unref, if non-null

Has no concept of managed, which is later introduced by Gtk::Object

Glib::RefPtr

Never exposes the internal *T! ->() can be used as a hack, though.

Always maintains a ref to the *T instance, via reference/unreference calls.

If an assignment to the RefPtr comes from a raw T*, it ASSUMES that it owns an implicit reference and doesn't bother to do a T->reference(). This method seems to be designed to be coupled only with class-instance create() methods, which know to return the 1 ref object, and not a general purpose smart pointer.

However, the assignment comes from another RefPtr, it will then reference() up its own reference on the object.

Basically, this makes HOW you assign to a RefPtr VERY important. RefPtr tries to "help" you by not letting you get at the raw pointer again, but you can still get yourself into trouble:

reftest *o = new reftest;
Glib::RefPtr<reftest> t(o), t2(o);
// the o instance has a reference count of 1 (from creation)
// but has to Refptrs on it. Whey they get out of scope, o->unreference()
// will be called TWICE.

// This is fine though:
reftest *o = new reftest;
Glib::RefPtr<reftest> t(o);
Glib::RefPtr<reftest> t2(t);

Conclusion, RefPtr isn't designed to be a general reference counting smart pointer.

Gtk::Object

GTKmm changes the floating stuff. It disables it by default, for its own "Object by default manages the G_OBJECT" thing.

The C++ object's life time (especially ref counting) information is kept within the C classes. That is, when the C object emits its "I'm being destroyed" message, THEN the C++ class dies. Of course, if the C++ class dies because of en explicit delete or stack deallocation, it kills (well, unrefs) the internal C object (while ignoring the then returned object destruction event).

ctor(): Initially sinks its first floating reference. Therefore all objects start with a real ref count of 1. This object is said to not be managed (I call it self-managed).

dtor(): If self managed, _unref the G_OBJECT instance.

set_manage() (aka manage(T)) will cause the widget to be managed (and no longer self-managed). I think of this as making the "C++ object managed by the lifetime of the C class". Obviously, the C++ object must be dynamically allocated for this. set_manage() also causes the only reference to be floating (again).

Container::add(Widget) simply does a container add on the raw GTK+/C widgets, ignoring the C++ stuff around the C widget. Although this seems like it leaves the C++ class shell dangling, it doesn't really. The C++ class will either be self-managed (ie. an automatic variable, and therefore in charge of its own lifetime), or it will be "listening" to when its child C class dies, and will kill itself accordingly.