Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Шаблон проектирования Singleton (одиночка)
Форум на CrossPlatform.RU > Разработка > С\С++
Litkevich Yuriy
Подбираю шаблон проектирования для моей задачи.
Задача такая:
Есть программа типа MDI, хочу сделать в ней многопользовательскую работу, в одно время работает только один пользователь.
В зависимости от группы пользователя ему становится доступным определенный, для данной группы, набор пунктов меню.

Я это так прикинул:
1-Нужно хранить переменную, например, uid, в которой указан код текущего пользователя/группы.
2-Нужен диалог ввода имени и пароля, который будет где-нибудь искать есть ли такая пара логин/пароль. Если есть, то устанавливать найденное значение в переменной uid. Если нет - ругнутся на пользователя.
3-Основное окно программы получает от диалога результат: ОК-пользователь найден, надо обновить меню, НЕ ОК - пользователь не найден, ничего не делать.

Но я подумал, что какому нибудь еще окну может понадобится знать uid, и поэтому я думаю, что простая глобальная переменная будет неудобна, и хочу сделать класс, в котором будет реализовандиалог авторизации и прочие штуки в том числе статическая переменная uid, а в других классах динамически создавать экземпляры, если сделать этот класс как "одиночку", то uid должен быть общим для всех экземпляров.
----
Вот такие мысли в моей голове, может уже есть для подобной задачи отработаный подход, и моя мысль слишком замудренная?
Просвятите пожалуйста.
ViGOur
Для твоей задачи можно использовать как глобальную переменную, так и Singleton. Singleton правда "красивше". :)
Немогу понять, что в чем именно заключается вопрос? Если в реализации Singleton'а, то это достаточно просто:
class CSingleton
{
private:
   static CSingleton *m_pThis;
   static int m_nCounter;

private:
   CSingleton(){}
   CSingleton( const CSingleton &){}
   ~CSingleton(){}
   operator=( const CSingleton&){}

public:
   static CSingleton *InitInstance();
   static void FreeInstance();
};

CSingleton *CSingleton::m_pThis=0;
int CSingleton::m_nCounter=0;

CSingleton *CSingleton::InitInstance()
{
   if( !m_pThis)
      m_pThis = new CSingleton();

   m_nCounter++;
  
   return m_pThis;  
}

void CSingleton::FreeInstance()
{
   if( m_nCounter>0)
      m_nCounter--;
   else
      return;

   if( m_nCounter==0)
   {
      delete m_pThis;
      m_pThis = 0;
   }
}
Примерно так, на наличие ошибок завтра проверю, а то сейчас уже голова не варит и студии под рукой нет. :)
Litkevich Yuriy
Цитата(ViGOur @ 5.5.2008, 1:12) *
Singleton правда "красивше". Немогу понять, что в чем именно заключается вопрос?

в этом собственно и вопрос, какой путь выбрать? да и вообще было интересно знать правильный ли я шаблон проектирования выбрал или нет.
Видимо правильно.
Litkevich Yuriy
слушай а функции InitInstance() и FreeInstance(); разве не должны быть объявлены как static, ведь они имеют дело со статической переменной?
ViGOur
Угу, именно так, только не потому, что статические переменные члены использует, а чтобы не создавать объект, который запрещено создавать вне класса. :)

Поправил.
Litkevich Yuriy
еще вопрос по теме задачи:
В голове крутится вариант без "Одиночки", т.е. класс прсто содержащий переменную uid обявленную как static, т.е. у всех классов она общая, краем уха слышал, что такой вариант чреват последствиями, но какими не знаю, может прояснишь?
ViGOur
Да никаких последствий вроде не должно быть. :)
Litkevich Yuriy
положим "одиночку" я кладу в два файла singleton.h и singleton.cpp, заголовочный подстегиваю к разным файлам, которым он может понадобиться, а в каком месте программы объявлять статические переменные:
CSingleton *CSingleton::m_pThis=0;
int CSingleton::m_nCounter=0;

