tl;dr: A singleton type enforces that only a single instance can exist at any time, usually by handling instantiation itself and preventing anybody else from creating other instances. That’s its defining feature. If you need that particular enforcement, you need a singleton. If you just need a globally accessible object, a “normal” global (a.k.a. static) variable will do.
The only instance of a singleton type acts a lot like a global variable. You might call it through a function instead of directly, but that’s a detail. Essentially it’s still a globally accessible object.
About lazy loading
Singletons can be both lazy or non-lazy (is there a better word for non-lazy?). Since .NET is foreign to me consider this common singleton implementation in C++.
class Singleton {
public:
static Singleton& get() {
static Singleton instance;
return instance;
}
private:
// private ctors prevent
// the rest of the world from instantiating
Singleton() = default; // default ctor
// ... same for copy ctor etc.
};
That’s lazy. instance
isn’t constructed until you call Singleton::get()
for the first time. Then that object lives until the program terminates. In particular, calling get()
again does not create any more instances or replace the existing one.
You can make it non-lazy, too:
// in file: singleton.hpp
class Singleton {
public:
static Singleton& get() {
return instance;
}
private:
Singleton() = default;
// ... same for copy ctor etc.
static Singleton instance;
};
// in file: singleton.cpp
Singleton Singleton::instance;
Now instance
is constructed immediately when the program starts and get()
returns a reference to the already existing object. Ignore the split into two files. That’s a C++ language detail.
I’d assume that most languages have the features to implement both lazy and non-lazy singletons. In any case singletonness and lazyness are orthogonal concepts.
Instantiation control vs. access scope
When looking at the fundamental nature of singletons and global variables you can say:
- Singletons are about restricting instantiation.
- Global variables are about making the same object accessible in a broad-ish scope.
There is a good bit of overlap. A singleton instance that’s not accessible in a relatively broad scope wouldn’t be particularly useful. And a global variable can be a singleton by convention because many different places in the program use that one canonical object.
The similarities continue when considering lifetime. Both singletons and global variables tend to be instantiated early in a program’s life and tend to exist until it terminates.
What I wouldn’t count as a meaningful difference is the usual get()
or instance()
access function of a singleton. Hide a global variable behind such a function and you have the same thing, including the possibility to do additional work when the function is called.