In software engineering, Creational Design Patterns deal with object creation mechanisms, i.e. try 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 & 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:
  1. Factory
  2. Builder
  3. Prototype
  4. Singleton
The code snippets you see throughout this series of articles are simplified not sophisticated. 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:

Intent

To ensure one & only one instance of a class exist at any point in time.

Singleton Design Pattern C++ Example

/* 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:
  1. Private constructor
  2. Deleted copy constructor & copy assignment operator
  3. Static object creation & static method to access

The Problem of Testability With Singleton

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;
    }
};
vector<string> countries= {"Japan", "India"}; // Strongly tied to data base entries
TEST(1000000 + 2000000, SingletonRecordFinder::total_population(countries));

Singleton Design Pattern With Dependency Injection

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:
  1. We have done a proper unit test rather an integration test,
  2. 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

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;
}

Benefits of Singleton Design Pattern

  1. 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.
  2. 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.
  3. 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?
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?