:blush2:
ViGOur
Принципиальной разницы не вижу, я обычно это делаю в *.cpp. :)
Litkevich Yuriy
просто я подумал, что если в хидере будет, то он несколько раз будет скормлен компилеру, и в последствии линкер может подавиться, или не подавится?
ViGOur
Для этого и существуют такие директивы препороцессора как:
#ifndef _MYCLASS_H_
#define _MYCLASS_H_

class CMyClass{ ... };

#endif // _MYCLASS_H_
;)
Litkevich Yuriy
хе, правильно хидер то один и тот же, туплю :)
Tonal
Обычно синглетон (одиночка) без FreeInstance и без подсчёта ссылок пишется.
Простейший:
struct singleton_t {
  static singleton_t& instance() {
    static singleton_t inst;
    return inst;
  }
  //Здесь пишем нужные методы
  private:
    singleton_t() {/*код инициализации*/}
    ~singleton_t() {/*код очистки*/}
    singleton_t(const singleton_t&);
    singleton_t& operator=(const singleton_t&);
};

Мне кажется, он хорошо подойдёт, т.к. те проблемы, которые он имеет, в данном случае не существенны.

Собственно проблемы:
1) Возможные гонки потоков при создании.
Решаются или более хитрым методом instance - с правильными блокировками, или принудительным созданием до запуска потоков.
2) Обращение к разрушенному объекту. Когда кто-нибудь пытается обратится к instance, когда inst уже разрушен.
Т.к. разрушается экземпляр такого одиночки после выхода из main, то такое может происходить, только если обращение идёт из деструкторов других глобальных объектов.
Решение - или запретить такие обращения, или создавать одиночку в куче и никогда не убивать (хорошо описано у Александреску).
3) Неясный порядок разрушения. Когда такой одиночка захватывает ресурсы время жизни которых завязано на другие ресурсы, неподконтрольные одиночке - например он держит открытый запрос, а конектом к базе занимается приложение.
Решение - не делать так! :)
Другое решение - предоставить метод очистки, который должен вызываться перед закрытием коннекта к базе. После закрытия, или возвращать признак ошибки, либо брасать исключение (может быть опасно если вылетит в деструкторе), либо возвращать заглушки. :)
Litkevich Yuriy
а зачем вот эта строка:
Цитата(ViGOur @ 5.5.2008, 1:12) *
operator=( const CSingleton&){}

Цитата(Tonal @ 5.5.2008, 15:02) *
singleton_t& operator=(const singleton_t&);

, какую пользу она дает?
Andrew Selivanov
Цитата(Litkevich Yuriy @ 5.5.2008, 11:42) *
хе, правильно хидер то один и тот же, туплю :)

Посмотри книгу Modern C++ Design: Generic Programming and Design Patterns Applied
By Andrei Alexandrescu. Там все очень хорошо про singleton-ы и прочие шаблоны расписано.
О, оказывается она у меня есть на русском :) Кому надо вышлю в приват.
Litkevich Yuriy
у меня есть Эрих Гамма, там тоже есть примеры, но пока для меня это ново и требует время на переваривание, тем более, что я незнаю как лучше применить этот шаблон, толи сделать его универсальным, в отдельном модуле в виде шаблонного класса, и потом делать от него наследование, либо сделать класс только для моей задачи, но если мне опять понадобится "одиночка" писать заново.
ViGOur
Цитата(Litkevich Yuriy @ 5.5.2008, 12:12) *
какую пользу она дает?
Этим мы просто говорим, что оператор присваивания нашего Singleton'a закрытый, чтобы небыло неправомерной раздачи ссылок на него.
Как ты мог заметить также закрытыми созданы: конструктор, деструктор, и конструктор копирования.
Litkevich Yuriy
да, про конструктор/деструктор я понял, это чтоб никто не мог создать дубликат
Litkevich Yuriy
Например, в Qt'ях я применяю такую строку:
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("Windows-1251"));


Если я ни где не создаю экземпляр класса QTextCodec, то можно ли пользоватся таким вариантом в место "одиночки"?
Правильно ли я понял, что "одиночка" спасает от "случайного" создания/удаления экземпляра?
ViGOur
Тот пример, что ты привел, обычная статическая функция.
Класс одиночка - это класс для которого можно создать только один объект...
Litkevich Yuriy
=1=
ViGOur, у меня компилер ругается на такую строчку, из твоего варианта "одиночки":
operator=( const CSingleton&){}

