Russian Qt Forum

Программирование => С/C++ => Тема начата: Fregloin от Март 11, 2015, 19:10



Название: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Fregloin от Март 11, 2015, 19:10
Привет. Задача такая, есть объекты, унаследованные от QObject и еще нескольких классов (множественное наследование). Хочется сделать из этих объектов синглтоны. По сути каждый объект - это менеджер отвечающий за определенную часть работы программы. Менеджеров куча, к ним нужен доступ с разных модулей (библиотек). Раньше приходилось извращаться, делать отдельный объект, который хранил указатели на все остальные менеджеры и возвращал их по имени, а далее через dynamic_cast кастилось к нужным типам на месте. Т.к. данные менеджеры по сути в единичном экземпляре используются, есть резон их сделать синглтонами. Раньше простые синглтоны я использовал нормально, но вот с QObject ругается.

привожу код сингтона (нагуглил)
Код:
template <class T>
class CORESHARED_EXPORT CSingleton
{
public:
    static T& instance();
    void    free();
protected:
    virtual ~CSingleton();
    CSingleton();
private:
    static  T* _instance;
    static  int _refCount;
};

template <class T> T * CSingleton<T>::_instance = NULL;
template <class T> int CSingleton<T>::_refCount = 0;

template <class T>
CSingleton<T>::CSingleton()
{

}

template <class T>
CSingleton<T>::~CSingleton()
{
    _instance = NULL;
}

template <class T>
T &CSingleton<T>::instance()
{
    if(!_instance)
        _instance = new T;
    _refCount++;
    return  _instance;
}

template <class T>
void CSingleton<T>::free()
{
    if(--_refCount==0)
        delete this;
}

И вот есть такой класс
Код:
class CORESHARED_EXPORT CObjectStorageManager : //public  CSingleton<CObjectStorageManager>,
                                                        public  CManagerInterface,
                                                        public  CXMLDocumentItemInterface,
                                                        public  CMMNCadImportInterface
{
Q_OBJECT
public:
    explicit CObjectStorageManager(QObject *parent = 0);
    ~CRailObjectStorageManager();
};....

Если раскоментировать строку
Код:
//public  CSingleton<CObjectStorageManager>,
то получаем вот аткие ошибки

cobjectstoragemanager.cpp:26: ошибка: undefined reference to `CSingleton<CObjectStorageManager>::CSingleton()'
cobjectstoragemanager.cpp:26: ошибка: undefined reference to `CSingleton<CObjectStorageManager>::~CSingleton()'


Подскажите как грамотно сделать шаблон синглтона и применять его к QObject?


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Johnik от Март 11, 2015, 23:48
в Qt 5 есть макрос: Q_GLOBAL_STATIC (http://doc.qt.io/qt-5/qglobalstatic.html#Q_GLOBAL_STATIC)
в Qt 4 он тоже есть, но там недокументирован.


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Old от Март 12, 2015, 10:28
Раньше приходилось извращаться, делать отдельный объект, который хранил указатели на все остальные менеджеры и возвращал их по имени, а далее через dynamic_cast кастилось к нужным типам на месте.
Как правило, с опытом, люди идут обратным путем: от плохих синглетонов к контекстам. :)
Вы решили пойти наоборот?


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Fregloin от Март 12, 2015, 13:54
Я конечно понимаю что пошёл в сторону говнокода, но пока не понимаю как сделать иначе. Если описать суть задачи то нужно следующее:

Есть библиотека, назвем ее core.dll, в которой есть несколько объектов-менеджеров, которые выполняют свой функционал. Эти менеджеры должны знать друг о друге.
Есть еще несколько подгружаемых либ, в которых могут быть свои менеджеры и юзаться менеджеры из core.dll. Так вот как сделать так, что бы не тянуть за собой кучу указателей.

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

Вот например описание класса одного из менеджеров (оставил только то что нужно)

Код:
class CORESHARED_EXPORT CObjectStorageManager : public  CManagerInterface,
                                                        public  CXMLDocumentItemInterface,
                                                        public  CMMNCadImportInterface
{
Q_OBJECT

CObjectFactoryLoader                       *   fmodelLoader; //указатель на другой менеджер, который будет использоваться этим

protected:

    void        enumerateEntities();
    void        detachEntities();

public:

    static  const   QString     SHARED_SPACE_NAME;
};

