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.
Comments
depowering
singletons appear to depower your code by breaking the expected modularity thus (1) preventing unexpected uses and (2) creating unexpected interactions
By maninalift at Mon, 09/07/2009 - 12:01