-
Notifications
You must be signed in to change notification settings - Fork 32
Singleton Pattern is No Option
The singleton pattern has been both famous and infamous for many years. In this article I will show you a tiny example why they are bad.
Suppose I have a fictional program that serves as a user database. There are some necessary classes, such as User_Data_Manager
, Client_Connection_Manager
, and Configuration_Manager
. I know these names are horrific but let's ignore that for now.
The design is basing on a reasonable assumption that data are centralized. For example, when I need data about a specific user, I can do:
shared_ptr<User> user = User_Manager::instance().find_user_by_id(user_id);
So what's the problem about this? One may expect a critical long-running process can be reconfigured without being restarted (sudo systemctl daemon-reload
is a good example). This is usually served by a reload()
function:
void
reload_all_modules()
{
Configuration_Manager::instance().reload();
User_Data_Manager::instance().reload();
Client_Connection_Manager::instance().reload();
}
Reloading a module can fail or throw exceptions. What if User_Data_Manager::reload()
throws an exception? It might leave User_Data_Manager
in an inconsistent and unusable state. Even if we can implement every submodule with strong exception safety in mind, the entire reload_all_modules()
function still does not guarantee strong exception safety; any exceptions thrown in the middle will render the entire system inconsistent.
The paradigm to address such issues is to copy-and-swap:
void
reload_all_modules()
{
Configuration_Manager::instance_two().reload();
User_Data_Manager::instance_two().reload();
Client_Connection_Manager::instance_two().reload();
Configuration_Manager::instance().swap(Configuration_Manager::instance_two()); // noexcept
User_Data_Manager::instance().swap(User_Data_Manager::instance_two()); // noexcept
Client_Connection_Manager::instance().swap(Client_Connection_Manager::instance_two()); // noexcept
}
And... they are violating the singleton pattern, because there are now two objects for each of them!
The conclusion is that singleton pattern is no option. Instead of having a class have two instances, it's much intuitive to have:
extern Configuration_Manager configuration_manager;
extern User_Data_Manager user_data_manager;
extern Client_Connection_Manager client_connection_manager;
void
reload_all_modules()
{
Configuration_Manager new_configuration_manager;
User_Data_Manager new_user_data_manager;
Client_Connection_Manager new_client_connection_manager;
configuration_manager.swap(new_configuration_manager); // noexcept
user_data_manager.swap(new_user_data_manager); // noexcept
client_connection_manager.swap(new_client_connection_manager); // noexcept
}
No, no singleton pattern any more.