говорит так:
Цитата
singleton.h:26: error: ISO C++ forbids declaration of `operator=' with no type

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

=2=
Обязательно ли мои методы, не отоносящиеся к основным "одиночки" типа InitInstance() и FreeInstance(), помечать как sttic?
Andrew Selivanov
Цитата(Litkevich Yuriy @ 14.5.2008, 11:29) *
=1=
ViGOur, у меня компилер ругается на такую строчку, из твоего варианта "одиночки":
operator=( const CSingleton&){}

говорит так:
Цитата
singleton.h:26: error: ISO C++ forbids declaration of `operator=' with no type

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

Попробуй вот так специфицировать
CSingleton& operator=( const CSingleton& )
ViGOur
Цитата(Andrew Selivanov @ 14.5.2008, 12:11) *
Попробуй вот так специфицировать
Угу, я забыл написать... :)
Litkevich Yuriy
тогда вот так ругается :
Цитата
singleton.h:26: warning: no return statement in function returning non-void

хоть и не ошибка, но неприятно.
LuckLess
return *this;
Litkevich Yuriy
LuckLess, да вот так:
Session& operator=(const Session&){return *this;}

вообще не ругается
LuckLess
еще такой вариант синглтона..
template <class T, int I = 0>
class LLSingleton
   {
private:
   LLSingleton ();
   LLSingleton (const LLSingleton&);
   LLSingleton& operator= (const LLSingleton&);
public:
   static T* Get ()
      {
      static T inst_;
      return &inst_;
      }
   };

typedef LLSingleton<int> TInt1;
typedef LLSingleton<int, 1> TInt2;

int main ()
   {
   *TInt1::Get () = 77;
   *TInt2::Get () = 88;

   std::cout << *TInt1::Get () << " " << *TInt2::Get ();
   }
Litkevich Yuriy
а для чего int I, она ведь неиспользуется, в шаблоне?
LuckLess
для того чтобы ты мог создать два разных синглтона, одного типа.
например у тебя есть class User; ты хочеш чтобы был скажем глобальный залогиненный юзер - пишеш
typedef LLSingleton<User> LoggedUser;
далее ты хочеш еще иметь некого глобального юзера, под которым программа имеет доступ к базе..
пишеш
typedef LLSingleton<User, 1> DbUser;

получиш два разных синглтона, которые оба возвращают User*.
Litkevich Yuriy
с каждым днем начинаю понимать пользу от единственного экземпляра класса, раньше думал: "пиши окуратно и дело с концом", а на практике выходит, что окуратность особо и не причем :)
Litkevich Yuriy
В итоге получилось так:
singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

class Session
{
private:
    static Session    *p_this;
    static int        cnt;
    
private:
    Session();
    Session(const Session &){} // конструктор копирования, не использует побитовое копирование
    ~Session(){}
    Session& operator=(const Session&){return *this;}
    
public:
    static Session *InitInstance();    /*!< Создает экземпляр класса. */
    static void        FreeInstance();    /*!< Удаляет экземпляр класса. */

};

#endif //

singleton.cpp
/*!
*    \file    singleton.cpp
*    \brief    Реализация класса "Управлние сессиями".
*/
#include <QDir>
#include "singleton.h"

//определяем переменные и константы
Session *Session::p_this=0;
int Session::cnt=0;

Session::Session()
{
    // Инициализация внутренних прикладных потрохов

}

// Псевдоконструктор
Session *Session::InitInstance()
{
    if( !p_this)
    {
        p_this = new Session();
    }

    cnt++;
    return p_this;  
}

// Псевдодеструктор
void Session::FreeInstance()
{
    if (cnt>0)
        cnt--;
    else
        return;

    if (cnt==0)
    {
        delete p_this;
        p_this = 0;
    }
}

