How to make a GObject singleton

A while back some people asked me to blog about how to make a GObject singleton. I think we are doing it right in Empathy’s code.

The most common way of doing a singleton is to add a function like that:

foo_bar_get_default (void)
  static FooBar *self = NULL;

  if (self == NULL)
    self = foo_bar_new ();

  return self;

It means that you should never unref the return value, and the singleton is leaked for the entire live of the process (unless you play some atexit magic). It also means that g_object_new(FOO_TYPE_BAR, NULL) will return a new object and not a singleton, which is not friendly for bindings.

Here is how we are doing it most of the time in Empathy:

static GObject*
constructor (GType type,
                 guint n_construct_params,
                 GObjectConstructParam *construct_params)
  static GObject *self = NULL;

  if (self == NULL)
      self = G_OBJECT_CLASS (foo_bar_parent_class)->constructor (
          type, n_construct_params, construct_params);
      g_object_add_weak_pointer (self, (gpointer) &self);
      return self;

  return g_object_ref (self);

static void
foo_bar_class_init (FooBarClass *klass)
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->constructor = constructor;

With that code, g_object_new() will return a singleton, and will always return a new ref. That means that you have to call g_object_unref() on the singleton once you don’t need it anymore, just like any normal object. If the last user of the singleton drops its ref, then the object is finalized (so there is no leak) and next time someone needs the singleton, a new one will be created (thanks to the weak pointer).

Of course to avoid object create/destroy ping-pong, you could want to keep a ref for the whole live of the process anyway, it really depend on your needs. But if you are writing a library, you cannot know in advance if the object will be needed for the whole live of the process, so it’s better to let the program using the library decide by managing the refcount itself.

Update: Thanks to Rony and Alexander for pointing me that this is not thread-safe. I think we have to use a mutex in that case. If someone has a better idea, please tell me :)

Common mistake with GtkMenu

While hacking on Empathy, I discovered that most GtkMenu were leaked everywhere in the code. It looks like a common mistake so I decided to blog about it. Maybe other projects are doing that as well. For me it’s something new I learned recently, sorry if everybody was already aware of that :)

How it works

gtk_menu_new() returns an initially unowned widget. It means that its refcount is 1 and the floating flag is set. When you call gtk_menu_popup() on it, an ref is temporarily added as long as the menu is shown. That means that its refcount is now 2 and the floating flag is still set.

Popup and forget

If you want to show the menu and forget, then after calling gtk_menu_popup() you have to clear the floating flag using g_object_ref_sink() then drop the ref you own using g_object_unref(). Like that the menu will only have 1 ref that will be dropped as soon as the menu is popped down. So your menu will be finalized.

Reuse your menu

If you want to keep a pointer to your menu to reuse it, you have to attach the menu to a widget, using gtk_menu_attach_to_widget(). That means the widget will become owner of your menu because it calls g_object_ref_sink() and will make sure the GdkScreen on the menu is the same than on the attach widget. So if ‘self’ is your widget on which you want to popup a menu, simply do priv->menu = gtk_menu_new(); gtk_menu_attach_to_widget (menu, self, NULL); then you can do gtk_menu_popup(priv->menu) when you want. Like that your menu will be finalized in the same time as self.

Using GtkUIManager

If your menu comes from a GtkUIManager, then it is already owned by the ui manager. That means that as long as you own a ref to the manager, you can use the menu. I think you should still attach the menu to your widget to make sure the GdkScreen is handled correctly. Can someone confirm this?