Litkevich Yuriy
4.5.2008, 20:04
Подбираю шаблон проектирования для моей задачи.
Задача такая:
Есть программа типа MDI, хочу сделать в ней многопользовательскую работу, в одно время работает только один пользователь.
В зависимости от группы пользователя ему становится доступным определенный, для данной группы, набор пунктов меню.
Я это так прикинул:
1-Нужно хранить переменную, например, uid, в которой указан код текущего пользователя/группы.
2-Нужен диалог ввода имени и пароля, который будет где-нибудь искать есть ли такая пара логин/пароль. Если есть, то устанавливать найденное значение в переменной uid. Если нет - ругнутся на пользователя.
3-Основное окно программы получает от диалога результат: ОК-пользователь найден, надо обновить меню, НЕ ОК - пользователь не найден, ничего не делать.
Но я подумал, что какому нибудь еще окну может понадобится знать uid, и поэтому я думаю, что простая глобальная переменная будет неудобна, и хочу сделать класс, в котором будет реализовандиалог авторизации и прочие штуки в том числе статическая переменная uid, а в других классах динамически создавать экземпляры, если сделать этот класс как "одиночку", то uid должен быть общим для всех экземпляров.
----
Вот такие мысли в моей голове, может уже есть для подобной задачи отработаный подход, и моя мысль слишком замудренная?
Просвятите пожалуйста.
Для твоей задачи можно использовать как глобальную переменную, так и 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
4.5.2008, 21:19
Цитата(ViGOur @ 5.5.2008, 1:12)
Singleton правда "красивше". Немогу понять, что в чем именно заключается вопрос?
в этом собственно и вопрос, какой путь выбрать? да и вообще было интересно знать правильный ли я шаблон проектирования выбрал или нет.
Видимо правильно.
Litkevich Yuriy
5.5.2008, 8:54
слушай а функции InitInstance() и FreeInstance(); разве не должны быть объявлены как static, ведь они имеют дело со статической переменной?
Угу, именно так, только не потому, что статические переменные члены использует, а чтобы не создавать объект, который запрещено создавать вне класса.
Поправил.
Litkevich Yuriy
5.5.2008, 9:16
еще вопрос по теме задачи:
В голове крутится вариант без "Одиночки", т.е. класс прсто содержащий переменную uid обявленную как static, т.е. у всех классов она общая, краем уха слышал, что такой вариант чреват последствиями, но какими не знаю, может прояснишь?
Да никаких последствий вроде не должно быть.
Litkevich Yuriy
5.5.2008, 10:08
положим "одиночку" я кладу в два файла
singleton.h и
singleton.cpp, заголовочный подстегиваю к разным файлам, которым он может понадобиться, а в каком месте программы объявлять статические переменные:
CSingleton *CSingleton::m_pThis=0;
int CSingleton::m_nCounter=0;
Принципиальной разницы не вижу, я обычно это делаю в *.cpp.
Litkevich Yuriy
5.5.2008, 10:27
просто я подумал, что если в хидере будет, то он несколько раз будет скормлен компилеру, и в последствии линкер может подавиться, или не подавится?
Для этого и существуют такие директивы препороцессора как:
#ifndef _MYCLASS_H_
#define _MYCLASS_H_
class CMyClass{ ... };
#endif // _MYCLASS_H_
Litkevich Yuriy
5.5.2008, 10:42
хе, правильно хидер то один и тот же, туплю
Обычно синглетон (одиночка) без 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
5.5.2008, 11:12
а зачем вот эта строка:
Цитата(ViGOur @ 5.5.2008, 1:12)
operator=( const CSingleton&){}
Цитата(Tonal @ 5.5.2008, 15:02)
singleton_t& operator=(const singleton_t&);
, какую пользу она дает?
Andrew Selivanov
5.5.2008, 11:19
Цитата(Litkevich Yuriy @ 5.5.2008, 11:42)
хе, правильно хидер то один и тот же, туплю
Посмотри книгу Modern C++ Design: Generic Programming and Design Patterns Applied
By Andrei Alexandrescu. Там все очень хорошо про singleton-ы и прочие шаблоны расписано.
О, оказывается она у меня есть на русском
Кому надо вышлю в приват.
Litkevich Yuriy
5.5.2008, 11:25
у меня есть Эрих Гамма, там тоже есть примеры, но пока для меня это ново и требует время на переваривание, тем более, что я незнаю как лучше применить этот шаблон, толи сделать его универсальным, в отдельном модуле в виде шаблонного класса, и потом делать от него наследование, либо сделать класс только для моей задачи, но если мне опять понадобится "одиночка" писать заново.
Цитата(Litkevich Yuriy @ 5.5.2008, 12:12)
какую пользу она дает?
Этим мы просто говорим, что оператор присваивания нашего Singleton'a закрытый, чтобы небыло неправомерной раздачи ссылок на него.
Как ты мог заметить также закрытыми созданы: конструктор, деструктор, и конструктор копирования.
Litkevich Yuriy
5.5.2008, 11:45
да, про конструктор/деструктор я понял, это чтоб никто не мог создать дубликат
Litkevich Yuriy
5.5.2008, 12:29
Например, в Qt'ях я применяю такую строку:
QTextCodec::setCodecForCStrings(QTextCodec::codecForName("Windows-1251"));
Если я ни где не создаю экземпляр класса QTextCodec, то можно ли пользоватся таким вариантом в место "одиночки"?
Правильно ли я понял, что "одиночка" спасает от "случайного" создания/удаления экземпляра?
Тот пример, что ты привел, обычная статическая функция.
Класс одиночка - это класс для которого можно создать только один объект...
Litkevich Yuriy
14.5.2008, 10:29
=1=
ViGOur, у меня компилер ругается на такую строчку, из твоего варианта "одиночки":
operator=( const CSingleton&){}
говорит так:
Цитата
singleton.h:26: error: ISO C++ forbids declaration of `operator=' with no type
я его пока закоментировал и вроде проблем не испытываю, но хочется чтоб все было правильно, что нужно исправить?
=2=
Обязательно ли мои методы, не отоносящиеся к основным "одиночки" типа
InitInstance() и
FreeInstance(), помечать как
sttic?
Andrew Selivanov
14.5.2008, 11:11
Цитата(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& )
Цитата(Andrew Selivanov @ 14.5.2008, 12:11)
Попробуй вот так специфицировать
Угу, я забыл написать...
Litkevich Yuriy
14.5.2008, 11:43
тогда вот так ругается :
Цитата
singleton.h:26: warning: no return statement in function returning non-void
хоть и не ошибка, но неприятно.
LuckLess
14.5.2008, 11:44
return *this;
Litkevich Yuriy
14.5.2008, 11:46
LuckLess, да вот так:
Session& operator=(const Session&){return *this;}
вообще не ругается
LuckLess
14.5.2008, 11:56
еще такой вариант синглтона..
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
14.5.2008, 12:08
а для чего int I, она ведь неиспользуется, в шаблоне?
LuckLess
14.5.2008, 14:13
для того чтобы ты мог создать два разных синглтона, одного типа.
например у тебя есть class User; ты хочеш чтобы был скажем глобальный залогиненный юзер - пишеш
typedef LLSingleton<User> LoggedUser;
далее ты хочеш еще иметь некого глобального юзера, под которым программа имеет доступ к базе..
пишеш
typedef LLSingleton<User, 1> DbUser;
получиш два разных синглтона, которые оба возвращают User*.
Litkevich Yuriy
16.5.2008, 12:50
с каждым днем начинаю понимать пользу от единственного экземпляра класса, раньше думал: "пиши окуратно и дело с концом", а на практике выходит, что окуратность особо и не причем
Litkevich Yuriy
27.9.2009, 21:16
В итоге получилось так:
#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 //
/*!
* \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
27.9.2009, 23:17
Цитата(Влад @ 28.9.2009, 1:47)
1. конструкторы-деструкторы, которые ты хочешь скрыть, не обязательно реализовывать с пустым телом {}, достаточно просто объявить их private;
они у меня с пустым телом, т.к. в моём применении они были не нужны.
Цитата(Влад @ 28.9.2009, 1:47)
надо реализовывать Double-Check Locking.
имеется в виду для основных переменных "Одиночки" (
*p_this и
cnt)?
Цитата(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
28.9.2009, 18:45
Влад, поясни пожалуйста свой код.
И m_cs что такое?
Цитата(Litkevich Yuriy @ 28.9.2009, 19:45)
Влад, поясни пожалуйста свой код.
И m_cs что такое?
Я не Влад, но могу пояснить. Собственно,
m_cs указатель на объект одиночки!
Litkevich Yuriy
28.9.2009, 20:29
AD, указатель в его примере - m_me
Ага, этот пример кода выдран с корнем из реально работающего (уж года два как... в режиме 24x7) проекта. m_me - это как раз и есть объект-одиночка, а m_cs - это объект блокировки (под MFC это критическая секция, но, в принципе, с таким же точно успехом при минимальной модификации кода может быть и posix-ный mutex). В коде принципиально, что m_me должен быть именно volatile.
Это - просто пример реализации идиомы Double-Checked Locking, а более подробно о ней может рассказать и гугл; по-моему, тема подробно освещена у классиков типа Мейерса, Саттера.... вот только не помню, у кого точно.
Litkevich Yuriy
28.9.2009, 21:13
Цитата(Влад @ 29.9.2009, 0:46)
Double-Checked Locking
а Почему собственно
Double? вроде в каждой фукции блокировка одна
Цитата(Litkevich Yuriy @ 28.9.2009, 22:13)
а Почему собственно Double? вроде в каждой фукции блокировка одна
Идет двойная проверка. А описано подробно у Александреску! А мне спать больше надо, тогда буду внимательнее... Простите, если что!
Litkevich Yuriy
28.9.2009, 21:59
ага, уже разобрался, в Вкикпедии прочитал
Гм, ну если уж говорить о кроссплатформе, то наиболее изящным решением представляется применение boost::call_once вкупе с boost::once_flag.
Для просмотра полной версии этой страницы, пожалуйста,
пройдите по ссылке.