Для решения этой проблемы паттерн Singleton возлагает контроль над созданием единственного объекта на сам класс

1) Доступ к этому объекту осуществляется через статическую функцию-член класса, которая возвращает указатель или ссылку на него. Этот объект будет создан только при первом обращении к методу, а все последующие вызовы просто возвращают его адрес.

2) Для обеспечения уникальности объекта, конструкторы и оператор присваивания объявляются закрытыми.

Для некоторых классов важно, чтобы существовал только один экземпляр.

При этом сам класс контролирует то, что у него есть только один экземпляр, может запретить создание дополнительных экземпляров, перехватывая запросы на создание новых объектов, и он же способен предоставить доступ к своему экземпляру.

3) «Одиночка» определяет операцию Instance, которая позволяет клиентам получать доступ к единственному экземпляру.

Клиенты имеют доступ к «одиночке» только через эту операцию.

Паттерн позволяет избежать засорения пространства имен глобальными переменными, в которых хранятся уникальные экземпляры.

4) Реализуется одиночка обычно с помощью статического метода класса, который имеет доступ к переменной, хранящей уникальный экземпляр, и гарантирует инициализацию переменной этим экземпляром перед возвратом ее клиенту.

 

Наиболее часто встречающуюся реализацию паттерна Singleton.

 

// Singleton.h

Class Singleton

{

private:

static Singleton * p_instance;

//другие данные

// Конструкторы и оператор присваивания недоступныклиентам

Singleton() {}

Singleton(const Singleton&); 

Singleton& operator=(Singleton&);

 

 public:

static Singleton * getInstance() {

   if(!p_instance)          

       p_instance = new Singleton();

   return p_instance;

}

//другие методы

};

 

// Singleton.cpp

#include "Singleton.h"

 

Singleton* Singleton::p_instance = 0;

    - Конструктор объявлен приватным, то есть его можно вызвать только из методов класса, в том числе из статических до создания объектов класса.

    - То есть, чтобы создать объект класса мы не создаем его экземпляр, а «запрашиваем» его.

- Вызывая статический метод Singleton::getInstance(), вы получите экземпляр, готовый к работе.

- Может оказаться, что к этому моменту уже есть экземпляр, и если его нет, то только в этом случае он создастся.

- То есть происходит запрос к самому классу.

- Кроме того, паттерн предоставляет глобальную точку доступа к экземпляру: обратившись с запросом к классу в любой точке программы, вы получаете ссылку на единственный экземпляр.

- Возможно отложенное создание экземпляра, что особенно важно для объектов, создание которых сопряжено с большими затратами ресурсов.

Клиенты запрашивают единственный объект класса через статическую функцию-член getInstance(), которая при первом запросе динамически выделяет память под этот объект и затем возвращает указатель на этот участок памяти.

В последствии клиенты должны сами позаботиться об освобождении памяти при помощи оператора delete.

 

Последняя особенность является серьезным недостатком классической реализации шаблона Singleton. Так как класс сам контролирует создание единственного объекта, было бы логичным возложить на него ответственность и за разрушение объекта.

Этот недостаток отсутствует в реализации Singleton, впервые предложенной Скоттом Мэйерсом.

// simpleclass.h class SimpleClass { public: int get() const; void set(int val); static SimpleClass& instance(); private: SimpleClass(int val); SimpleClass(); int m_val; }; //simpleclass.cpp SimpleClass& SimpleClass::instance() { static SimpleClass instance; return instance; }

 

Class OnlyOne

{

public:

   static const OnlyOne& Instance()

   {

           static OnlyOne theSingleInstance;

           return theSingleInstance;

   }

Private:       

   OnlyOne(){}

   OnlyOne(const OnlyOne& root);

   OnlyOne& operator=(const OnlyOne&);

};

 

Классическая реализация паттерна Singleton может быть улучшена.

 

// Singleton.h

Class Singleton; // опережающее объявление

 

Class SingletonDestroyer

{

private:

Singleton* p_instance;

public:   

~SingletonDestroyer();

void initialize(Singleton* p);

};

 

Class Singleton

{

private:

static Singleton* p_instance;

static SingletonDestroyer destroyer;

protected:

Singleton() { }

Singleton(const Singleton&);

Singleton& operator=(Singleton&);

~Singleton() { }

friend class SingletonDestroyer;

public:

static Singleton& getInstance();   

};

 

// Singleton.cpp

#include "Singleton.h"

 

Singleton * Singleton::p_instance = 0;

