Singleton Design Pattern in C++ and How It Is Used in HALs
Why some classes should have only one instance - and how AOSP HALs rely on this idea
1 min read
In software design, not everything should be created again and again. Some components are too important - and too shared - to have multiple copies floating around in memory. That is exactly the problem the Singleton Design Pattern was created to solve.
What is Singleton?
The Singleton pattern is a creational design pattern that guarantees a class has only one instance, and provides a single, well-known global access point to it. Think of it as a single source of truth for a particular concern in your system.
- ▸Only one instance of the class can ever exist in the process
- ▸That single instance is globally accessible through a controlled interface
- ▸Construction is restricted - you cannot accidentally create another one with new
Why It Matters in AOSP HALs
In systems like Android Automotive and AOSP HALs, the underlying hardware is a shared resource. Multiple framework modules, services, and clients all need to talk to the same physical sensor, audio device, vehicle bus, or display controller.
What goes wrong without a Singleton
- ▸Conflicts - two modules try to drive the same hardware register at the same time
- ▸Inconsistent state - each instance keeps its own copy of cached values
- ▸Resource leaks - file descriptors and IPC handles get duplicated unnecessarily
- ▸Painful debugging - logs from multiple instances make it hard to trace one flow
What a Singleton gives you
- ▸One interface that every client uses to reach the hardware
- ▸One controlled access point, easy to guard with locking
- ▸Consistent state shared across the whole HAL implementation
- ▸Predictable lifecycle - created once, lives for the lifetime of the HAL service
Singleton in C++
Below is a classic Singleton implementation in C++. The constructor is private, the only way to obtain the instance is through the static getInstance() method, and a static pointer holds the one and only object.
C++ Code
// input-output stream library
#include <iostream>
// avoid the std:: prefix on cout
using namespace std;
class Singleton {
private:
// holds the single instance
static Singleton* instance;
// private constructor
Singleton() {
cout << "Singleton Instance Created!" << endl;
}
public:
// global access point to the instance
static Singleton* getInstance() {
if (instance == nullptr) {
// create the instance only the first time
instance = new Singleton();
}
// return the same instance every call
return instance;
}
void showMessage() {
cout << "Hello from Singleton!" << endl;
}
};
// initialize the static member
Singleton* Singleton::instance = nullptr;
int main() {
// creates the instance
Singleton* obj1 = Singleton::getInstance();
// returns the same instance
Singleton* obj2 = Singleton::getInstance();
obj1->showMessage();
obj2->showMessage();
if (obj1 == obj2) {
cout << "Both objects are the same instance!" << endl;
}
return 0;
}What this code shows
- ▸The constructor is private, so no client code can do new Singleton() directly
- ▸The static instance pointer survives across calls and holds the single object
- ▸getInstance() lazily creates the object on the first call and reuses it after that
- ▸obj1 and obj2 always point to the exact same address - proving uniqueness