Lazy initialization for Juce singletons


#1

In my app I have a few objects that have to be constructed before main() is entered, and some of them are interdependent on each other. I’m sure you see the obvious problem with that.

My solution starts with using the “StaticObject” template I wrote about previously which solves order-of-construction issues with synchronization primitives used to protect singletons.

Using the StaticObject, I wrote a “SharedSingleton” template class that acts as a ReferenceCountedObject. The template provides a static method getInstance() which returns a ReferenceCountedObjectPtr to the singleton, creating the singleton if it hasn’t been created. The creation of the singleton uses the thread-safe mechanism I wrote about here:

Generic StaticObject, any static var can be thread safe!

I use a SpinLock instead of a CriticalSection because SpinLock doesn’t need to be destroyed, this avoids order of destruction issues on exit.

So, to address your original problem, with this model if you wanted to get rid of the manual initializer for the juce Gui, just shove it into a reference counted singleton that has a thread-safe getInstance () immune to order of initialization issues (using the StaticObject technique), and have top level objects like JUCEApplication acquire and hold a reference. When the last dependent object is destroyed, the reference to the singleton will go away and things will clean themselves up lovely.


#2

I think Singleton should be deprecated.

I’m just of the opinion these days that singletons are an intrinsically bad idea and should never be used, even if you have to write a little more code.

The previous place I worked had a strict rule of no static globals of any class type. This seemed very draconian to me but I have to say that years later, I completely agree.

Now, plain old data is fine, because it can be constructed at compile time and “burned” into the executable. But it’s the order of constructors and destructors that’s so dangerous and so subtle - particularly if you have threads involved!

I also like to write tests for my code - and singletons and static globals make writing tests hard and some tests impossible.

I did pretty well in my first try at my current application but as you know I had small troubles with both static class constructors, and separately with threads and destructors.

My newest version has a single “God struct” called Instance which many top-level classes get a pointer to and which owns all my threads and “static” things. Instance has no methods except a constructor and destructor - I use it entirely to control the construction and destruction of my “static” objects.

This works out very well in practice. I end up passing a single Instance pointer always everywhere, but since it generally replaces other pointers I was passing anyway, my total parameter count is down, and the code is much cleaner - I have a uniform way to access all my services and a uniform place to manage my resources like threads, listeners and static objects.

It means, moreover, that making my application “multi-copy” becomes trivial - there’s no reason, now, that I can’t run multiple copies of my application in the same process space/program.


#3

I don’t. And I’m not going to get into a religious argument over singletons - already had plenty of those in the ##C++ channel on IRC and they are totally non productive. Bottom line, some people like them, some don’t. If you don’t like singletons, don’t use them.

While Singleton patterns can be largely avoided in user code, they occur with regularity when writing libraries.


#4

That’s certainly been my experience too. Would love to get rid of them in juce, but there are places where it’d just be an absolute hassle to expect everyone to supply pointers for shared resources. In your own app, doing that’s not a problem, but I wouldn’t want to make half the methods in Component also take a pointer to a Desktop object, or make the user declare their own glyph cache object, and pass it to all the glyph rendering functions!

Something I’ve thought about would be to roll all the juce singletons into a single uber-singleton… but TBH most of them are in platform-specific code, doing small but essential tasks, and trying to bring them all together would create more mess than it solves.


#5

Did the forum get rolled back? My reply in this thread is missing


#6

There seem to have been some weird glitches in the forum, I got an update for another thread which seemed to have no new information!

Regarding singletons… in the last decade I’ve started to try think an engineer and not like a computer programmer so if you get good results from using Singletons then more power to you. And I certainly don’t think that Jules should get rid of them, BUT people should be aware of the non-obvious consequences of using them.[quote]While Singleton patterns can be largely avoided in user code, they occur with regularity when writing libraries.[/quote]While that’s to some extent true, please remember that some of the most famous badnesses from classic libraries come from singletons (I’m looking at you, errno!) And today, with multicore machines, thread-safety is far more important - if your library has singletons and you are using it in your multi-threaded code, you’re just hoping that your library creator got his threading right, and even more, that his thread-safety strategy is compatible with yours.

I’ve received the “no singletons” advice for almost a decade, and pooh-poohed it as too conservative, but I did shoot myself in the foot more than once with singletons, and in hindsight I think I would have written better code every time if I didn’t have any singletons. Singletons are very convenient, but have lots of traps - perhaps the best advice is “avoid them unless there’s no other good solution, and then be very careful.”


#7

Jules:

You said that my reference counted Singleton for juce library objects was a good idea, except for when there are cycles (i.e. co-dependent singletons).

I posted a simple solution, which is to aggregate the dependent singleton pointers into an aggregate singleton and reference count that instead. So any singletons in a cyclic dependency would get created together and deleted together.


#8

Yeah that’s true. But traps can be avoided by being more proficient and skilled. Of course as a team grows in size this may be difficult in practice. I have observed that the complexities of singleton patterns are trivial when compared to the greater complexity of building a highly concurrent system.

Good point about errno, and there are definitely good ways and bad ways to use singletons. However, they are just too damn convenient to dismiss entirely. I would hate to have to pass a ‘pool pointer’ in every call to malloc, operator new, etc… or pass around in the first parameter of every function, a pointer to a structure containing cin, cout, cerr, et. al. for all the C runtime globals.

You also conveniently ignored the obvious solution of wearing a helmet.


#9

[quote]But traps can be avoided by being more proficient and skilled. [/quote]You must design nuclear power plants for a living. :stuck_out_tongue: Sorry, kidding, kidding!

My implication with the joke is that even the best programmer has significant periods in their coding when they are thinking under par - or otherwise bright and dedicated individuals might have specific holes in their knowledge, experience, or reasoning process that mean they miss the errors they are making.[quote]Of course as a team grows in size this may be difficult in practice. I have observed that the complexities of singleton patterns are trivial when compared to the greater complexity of building a highly concurrent system.[/quote]One solution to this issue is to only allow a few of the developers to have any knowledge of the threading and make everyone else write only thread-indifferent code, in other words, pure functions or methods where they are given what they need to perform their calculations and little else. When people are writing pure functions or methods that run in such a sandbox, their knowledge of concurrency is no longer at issue.[quote]I would hate to have to pass a ‘pool pointer’ in every call to malloc, operator new, etc… or pass around in the first parameter of every function, a pointer to a structure containing cin, cout, cerr, et. al. for all the C runtime globals.[/quote]Clearly behind the scenes many system functions can only be accomplished with statics - particularly memory management and logging.

But, in fact, cin, cout and their buddies are classic singleton horrors, because they have state and yet they are globals! Who amongst us has not fallen into a trap like the following…

std::cout << std::hex << someHexNumber << ", etc"; // [1] std::cout << std::dec << someDecimalNumber << ", etc"; // [2] std::cout << someNumber; // [3]where [2] usually executes after [1], which means that the statement [3] is usually in decimal but occasionally in hex…? The worst is that you can get caught because of code someone else checked in…

And I always wonder, what about the thread-safety of these streams? I simply have no idea. I know that std::cerror allows interleaving on one line…!

So I literally NEVER use cin, cout, cerr in my production code. For my logging needs, I use Google’s open source glog, which I know is thread-safe, and I use Juce’s I/O for anything else.