Код:

const   QString CObjectStorageManager::SHARED_SPACE_NAME = "storage_manager"; //это ключ, по которому будем искать указатель на этот менеджер

CObjectStorageManager::CObjectStorageManager(QObject *parent) :
    CManagerInterface(parent),
    CXMLDocumentItemInterface(),
    CMMNCadImportInterface(),
    fmodelLoader(NULL)
{

}

void CObjectStorageManager::enumerateEntities() //находим нужные нам менеджеры
{
    fmodelLoader = dynamic_cast<CObjectFactoryLoader*>(getSharedInstance(CObjectFactoryLoader::SHARED_SPACE_NAME));
}

void CObjectStorageManager::detachEntities()
{
    fmodelLoader = NULL;
}

Вот в программе происходит регистрация объектов (увы пока создаются не в либах, а в самой программе,да да говнокодом пахнет)
Код:
//конструктор главного окна
fsharedSpace->registerObject(CObjectStorageManager::SHARED_SPACE_NAME,fstorageManager);
fsharedSpace->registerObject(CObjectFactoryLoader::SHARED_SPACE_NAME,fmodelLoader);

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


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Johnik от Март 12, 2015, 14:02
Я у себя сделал так:
Объект ядра умеет только искать модули, загружать/выгружать, запускать/останавливать.
Ядро при запуске модуля, предоставляет модулям свой интерфейс, через который модуль может запросить интерфейс другого модуля по его имени.




Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Fregloin от Март 12, 2015, 14:49
смотрю на шаблоны Mediator и Multiton - вот мне нужно нечто среднее..


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Igors от Март 12, 2015, 15:03
A если незатейливо
Код
C++ (Qt)
struct CBaseManager;
typedef CBaseManager * (*TCreateFunc)( void );
 
struct CBaseManager {
 static  bool AddManager( const QString & name,  TCreateFunc func );
 static  CBaseManager * GetManagerByName( const QString & name );
 
 virtual QString GetName( void ) const = 0;
 void Release( void );
 
private:
 ~CBaseManager( void );
 QAtomicInt mNumRef;
 
 typedef QPair <CBaseManager *, TCreateFunc> TPair;
 static QMap <QString, TPair> mMap;
};
 


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Fregloin от Март 12, 2015, 15:37
в целом это повторяет мой код. забыл уточнить, в этом "реестре" могут храниться не только менеджеры, но и объекты наследованные от других классов.. вот собственно это все и усложняет немного, поэтому приходится прибегать к dynamic_cast


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Igors от Март 12, 2015, 17:43
Если обязуетесь наследоваться от QObject и регистрироваться (Q_DECLARE_METATYPE). то можно избежать забот с "Create""
Код
C++ (Qt)
#include <QtWidgets>
 
QHash <int, QPointer<QObject> > theHash;
 
template <class T>
QSharedPointer<T> AcquireResource( void )
{
T * ptr = 0;
int id = qMetaTypeId<T> ();
QObject * obj = theHash.value(id);
if (obj)
ptr = dynamic_cast<T *> (obj);
else {
ptr = static_cast <T *> (QMetaType::create(id));
theHash[id] = ptr;
}
return QSharedPointer<T> (ptr);
}
 
//-------------------- test ------------------------
 
struct CTest : public QObject {
CTest( void ) : mA(5)
{
qDebug() << "construct";
}
 
CTest( const CTest & src )
{
mA = src.mA;
qDebug() << "construct copy";
}
 
  ~CTest( void )
{
qDebug() << "destroy";
}
 
int mA;
};
 
Q_DECLARE_METATYPE(CTest)
 
int main(int, char **)
{
for (int i = 0; i < 2; ++i) {
QSharedPointer<CTest> p = AcquireResource<CTest>();
qDebug() << p->mA;
}
 
return 0;
}
 

Edit: это неверно, будет работать только для одного клиента, когда он избавится от своего QSharedPointer - удалится и объект


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Johnik от Март 12, 2015, 17:54
странно выглядят эти два условия:
наследоваться от QObject и регистрироваться (Q_DECLARE_METATYPE).
при том, что QObject не должен иметь конструктора копирования и оператора =, а классы регистрируемые Q_DECLARE_METATYPE должны их иметь.


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Old от Март 12, 2015, 19:04
забыл уточнить, в этом "реестре" могут храниться не только менеджеры, но и объекты наследованные от других классов..
наследников CSharedObjectInterface?

