Singleton Design Pattern in Modern C++
To create only one instance of a class at any point in time.
Join the DZone community and get the full member experience.
Join For FreeIn software engineering, Creational Design Patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. The basic or ordinary form of object creation could result in design problems or added complexity to the design. In this article of the Creational Design Patterns, we're going to take a look at the much-hated & commonly asked design pattern in a programming interview. That is Singleton Design Pattern in Modern C++ which criticizes for its extensibility and testability. I will also cover the Multiton Design Pattern which quite contrary to Singleton.
By the way, If you haven't check out my other articles on Creational Design Patterns, then here is the list:
The code snippets you see throughout this series of articles are simplified and unsophisticated. So you often see me not using keywords like override
, final
, public
(while inheritance) just to make code compact & consumable(most of the time) in single standard screen size. I also prefer struct
instead of class
just to save line by not writing "public:
" sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::
, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons.
Note:
- If you stumbled here directly, then I would suggest you go through, "What Is Design Pattern?" first, even if it is trivial. I believe it will encourage you to explore more on this topic.
- All of this code you encounter in this series of articles are compiled using C++20(though I have used Modern C++ features up to C++17 in most cases). So if you don't have access to the latest compiler you can use https://wandbox.org/ which has preinstalled boost library as well.
Intent
To create one & only one instance of a class at any point in time.
The Singleton Design Pattern ensures that a class has only one instance and provides a global point of access to that instance. This is useful when exactly one object need to coordinate actions across the system. So, essentially, the Singleton Design Pattern is nothing more than specifying a lifetime.
Singleton Design Pattern Example in C++
So the motivation for using a Singleton Design Pattern is fairly obvious. Some components in our system only need to have a single instance. For example, a database that loads up from its constructor into memory and then gives out information about its contents. Once it's loaded up you don't really want more than one instance of it because there is no point, and you also want to prevent your clients/API-users from making any additional copies of that object. Following is a trivial example of the Singleton Design Pattern in C++.
xxxxxxxxxx
/* country.txt
Japan
1000000
India
2000000
America
123500
*/
class SingletonDatabase {
std::map<std::string, int32_t> m_country;
SingletonDatabase() {
std::ifstream ifs("country.txt");
std::string city, population;
while (getline(ifs, city)) {
getline(ifs, population);
m_country[city] = stoi(population);
}
}
public:
SingletonDatabase(SingletonDatabase const &) = delete;
SingletonDatabase &operator=(SingletonDatabase const &) = delete;
static SingletonDatabase &get() {
static SingletonDatabase db;
return db;
}
int32_t get_population(const std::string &name) { return m_country[name]; }
};
int main() {
SingletonDatabase::get().get_population("Japan");
return EXIT_SUCCESS;
}
- Some of the things to note here from the design perspective are:
- Private constructor
- Deleted copy constructor & copy assignment operator
- Static object creation & static method to access
The Problem of Testability With Singleton
So we have our Singleton database and let's suppose that we decide to use this database to do some research and we actually made a new class called a SingletonRecordFinder
which is going to find the total population from the collection of city names provided in the argument as follows.
xxxxxxxxxx
struct SingletonRecordFinder {
static int32_t total_population(const vector<string>& countries) {
int32_t result = 0;
for (auto &country : countries)
result += SingletonDatabase::get().get_population(country);
return result;
}
};
But let's suppose that we decide that we want to test the SingletonRecordFinder
; this is where all the problems show up.
xxxxxxxxxx
vector<string> countries= {"Japan", "India"}; // Strongly tied to data base entries
TEST(1000000 + 2000000, SingletonRecordFinder::total_population(countries));
Unfortunately, because we are strongly tied to the real database and there is no way to substitute this database, I have to use the values taken from the actual file. When these entries change, your test will start failing as you may have not updated the code. And this going to be a continuous problem. Moreover, this is not going to be a unit-test, but it is an integration test as we are not only testing our code but also a production database which is not good design.
Singleton Design Pattern With Dependency Injection
- The problem that we're encountering in the testing of the
SingletonRecordFinder
is to do with the fact that we have a dependency upon essentially the details of how a database provides its data because we're depending directly on the singleton database and the fact that it's a singleton. - So why don't we use a little bit of dependency injection on an interface or abstract class!
x
struct Database { // Dependency
virtual int32_t get_population(const string& country) = 0;
};
class SingletonDatabase : Database {
map<string, int32_t> m_countries;
SingletonDatabase() {
ifstream ifs("countries.txt");
string city, population;
while (getline(ifs, city)) {
getline(ifs, population);
m_countries[city] = stoi(population);
}
}
public:
SingletonDatabase(SingletonDatabase const &) = delete;
SingletonDatabase &operator=(SingletonDatabase const &) = delete;
static SingletonDatabase &get() {
static SingletonDatabase db;
return db;
}
int32_t get_population(const string &country) { return m_countries[country]; }
};
class DummyDatabase : public Database {
map<string, int32_t> m_countries;
public:
DummyDatabase() : m_countries{{"alpha", 1}, {"beta", 2}, {"gamma", 3}} {}
int32_t get_population(const string &country) { return m_countries[country]; }
};
/* Testing class ------------------------------------------------------------ */
class ConfigurableRecordFinder {
Database& m_db; // Dependency Injection
public:
ConfigurableRecordFinder(Database &db) : m_db{db} {}
int32_t total_population(const vector<string> &countries) {
int32_t result = 0;
for (auto &country : countries)
result += m_db.get_population(country);
return result;
}
};
/* ------------------------------------------------------------------------- */
int main() {
DummyDatabase db;
ConfigurableRecordFinder rf(db);
rf.total_population({"Japan", "India", "America"});
return EXIT_SUCCESS;
}
Due to Dependency Injection i.e. Database
interface, our both following issues are resolved:
- We have done a proper unit test rather an integration test,
- Now our testing class is not directly tie-up to Singleton. So no need to change our unit-test over & over in accordance with a database change.
Multiton Design Pattern
Multiton is a variation to Singleton but is not directly linked to it. Remember that Singleton prevents you to have additional instances while Multiton Design Pattern sets up kind of key-value pair along with the limitation for the number of instance creation.xxxxxxxxxx
enum class Importance { PRIMARY, SECONDARY, TERTIARY };
template <typename T, typename Key = std::string>
struct Multiton {
static shared_ptr<T> get(const Key &key) {
if (const auto it = m_instances.find(key); it != m_instances.end()) { // C++17
return it->second;
}
return m_instances[key] = make_shared<T>();
}
private:
static map<Key, shared_ptr<T>> m_instances;
};
template <typename T, typename Key>
map<Key, shared_ptr<T>> Multiton<T, Key>::m_instances; // Just initialization of static data member
struct Printer {
Printer() { cout << "Total instances so far = " << ++InstCnt << endl; }
private:
static int InstCnt;
};
int Printer::InstCnt = 0;
int main() {
using mt = Multiton<Printer, Importance>;
auto main = mt::get(Importance::PRIMARY);
auto aux = mt::get(Importance::SECONDARY);
auto aux2 = mt::get(Importance::SECONDARY); // Will not create additional instances
return EXIT_SUCCESS;
}
Multiton
. Rest of the code is self-explanatory I hope.
Benefits of Singleton Design Pattern
- The Singleton Design Pattern is quite helpful for application configurations as configurations may need to be accessible globally, and future expansions to the application configurations can be consolidated at single place.
- A second common use of this class is in updating old code to work in a new architecture. Since developers may have used globals liberally, moving them into a single class and making it a singleton, can be an intermediary step to bring the program inline to the stronger object-oriented structure.
- Singleton Design Pattern also enhance the maintainability as it provides a single point of access to a particular instance.
Summary by FAQs
What is so bad about the Singleton Design Pattern?
- Singleton object holds the state for the lifetime of the application. Which is bad for testing since you can end up with a situation where tests need to be ordered which is a big no-no for unit tests. Why? Because each unit test should be independent of the other.
- Singleton object causes code to be tightly coupled. This makes guessing the expected result under test scenarios rather difficult as we have seen above in database example. But you can overcome it by using Dependency Injection along with Singleton Design Pattern.
- Imagine the situation where you have a concurrent application accessing Singleton object from every part of your application, It just mashes up things or slows it down if you use a mutex or any other synchronization primitives.
What Is the Correct Way to Implement Singleton Design Pattern?
The right way to implement Singleton is by dependency injection, So instead of directly depending on a singleton, you might want to consider it depending on an abstraction(e.g. an interface). I would also encourage you to use synchronization primitives(like a mutex, semaphores, etc) to control access.
When Should You Use the Singleton Design Pattern?
- Usually, Singleton is used in hardware interface usage limitation. For example, Printers are limited in numbers, so in such a case, a singleton or multiton design pattern is used to manage access.
- Singleton Design Pattern is also widely employed in managing configuration or properties file to manage access.
- We can use the cache as a singleton object as it can have a global point of reference and for all future calls to the cache object, the client application will use the in-memory object.
Have Any Suggestions, Query or Wants to Say Hi
? Take the Pressure Off, You Are Just a Click Away.
Published at DZone with permission of Vishal Chovatiya. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments