Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Странная проблема с реализацией синглтон-класса


Автор: Alpher 5.2.2007, 17:01
Доброго времени суток, уважаемые!
Столкнулся со следующей проблемой и туплю уже пару дней:
(те вещи, которые к делу не относятся, для краткости я опустил)

Файл Singleton.h
Код

template<typename T> class Singleton
{
public:
    Singleton() { assert(!_glob); _glob = static_cast<T*>(this); }
    virtual ~Singleton() { assert(_glob); _glob = 0; }
    static T &Inst() { assert(_glob); return *_glob; }
    static T *Ref() { return _glob; }
protected:
    static T *_glob;
};


Файл LibManager.h
Код

#include "Singleton.h"

// _UT_EXPORT определен, как __declspec(dllexport)

class _UT_EXPORT LibManager : public Singleton<LibManager>
{
public:
    LibManager(String &root);
    int EnumLibraries(UT_LIB_TYPE type, StringList &libs);
    ExtLib *CreateLib(String path);

    static LibManager &Inst();
    static LibManager *Ref();
protected:
    String _root;
};


Файл LibManager.cpp
Код

#include "LibManager.h"

template<> LibManager *Singleton<LibManager>::_glob = 0;
......


Компилятор (MSVC8) выдает следующее:
warning C4661: 'LibManager *Singleton<T>::_glob' : no suitable definition provided for explicit template instantiation request

Ну и линкер, соответственно:
error LNK2001: unresolved external symbol "protected: static class PlatformManager * PlatformManager::_glob" .....

Никак не могу понять, где косяк-то? Определение для _glob ведь присутствует...

Автор: archimed7592 5.2.2007, 17:11
Цитата(Alpher @  5.2.2007,  17:01 Найти цитируемый пост)
template<> LibManager *Singleton<LibManager>::_glob = 0;
не пойму че здесь написано smile type *type::field = 0; это как?
почему бы не сделать что-то типа
Код
class S
{
public:
S *getInst ()
{
static S s;
return &s;
};
private:
S ();
~S ();
S (const S &);
operator = (const S &);
}

Автор: Alpher 5.2.2007, 17:19
Цитата(archimed7592 @  5.2.2007,  17:11 Найти цитируемый пост)
template<> LibManager *Singleton<LibManager>::_glob = 0;
не пойму че здесь написано smile type *type::field = 0; это как?


Тут написано, что статическое поле _glob, унаследованное классом LibManager от шаблонного класса Singleton, являющееся указателем на LibManager равно нулю  smile 

Только вот почему компилятор этого не замечает?  smile 

P.S. С синтаксической точки зрения тут все ок

Насчет
Код

class S
{
public:
S *getInst ()
{
static S s;
return &s;
};
private:
S ();
~S ();
S (const S &);
operator = (const S &);
}


Это, конечно, вариант, но мне нужно будет в будущем:
1) подсчитывать ссылки и иметь возможность все-таки уничтожить экземпляр
2) наследовать от этого класса кучу других

Автор: Любитель 5.2.2007, 17:44
Alpher, у тебя код честно говоря напоминает принцип - чтоб враг не понял.
По делу - почему в принципе инитить в ноль должен наследник???
Логичней вместо указателя иметь в предке протектед статик метод:
Код

static T*& getGlob()
{
    static T* glob = 0;
    retrun glob;
}


Инициализация будет выполнена при первом вызове метода (правило инициализация локальных статик-переменных).

Добавлено @ 17:47 
А то что ты хотел пишется так по идее:
Код

LibManager* LibManager::_glob = 0;

Заметь - разные вещи.

Автор: Alpher 5.2.2007, 18:23
Цитата(Любитель @  5.2.2007,  17:44 Найти цитируемый пост)
Alpher, у тебя код честно говоря напоминает принцип - чтоб враг не понял.
По делу - почему в принципе инитить в ноль должен наследник???
Логичней вместо указателя иметь в предке протектед статик метод:


Да, логичней. В случае, если наследник один.
Если их много и статический указатель на экземпляр будет реализован в предке, то (как я понимаю, т.е. IMHO smile ) они будут использовать этот единственный указатель. А мне надо, чтобы у каждого наследника он был свой, соотв. его реализация и вынесена в наследника.

Автор: Любитель 5.2.2007, 18:39
 smile Ничего подобного.

Пару примеров:
Код

class A
{ static int field; }

class B: A
{}


Где бы ты не инитил field он будет один и тот же!!!

Код

template <typename T>
class A
{ static int field; }

class B: A<B>
{}

class C: A<C>
{}


A<B> и A<C> - два разных типа. У них будут два разных field. A - это вовсе не тип, это шаблон типа. У A<B> и B будет один field, потомучто B наследует от A<B>, но у A<C> и C - другой.

Автор: Alpher 5.2.2007, 18:49
Цитата(Любитель @  5.2.2007,  18:39 Найти цитируемый пост)
A<B> и A<C> - два разных типа. У них будут два разных field. A - это вовсе не тип, это шаблон типа. У A<B> и B будет один field, потомучто B наследует от A<B>, но у A<C> и C - другой.


Виноват, совсем забыл, что родитель - шаблонный  smile 
В общем, это - выход  smile 

Но все же, почему же приведенная мной конструкция не собирается? (Уже из чисто спортивного интереса smile )
В рамках одного модуля - все ок, а когда подключаю к приложению - вопит линкер.
Не понятно, почему он вопит, т.к. в самом приложении _glob не используется и приложению должно быть фиолетово, экспортируется оно или нет...

Автор: Любитель 5.2.2007, 18:54
Я выше писал - читай внимательно. Заметь, что LibraryManager и Singleton<LibraryManager> - два разных класса. И первый у тебя нигде явно не инстансируются, посему инит его полей (хотя стати поля у них и одни и те же) невозможен.