SingletonDestroyer Singleton::destroyer;

 

SingletonDestroyer::~SingletonDestroyer() {  

delete p_instance;

}

void SingletonDestroyer::initialize(Singleton* p) {

p_instance = p;

}

Singleton& Singleton::getInstance() {

if(!p_instance) {

   p_instance = new Singleton();

   destroyer.initialize(p_instance);    

}

return *p_instance;

}

Ключевой особенностью этой реализации является наличие класса SingletonDestroyer, предназначенного для автоматического разрушения объекта Singleton.

Класс Singleton имеет статический член SingletonDestroyer, который инициализируется при первом вызове Singleton::getInstance() создаваемым объектом Singleton.

При завершении программы этот объект будет автоматически разрушен деструктором SingletonDestroyer (для этого S ingletonDestroye r объявлен другом класса Singleton).

 

Для предотвращения случайного удаления пользователями объекта класса Singleton, деструктор теперь уже не является общедоступным как ранее. Он объявлен защищенным.

 

/*Использование нескольких взаимозависимых одиночек

 

До сих пор предполагалось, что в программе используется один одиночка либо несколько несвязанных между собой. При использовании взаимосвязанных одиночек появляются новые вопросы:

 

Как гарантировать, что к моменту использования одного одиночки, экземпляр другого зависимого уже создан?

Как обеспечить возможность безопасного использования одного одиночки другим при завершении программы? Другими словами, как гарантировать, что в момент разрушения первого одиночки в его деструкторе еще возможно использование второго зависимого одиночки (то есть второй одиночка к этому моменту еще не разрушен)?

Управлять порядком создания одиночек относительно просто. Следующий код демонстрирует один из возможных методов.

 

// Singleton.h

Class Singleton1

{

private:

Singleton1() { }

Singleton1(const Singleton1&); 

Singleton1& operator=(Singleton1&);

public:

static Singleton1& getInstance() {

   static Singleton1 instance;

   return instance;

}   

};

 

Class Singleton2

{

private:    

Singleton2(Singleton1& instance): s1(instance) { }

Singleton2(const Singleton2&); 

Singleton2& operator=(Singleton2&);

Singleton1& s1;

public:

static Singleton2& getInstance() {

   static Singleton2 instance(Singleton1::getInstance());

   return instance;

}   

};

 

// main.cpp

#include "Singleton.h"

 

Int main()

{

Singleton2& s = Singleton2::getInstance(); 

return 0;

}

Объект Singleton1 гарантированно инициализируется раньше объекта Singleton2, так как в момент создания объекта Singleton2 происходит вызов Singleton1::getInstance().

 

Гораздо сложнее управлять временем жизни одиночек. Существует несколько способов это сделать, каждый из них обладает своими достоинствами и недостатками и заслуживают отдельного рассмотрения. Обсуждение этой непростой темы остается за рамками лекции.

*/

Несмотря на кажущуюся простоту паттерна Singleton (используется всего один класс), его реализация не является тривиальной.

 

Например, если у нас не однопоточное приложение могу возникнуть гонки потоков.  

При обращении к Singleton гонки связаны с тем, что если объект еще не был создан и 2 потока одновременно выполнят операцию instance, то может быть создано несколько одиночек.

 

SimpleClass* SimpleClass::instance() { // 1

if(m_pinstance == 0) // 2

m_pinstance = new SimpleClass; // 3

return m_pinstance; // 4

} // 5

Так, например, первый поток может выполнить проверку условия из второй строки и быть прерван во время выполнения конструктора.

Пока первый поток отдыхает, начинается работать второй поток, который выполняет тот же код, но в связи с тем, что первый поток не выполнял инициализации m_pinstance, второй поток также вызывает конструктор SimpleClass и возвращает указатель на созданный объект.

После этого может быть активирован первый поток, который заканчивает создание своего экземпляра объекта и повторно инициализирует m_pinstance.

 

Для решения проблемы гонок можно использовать средства синхронизации потоков (мьютексы), которые в настоящее время включены в стандартную библиотеку.

 

static SimpleClass* SimpleClass::instance() {

static QMutex mutex;

if (!m_pinstance) {

mutex.lock();

if (!m_pinstance)

m_pinstance = new SimpleClass;

mutex.unlock();

}

return m_pinstance;

}

С другой стороны, у реализации Singleton, использующей статическую локальную переменную метода instance, состояния гонок потоков возникнуть не может. Это гарантируется стандартом (цитата относится к инициализации локальных статических переменных):

 


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: