Modern C++ Design Pattern/Chatper 5. 싱글턴

created : 2020-04-07T11:44:17+00:00
modified : 2020-09-26T14:17:37+00:00
singleton cpp

Singleton

    static Database database{}; /* not recommended */

    /**
     * This method is only secured about MT(Multi Thread)-Safe on C++11 or Higher.
     */
    Database& get_database()
    {
      static Database databse;
      return database;
    }

Traditional Implementation

    struct Database
    {
    protected:
      Database() { /* Do something */ }
    public:
      static Database& get()
      {
        // MT-Safe on C++11 or Higher
        static Database database;
        return database;
      }
      Database(Database const&) = delete;
      Database(Database &&) = delete;
      Database& operator=(Database const&) = delete;
      Database& operator=(Database &&) = delete;
    };
        static Database& get() {
          static Database* database = new Database();
          return *database;
        }
        /* No Memory Leak, because static variable only once initializes in runtime */

Multi-Thread Safety

Well-Known Issue of Singleton

        class Database
        {
        public:
          virtual int get_population(const std::string& name) = 0;
        };

        class SingletonDatabase : public Database
        {
        protected:
          SingletonDatabase() { /* Read Data from db */ }
          std::map<std::string, int> capitals;
        public:
          SingletonDatabase(SingletonDatabase const&) = delete;
          void operator=(SingletonDatabase const&) = delete;

          static SingletonDatabase& get()
          {
            static SingletonDatabase db;
            return db;
          }

          int get_population(const std::string& name) override
          {
            return capitals[name];
          }
        };

        struct SingletonRecordFinder
        {
          int total_population(std::vector<std::string> names)
          {
            int result = 0;
            for (auto& name : names)
              result += SingletonDatabase::get().get_population(name);
            return result;
          }
        };
- This code can create issue, data can be changed in unit test. A solution is to make configuraion class like this.
            struct ConfigurableRecordFinder
            {
              explicit ConfigurableRecordFinder(Database& db)
                : db{db} {}

              int total_population(std::vector<std::string> names)
              {
                int result = 0;
                for (auto& name : names)
                  result += db.get_population(name);
                return result;
              }

              Database& db;
            };

            class DummyDatabase : public Database
            {
              std::map<std::string, int> capitals;
            public:
              DummyDatabase()
              {
                capitals["alpha"] = 1;
                capitals["beta"] = 2;
                capitals["gamma"] = 3;
              }

              int get_population(const std::string& name) override {
                return capitals[name];
              }
            };

            TEST(RecordFinderTests, DummyTotalPopulationTest)
            {
              DummyDatabase db{};
              ConfigurableRecordFinder rf{ db};
              EXPECT_EQ(4, rf.total_population(
                std::vector<std::string>{"alpha", "gamma"}));
            }

Inversion of Control

    auto injector = di::make_injector(
      di::bind<IFoo>.to<Foo>.in(di::singleton),
      // Other configuration
    };

Monostate

    class Printer
    {
      static int id;
    public:
      int get_id() const { return id; }
      void set_id(int value) { id = value; }
    };

Summary