Автор: Alpher 5.2.2007, 19:07
Цитата(Любитель @  5.2.2007,  18:54 Найти цитируемый пост)
Я выше писал - читай внимательно. Заметь, что LibraryManager и Singleton<LibraryManager> - два разных класса. И первый у тебя нигде явно не инстансируются, посему инит его полей (хотя стати поля у них и одни и те же) невозможен. 


Сейчас попробовал:

LibraryManager *LibraryManager::_glob = 0;

и

template<> LibraryManager *Singleton<LibraryManager>::_glob = 0;

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

Автор: Любитель 5.2.2007, 19:09
Не знаю тогда - без компилера не берусь гадать.

Автор: Alpher 5.2.2007, 19:15
Цитата(Любитель @  5.2.2007,  19:09 Найти цитируемый пост)
Не знаю тогда - без компилера не берусь гадать. 


Ладно, на досуге поковыряю. Если разберусь - закину причины и решение.

P.S. За совет - спасибо  smile 

Автор: Vyacheslav 5.2.2007, 19:23
А вообще весьма любопытная концепция синглтона smile 
Обычно смысл его в том, чтобы гарантировно обеспечить создание единственного экземпляра: второй просто создать невозможно. А у Вас при создании второго, а содать его можно, гарантированый вылет программы через assert при дебажной сборке и просто вылет при релизной.

Автор: Alpher 5.2.2007, 19:38
Цитата(Vyacheslav @  5.2.2007,  19:23 Найти цитируемый пост)
А вообще весьма любопытная концепция синглтона smile 
Обычно смысл его в том, чтобы гарантировно обеспечить создание единственного экземпляра: второй просто создать невозможно. А у Вас при создании второго, а содать его можно, гарантированый вылет программы через assert при дебажной сборке и просто вылет при релизной.


Конструктор/деструктор наследника пока висят в public для дебажных целей. Когда допишу класс, который будет собирать все синглтоны для использования приложением (и будет френдом для этих синглтонов), перенесу в protected  smile 

Смысл моих мытарств заключается в том, чтобы централизованно создать экземпляры различных синглтонов, а потом их использовать из любого места программы через класс-ядро. Т.е. что-то вроде:

Core::Inst().GetLibManager().doSomething();

Автор: Vyacheslav 5.2.2007, 19:53
И что это даст? smile  Все равно что-то Вы наружу для содания экземляра класса оставите. И тогда кто мешает запустить эту процедуру раньше? Это, во-первых. Во-вторых, синглтон как правило гарантирует, что он будет создан до любого первого обращения к нему.
Цитата(Alpher @  5.2.2007,  19:38 Найти цитируемый пост)
Смысл моих мытарств заключается в том, чтобы централизованно создать экземпляры различных синглтонов, а потом их использовать из любого места программы через класс-ядро. Т.е. что-то вроде:

То есть при разработке  Вашей программы нужно всегда помнить о том, что прежде чем обратиться к чему либо, нужно проделать процедуру инициализации?
Но тогда зачем это называть красивыми словом Singleton. Тогда проще класс с набором статических полей, проинициализировать их с помощью того же статического метода и обращаться  к ним через теже статические методы.  






Автор: Alpher 5.2.2007, 21:23
Цитата(Vyacheslav @  5.2.2007,  19:53 Найти цитируемый пост)
То есть при разработке  Вашей программы нужно всегда помнить о том, что прежде чем обратиться к чему либо, нужно проделать процедуру инициализации?
Но тогда зачем это называть красивыми словом Singleton. Тогда проще класс с набором статических полей, проинициализировать их с помощью того же статического метода и обращаться  к ним через теже статические методы.  


Не совсем так.

Красивое слово Singleton было выбрано, т.к. оно короче, чем PseudoSingleton smile
Инициализация экземпляров происходит единовременно при инициализации класса-ядра. Пользователь может обращаться к ним только через Core::Inst().getЧтоНибудь().doЧтоНибудь() или ЧтоНибудьКласс::Inst().doЧтоНибудь()  smile 

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

class LibManager : public Singleton<LibManager>
class PluginManager : public LibManager, public PluginFunctions
class RichPluginManager : public PluginManager, public CoolFunctions
и т.п.

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

В моем случае нужно перегрузить только Inst() и Ref(), а также определить _glob.

P.S. Согласен, без определения _glob в потомках можно обойтись  smile

Добавлено @ 21:36 
Кстати, нашел причину первоначальной проблемы  smile 

Банально, в хедере, где определяется _UT_EXPORT была опечатка и оно компилилось как __declspec(dllimport)  smile 
Сейчас конструкция, приведенная в первом посте отлично работает.

Автор: Alpher 7.2.2007, 00:47
Поковырялся на досуге.
Вот настоящий синглтон  (для Вячеслава smile )
Создается при первом обращении, доступ к _glob есть только через Inst() и Ref() (даже у самого класса smile )
Создать/уничтожить через new/delete также нельзя...

Singleton.h
Код

template<typename T> class Singleton
{
public:
    Singleton() { static T *_glob; _glob = static_cast<T*>(this); }
    virtual ~Singleton() { static T *_glob;  _glob = 0; }
    static T &Inst() { static T* _glob=0; if(!_glob) _glob = new T; return *_glob; }
// или static T &Inst() { return *Ref(); } // ;)
    static T *Ref() { static T* _glob=0; if(!_glob) _glob = new T; return _glob; }
};


SampleClass.h
Код

#include "Singleton.h"

class Sample : public Singleton<Sample>
{
 friend class Singleton<Sample>;
 public:
    doSomething();
 protected:
    Sample();
    virtual ~Sample();
};



Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)