Bitten by singletons


This weekend I have been bitten by some singletons. They have annoyed me so much that I am writing this blog about them. I will expose the singleton as a dangerous construct. Tempting, but dangerous.

Singletons, or K_GLOBAL_STATIC as they are called in the KDE world can do a lot of harm because they are sneaky. The are a source of hidden information that makes your function calls behave in unexpected ways.

Singletons are global instances of classes. They contain data that is not obvious in a stack trace. Take for example this function:

int count() {
   static int count = 0;
   return ++count;

The above function will return a different value each time you call it. And the reason is that it knows things that you do not know. It has hidden information, unaccessible to you, the caller.

Compare this to a different function:

int count(int& counter) {
   return ++counter;

This function is better behaved: if you call it with the same arguments, the result is the same.

This example is about a static int, not a K_GLOBAL_STATIC, but the idea is the same: the caller of the function is not in complete control. Yes, you are reading that correctly, if you use singletons, your code is no longer in control: the singletons are there before the first line in main() is executed. They exist before the command-line arguments have been parsed and they are still around after the main() return statement has wound down the stack. The singletons call the shots.

Now, I could go on about downsides to singletons, but other people have done so convincingly.

Instead, I'll tell a tale about how two singletons that are making command-line scripting of KWord impossible for now. The two protagonists in this tale are KoPluginLoader and Kross::Manager.

KoPluginLoader is a singleton that keeps a registry of the plugins that are loaded in KOffice applications. If you want to put a shape in your KOffice application, you have to ask KoPluginLoader, because KoPluginLoader loads the the code that is the shapes.

So what about Kross::Manager? Kross::Manager can, like the name suggests, be dangerous. If you can avoid it, do not cross a cross manager, especially not when it is in charge of your objects. And that is exactly what it is when your run kross from the command-line. Kross::Manager is a singleton that keeps track of all your script variables. If you run a script in kross, the variables you use are deleted when the K_GLOBAL_STATIC Kross::Manager is deleted, which is after the main() function ends.

And this is where the problem occurs. Here is a simple kross script that demonstrates it:

#!/usr/bin/env kross
import Kross
kword = Kross.module("KWord")
doc = kword.document()

What happens in the script is that a KWDocument is created and when it is created, it falls under the control of Kross::Manager. The document can contain shapes that can only be deleted by the code which is controlled by KoPluginLoader. So KoPluginLoader has to be deleted after Kross::Manager deletes the document. And that is exactly what does not happen. KoPluginLoader is destroyed before Kross::Manager and nothing can be done about it because the order of destruction of K_GLOBAL_STATIC objects is mostly outside of your control.

The question that remains is: "Why are KoPluginLoader and Kross:Manager singletons?". There answer is: there is no reason that they are singletons. Singletons are mostly used as an easy way out when a developer does not want to make a dependency explicit by adding an extra argument to functions or constructors that depend on the singletons.

As an exercise to the reader look up one of the many uses of K_GLOBAL_STATIC in KDE and ask yourself how that singleton influences the repeatability of your unit tests.



singletons appear to depower your code by breaking the expected modularity thus (1) preventing unexpected uses and (2) creating unexpected interactions