Порождающий паттерн. Одиночка.



Паттерн - Одиночка(Singleton)

Паттерн гарантирует, что некоторый класс может иметь только один экземпляр, и предоставляет глобальную точку доступа к нему.
Для некоторых классов важно, чтобы существовал только один экземпляр. При этом сам класс контролирует то, что у него есть только один экземпляр, может запретить создание дополнительных экземпляров, перехватывая запросы на создание новых объектов, и он же способен предоставить доступ к своему экземпляру.
Одиночка определяет операцию Instance, которая позволяет клиентам получать доступ к единственному экземпляру. Клиенты имеют доступ к одиночке только через эту операцию.
Паттерн позволяет избежать засорения пространства имен глобальными переменными, в которых хранятся уникальные экземпляры. От класса Singleton можно порождать подклассы, а приложение легко сконфигурировать экземпляром расширенного класса.
Реализуется одиночка обычно с помощью статического метода класса, который имеет доступ к переменной, хранящей уникальный экземпляр, и гарантирует инициализацию переменной этим экземпляром перед возвратом ее клиенту.

Начиная с С++11 инициализация static переменных, является потокобезопасной (If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once (similar behavior can be obtained for arbitrary functions with std::call_once).
Note: usual implementations of this feature use variants of the double-checked locking pattern, which reduces runtime overhead for already-initialized local statics to a single non-atomic boolean comparison. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.)
Поэтому классический пример одиночки Мэйерса(представленный ниже), является потокобезопасным.
#include <iostream>

struct logSingleton
{
  void init()
  {
    // вызывается гарантированно один раз
    // выполняем открытие файла
    // выполняем нужные действия
    std::cout << "inited!" << std::endl;
  }

  static logSingleton& instance()
  {
    static logSingleton res;
    return res;
  }

  void log()
  {
    std::cout << "Logging..." << std::endl;
  }

private:
  logSingleton()
  {
    init(); // инициализация выполняется один раз
  }

  logSingleton(const logSingleton&) = delete;
  logSingleton & operator=(const logSingleton&) = delete;

  logSingleton(const logSingleton&&) = delete;
  logSingleton &operator=(const logSingleton&&) = delete;
};

int main()
{
  {
    logSingleton& s1 = logSingleton::instance();
    s1.log();
  }
  {
    logSingleton& s2 = logSingleton::instance();
    s2.log();
  }
  return 0;
}

Какие есть проблемы у одиночек.
Проблема с созданием объекта, довольно сложно сделать удобный механиз, с помощью которого мы имели бы возможность вызывать метод instance(), с различными аргументами, по факту нужно описать несколько конструкторов и столько же функций instance().
Объект одиночка, живет до конца жизни программы, лично для меня это не является проблемой, но некоторые считают что это проблема. Опять таки, все зависит от задач, и всегда можно сделать реализацию одиночки так, чтобы можно было освобождаться не от одиночки, а от данных которые он держит в памяти. Проблема с наследованием, я советую вообще не пытаться наследовать одиночку, потому что с наследованием связано много подводных камней, которые трудно вылавливать, если вам нужно наследоваться от одиночки, то скорее всего вам не нужен одиночка, а нужно что-то другое, и возможно это будет одно решение для конкретной задачи, и паттерном называться уже не будет. Наследуюясь вы должны обратить внимание на статический метод instance() базового класса, оставлять таким как есть? или менять?
#include <iostream>

struct SingBase
{
  static SingBase &instance()
  {
    std::cout << "SingBase::instance()" << std::endl;
    static SingBase res;
    return res;
  }

protected:
  SingBase()
  {
    std::cout << "SingBase()" << std::endl;
  }

  ~SingBase()
  {
    std::cout << "~SingBase()" << std::endl;
  }
};

struct SingType1 : SingBase
{
  static SingType1 &instance()
  {
    std::cout << "SingType1::instance()" << std::endl;
    static SingType1 res;
    return res;
  }
protected:
  SingType1()
  {
    std::cout << "SingType1()" << std::endl;
  }

  ~SingType1()
  {
    std::cout << "~SingType1()" << std::endl;
  }

};

int main()
{
  SingBase::instance();
  SingType1::instance();

  SingBase::instance();
  SingType1::instance();
  return 0;
}

Сложно проследить зависимость одних одиночек от других.
Всегда думайте и размышляйте :-)
Можно завести специальное место, в начале функции main() в котором вы вызовете все методы instance(), всех одиночек, этим вы уже оградите себя от ряда проблем.
Но если следовать правилу - код должен быть простым, код пишут для человека, а компилятор переводит этот код для компьютера, то только это одно правило уже избавляет от многих проблем.




!ВОЗМОЖНЫ ДОПОЛНЕНИЯ!

Комментарии