вот собственно это все и усложняет немного, поэтому приходится прибегать к dynamic_cast
Скройте это в методе получения указателя на объект.
Добавьте метод, который будет по типу и имени возвращать объекты:
Код
C++ (Qt)
Object *o1 = manager.instance<Object>(); // Вернет указатель на первый попавшийся объект класса Object или его наследников.
Object *o2 = manager.instance<Object>( "Obj2" ); // Тоже, только с именем Obj2
Storage *store = manager.instance<Storage>();
 


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: lit-uriy от Март 12, 2015, 19:25
QObject (точнее мета-объектный компилятор) и шаблонный класс - несовместимы.

Подробнее о ситуации и способах обхода: Academic Solutions to Academic Problems (http://www.wiki.crossplatform.ru/index.php/Academic_Solutions_to_Academic_Problems)


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Igors от Март 13, 2015, 05:19
странно выглядят эти два условия:
наследоваться от QObject и регистрироваться (Q_DECLARE_METATYPE).
при том, что QObject не должен иметь конструктора копирования и оператора =, а классы регистрируемые Q_DECLARE_METATYPE должны их иметь.
Да, для каких-то классов конструктор копирования может быть private. И вообще чувствуется что metaType здесь избыточен, требуются только new и delete


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Igors от Март 13, 2015, 06:11
Вроде все получается. Есть одно "не очень академическое" приведение, ну ничего, знатоки подправят  :)
Код
C++ (Qt)
#include <QtWidgets>
 
QHash <const char *, QWeakPointer <int> > theHash;
 
template <class T>
QSharedPointer<T> AcquireResource( void )
{
const char * key = typeid(T).name();
 
typedef QWeakPointer <T> TWeakPtr;
typedef QSharedPointer <T> TSharedPtr;
 
TWeakPtr & weakPtr = (TWeakPtr &) theHash[key];
TSharedPtr sharedPtr = weakPtr.toStrongRef();
if (sharedPtr.isNull()) {
sharedPtr = TSharedPtr(new T);
weakPtr = sharedPtr;
}
return sharedPtr;
}
 
//-------------------- test ------------------------
 
struct CTest {
CTest( void ) : mA(5) { qDebug() << "construct CTest"; }
~CTest( void ) { qDebug() << "destroy CTest"; }
 
int mA;
};
 
struct CTest2 : public QObject {
CTest2( void ) : mB(6) { qDebug() << "construct CTest2"; }
~CTest2( void ) { qDebug() << "destroy CTest2"; }
 
int mB;
};
 
int main(int, char **)
{
if (1) {
QSharedPointer<CTest2> test = AcquireResource<CTest2>();
test->mB = 7;
qDebug() << "test" << test.data() << test->mB;
 
for (int i = 0; i < 2; ++i) {
QSharedPointer<CTest> p = AcquireResource<CTest>();
qDebug() << "p" << p.data() << p->mA;
 
QSharedPointer<CTest2> p2 = AcquireResource<CTest2>();
qDebug() << "p2" << p2.data() << p2->mB;
}
}
 
return 0;
}
 


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Old от Март 13, 2015, 06:28
Не видно, кто в хеш значения кладет.


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Igors от Март 13, 2015, 06:53
Не видно, кто в хеш значения кладет.
оператор [] (theHash[key])


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Old от Март 13, 2015, 06:59
Не видно, кто в хеш значения кладет.
оператор [] (theHash[key])
Увидел. На ссылку не обратил внимание.


Название: Re: Сделать из QObjectов синглтоны (по шаблону)
Отправлено: Fregloin от Март 25, 2015, 13:52
Добавлю вопрос, но уже немного в другой стези.

Вспомним про классы-менеджеры которые я писал. Они представлены у меня синглтонами. Хочу добавить в них подсчёт ссылок, что бы каждый менеджер удалялся, после того как не нужен.
Дело в том что некоторые менеджеры используют другие менеджеры. И по сути они не освободятся никогда, так как ссылки висят ненулевые. Как быть? Пока что подсчет ссылок QSharedPointer я добавил только для одного менеджера - диспетчер сообщений.

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

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