//===========================================================
//        Всякие внутренние прикладные функции
//===========================================================
Влад
Юрий, по твоей реализации (кстати, практически классической!) у меня только два замечания:
1. конструкторы-деструкторы, которые ты хочешь скрыть, не обязательно реализовывать с пустым телом {}, достаточно просто объявить их private;
2. учти, что эта реализация работает только в строго однопоточной модели! В многопоточной ты либо "влетишь", либо надо реализовывать Double-Check Locking. Это, впрочем, тоже классика.

А так ничего, вполне себе красиво!...
Litkevich Yuriy
Цитата(Влад @ 28.9.2009, 1:47) *
1. конструкторы-деструкторы, которые ты хочешь скрыть, не обязательно реализовывать с пустым телом {}, достаточно просто объявить их private;
они у меня с пустым телом, т.к. в моём применении они были не нужны.

Цитата(Влад @ 28.9.2009, 1:47) *
надо реализовывать Double-Check Locking.
имеется в виду для основных переменных "Одиночки" (*p_this и cnt)?
AD
Цитата(Litkevich Yuriy @ 28.9.2009, 0:17) *
Цитата(Влад @ 28.9.2009, 1:47) *
1. конструкторы-деструкторы, которые ты хочешь скрыть, не обязательно реализовывать с пустым телом {}, достаточно просто объявить их private;
они у меня с пустым телом, т.к. в моём применении они были не нужны.

Ну вот, раз не нужны, то можно объявлять вообще без тела!
Цитата(Litkevich Yuriy @ 28.9.2009, 0:17) *
имеется в виду для основных переменных "Одиночки" (*p_this и cnt)?

Нет. В смысле сделать так:
mutex.lock()
//<какая-то проверка>
mutex.lock()
/// что-то делается....
mutex.unlock()
mutex.unlock()

Влад
Не совсем так. Правильный (имхо) пример кода приведен ниже:
template <class T>
class Singleton
{
    .........
    
private:
    static T* volatile m_me;
};

template <class T>
T& Singleton<T>::Instance()
{
    if (!m_me)
    {
        Locker lock(m_cs);
        if (!m_me)
        {
            m_me = new T;
        }
    }
    return (*m_me);
}

template <class T>
void Singleton<T>::FreeInstance()
{
    if (m_me)
    {
        Locker lock(m_cs);
        if (m_me)
        {
            delete m_me;
            m_me = NULL;
        }
    }
}
Litkevich Yuriy
Влад, поясни пожалуйста свой код.

И m_cs что такое?
AD
Цитата(Litkevich Yuriy @ 28.9.2009, 19:45) *
Влад, поясни пожалуйста свой код.

И m_cs что такое?

Я не Влад, но могу пояснить. Собственно, m_cs указатель на объект одиночки!
Litkevich Yuriy
AD, указатель в его примере - m_me
Влад
Ага, этот пример кода выдран с корнем из реально работающего (уж года два как... в режиме 24x7) проекта. m_me - это как раз и есть объект-одиночка, а m_cs - это объект блокировки (под MFC это критическая секция, но, в принципе, с таким же точно успехом при минимальной модификации кода может быть и posix-ный mutex). В коде принципиально, что m_me должен быть именно volatile.

Это - просто пример реализации идиомы Double-Checked Locking, а более подробно о ней может рассказать и гугл; по-моему, тема подробно освещена у классиков типа Мейерса, Саттера.... вот только не помню, у кого точно.
Litkevich Yuriy
Цитата(Влад @ 29.9.2009, 0:46) *
Double-Checked Locking
а Почему собственно Double? вроде в каждой фукции блокировка одна
AD
Цитата(Litkevich Yuriy @ 28.9.2009, 22:13) *
а Почему собственно Double? вроде в каждой фукции блокировка одна

Идет двойная проверка. А описано подробно у Александреску! А мне спать больше надо, тогда буду внимательнее... Простите, если что!
Litkevich Yuriy
ага, уже разобрался, в Вкикпедии прочитал
Влад
Гм, ну если уж говорить о кроссплатформе, то наиболее изящным решением представляется применение boost::call_once вкупе с boost::once_flag.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2024 IPS, Inc.