RuNTiME
|
|
« : 27-02-2009 19:43 » |
|
Есть практическая задача, написать единый интерфейс для работы с несколькими движками баз данных. Сейчас это реализовано примерно так: class IDatabase { public: virtual void someMethod1() = 0; virtual void someMethod2() = 0; };
class CDatabase1 : public IDatabase { public: void someMethod1() { } void someMethod2() { } };
class CDatabase2 : public IDatabase { public: void someMethod1() { } void someMethod2() { } };
typedef enum _DB_TYPE { DATABASE_1, DATABASE_2 }DB_TYPE;
class CDatabase { private: static IDatabase *_inst; public: CDatabase(DB_TYPE db_type) { if( _inst == 0 ) { switch(db_type) { case DATABASE_1: _inst = new CDatabase1(); break; case DATABASE_2: _inst = new CDatabase2(); break; } } } static IDatabase *get_inst() { return _inst; } static void free_inst() { if( _inst != 0 ) { delete _inst; _inst = 0; } } }
Так вот, вопрос состоит в следующем: хотя этот код позволяет достичь поставленой задачи, но приходится освобождать память в конце программы, вызывая метод: CDatabase::free_inst();
хотелось бы от этого избавиться. Возможный вариант это объявление ссылкой переменную: static IDatabase *_inst;
в static IDatabase &_inst;
Тогда память освобождалась бы автоматически. Но как инициализировать статическую ссылку нужным классом при вызове конструктора? CDatabase::CDatabase(DB_TYPE db_type)
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #1 : 27-02-2009 19:53 » |
|
Ну первое, а зачем вообше ты используеш статическую переменную? Стоит тебе в программе создать два экземпляра данного класса и память потекла струёй. И еше вопрос, знаеш ли ты, что сушествуют деструкторы, которые срабатывают автоматически при уничтожении класса.
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
RuNTiME
|
|
« Ответ #2 : 27-02-2009 19:59 » |
|
Finch, 1.Статическая переменная применяется т.к. нужен всего один экземпляр класса на всю программу (паттерн Синглетон) 2.Создать еще один экземпляр не получится, от этого защищает условие: if( _inst == 0 ) { // _inst - статическая и при следующем вызове конструктора она уже будет отличаться от нуля switch(db_type) { case DATABASE_1: _inst = new CDatabase1(); break; case DATABASE_2: _inst = new CDatabase2(); break; } }
3.Деструктор нельзя использовать потому что если я случайно создам где нибудь в середине программы экземпляр класса CDatabase, то после выхода переменной этого класса за пределы видимости будет вызван деструктор, который удалит статический экземпляр класса, указатель на который храниться в _inst
|
|
« Последнее редактирование: 27-02-2009 20:03 от RuNTiME »
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #3 : 27-02-2009 20:22 » |
|
RuNTiME, ּSingleton строится по другой схеме. И конструктор и деструктор прячутся в private зоне. Наружу вытаскивается статический Init и Done в которых и создается один единственный класс. #include <iostream> class single { public: static single* Init(); void Done(); private: single() {std::cout << "Constructor" << std::endl;}; ~single() {std::cout << "Destructor" << std::endl;}; static single *ehid; };
single * single::ehid=NULL;
single *single::Init() { if (!ehid) ehid = new single(); return ehid; }
void single::Done() { delete this; ehid = NULL; }
int main() { single *exem1=single::Init(); single *exem2=single::Init(); exem1->Done(); return 0; }
Можеш вести счетчик вызовов и при обнулении только уничтожать класс. В том примере, что ты привел, все равно возможна течь памяти.
|
|
« Последнее редактирование: 27-02-2009 20:24 от Finch »
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
RuNTiME
|
|
« Ответ #4 : 27-02-2009 20:39 » |
|
Finch, я знаю, что можно так сделать синглетон... мой пример приводиться к твоему переносом кода из конструктора в статическую функцию Init..., но проблема сейчас не в этом, мне не хочеться вызывать функцию Done вообще как таковую.... Фишка в том, что если удастся инициализировать статическую ссылку вместо указателя нужным классом, то память будет освобождена автоматом, без вызова Done...
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
RuNTiME
|
|
« Ответ #5 : 27-02-2009 20:45 » |
|
Finch, и кстати твой пример можно реализовать еще проще, возвращая ссылку: class CSingleton { private: CSingleton() { } public: static CSingleton &inst() { static CSingleton _inst; return _inst; } void someMethod() { } };
CSingleton &c = CSingleton::inst(); c.someMethod();
И кстати еще по твоему примеру... не стоит запирать деструктор класса в private
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #6 : 27-02-2009 20:55 » |
|
RuNTiME, Когда вызывается конструктор, то идет автоматическое выделение памяти на экземпляр класса. Даже если у тебя пустой конструктор. Получается, что у тебя провисает память. Кстати, если ты сделаеш даже статическую ссылку. Экземпляр класса с интерфейсом IDatabase все равно должен где то хранится. Значит будет кушать память. С другой стороны твой пример можно решить с помошью шаблонов. include <iostream> class IDatabase { public: virtual void someMethod1() = 0; virtual void someMethod2() = 0; };
class CDatabase1 : public IDatabase { public: void someMethod1() { } void someMethod2() { } };
class CDatabase2 : public IDatabase { public: void someMethod1() { } void someMethod2() { } };
typedef enum _DB_TYPE { DATABASE_1, DATABASE_2 }DB_TYPE;
template <typename T> class CDatabase: public T { public: CDatabase() { std::cout << "Constructor" << std::endl; } ~CDatabase() { std::cout << "Destructor" << std::endl; } };
int main() { CDatabase<CDatabase1> ehid;
return 0; }
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #7 : 27-02-2009 20:58 » |
|
И кстати еще по твоему примеру... не стоит запирать деструктор класса в private
Ну шаловливые ручки программистов еше никто не отменял. Если я например использую счетчик вызовов, то будет не очень приятно, если кто либо сдуру вызовет деструктор заместо метода Done. А так компилятор на попытку delete exem1; еше на этапе компиляции покажет язык.
|
|
« Последнее редактирование: 27-02-2009 21:00 от Finch »
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
RuNTiME
|
|
« Ответ #8 : 27-02-2009 21:11 » |
|
Finch, 1.) Насчет автоматического выделения памяти в конструкторе: class CSingleton { private: CSingleton() { } public: static CSingleton &inst() { static CSingleton _inst; return _inst; } void someMethod() { } };
CSingleton &c = CSingleton::inst(); c.someMethod();
Функция inst объявлена как статическая, а это значит, что она может вызываться без создания экземпляра класса... фактически класс для этой функции является всего лишь пространством имен. Отсюда выходит следующая последовательность: 1.Вызывается inst 2.Внутри inst происходит выделение памяти под Статическую переменную _inst куда записывается 1 экземпляр класса CSingleton При дальнейших вызовах метода CSingleton::inst() выделения памяти больше происходить не будет, а это значит мы имеем 1 экземпляр класса CSingleton и никакой утечки памяти. 2.) Теперь что касается IDatabase ... по его определению видно, что это чисто виртуальный класс и переменных в этом классе нет... если ты попытаешься создать экземпляр чисто виртуального класса, то компилятор будет очень недоволен:) Под чисто виртуальные классы память не выделяется вообще, они просто служат сигналом для компилятора, что все классы наследники должны иметь в себе реализации объявленных в виртуальном классе функции. Отсюда мы НЕ имеем никакой утечки памяти на IDatabase. 3.) Насчет шаблонов, так решить можно. Но как ты себе представляешь передачу класса-шаблона в параметре функции.... это же придется указывать типы для каждой функции. Если использовать просто интерфейс то можно передавать его в параметре: void someFunction(IDatabase *db); //или void someFunction(IDatabase &db);
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #9 : 27-02-2009 21:21 » |
|
3) Посмотри как я сделал. Я наследуюсь от класса, поэтому твоя выкладка будет также хорошо работать, и компилятор даже ничего не скажет. 2) Я специяально подчеркнул Экземпляр класса с интерфейсом IDatabase
То что, нельзя создать экземпляр абстрактного класса я знаю. Но ты от него наследуюшся, значит можно создать дочернии классы. Дай точную формулировку задачи, а то мы сейчас ходим кругами, проверяя знания друг друга. Может твоя задача будет решаться намного проше и легче.
|
|
« Последнее редактирование: 27-02-2009 21:23 от Finch »
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
RuNTiME
|
|
« Ответ #10 : 27-02-2009 21:32 » |
|
Finch,По поводу шаблонов проверю как работает.... Я специяально подчеркнул Цитировать Экземпляр класса с интерфейсом IDatabase То что, нельзя создать экземпляр абстрактного класса я знаю. Но ты от него наследуюшся, значит можно создать дочернии классы.
Дочерние классы да можно, но не нужно Но чтобы они не создавались, пока в голову приходит только запереть определения служебных классов в private секцию CDatabase. Но лень потом разыменовывать: CDatabase::CDatabase1::someFunction1() { }
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
RuNTiME
|
|
« Ответ #11 : 27-02-2009 21:34 » |
|
Finch,А точную задачу я сформулировал еще в самом начале мне надо чтобы через один и тот же интерфейс можно было работать с разными объектами конкретных баз данных.... причем сделать это так, чтобы не надо было вызывать функции типа free_inst или Done
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #12 : 27-02-2009 22:13 » |
|
Скажем так, что это только как ты представляеш решение. Но не сама задача. Можно например вообше не заниматься ерундой, а сделать обычный конструктор, обычную (не static) переменную указатель на экземляр класса баз данных. В конструкторе создавать базу, в деструкторе удалять. И будет счастье. Ты начинаеш переводить все в static обзывая это singleton. Не расказывая, для каких целей ты это делаеш. Поэтому наверно очень сложно найти подходяшее решение.
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
RuNTiME
|
|
« Ответ #13 : 27-02-2009 22:26 » |
|
Finch, Не расказывая, для каких целей ты это делаеш. Хорошо... рассказываю: Программа соединяется с БД один раз при старте, далее по всей программе должно использоваться это подключение, по этому static... Если делать как говоришь ты, создавать в конструкторе и удалять в деструкторе, то получается каждый раз программа будет переподключаться к БД... А это не совсем хорошо. И еще один момент: У меня существует соответсвующий класc CQuery для исполнения SQL запросов. Этот класс тоже использует созданное подключение к БД. И когда оно статическое, становиться очень удобно выполнять запросы, все сводится примерно к этому: CQuery query( "CREATE TABLE login(" "ID INTEGER PRIMARY KEY AUTOINCREMENT," "AUTH_DATA CHAR(32) UNIQUE NOT NULL," "REG_TIME BIGINT," "LAST_AUTH BIGINT);" ); return query.exec();
отпадает даже надобность передавать в пареметре query класс CDatabase, что весьма удобно. Что касается реализации нескольких БД.... хотелось бы научить программу работать с несколькими базами данных через один интерфейс... к примеру SqLite, MySQL, PgSQL и все через один класс.... Чтобы не переписывать код программы под API каждой конкретной СУБД.
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #14 : 27-02-2009 23:06 » |
|
Ну экземпляр класса нужно где то хранить, если правда ты весь класс не сделаеш static, мое предложение создавать его только один раз в main примерно так #include <iostream>
class IDatabase { public: virtual void someMethod1() = 0; virtual void someMethod2() = 0; };
class CDatabase1 : public IDatabase { public: void someMethod1() { } void someMethod2() { } };
class CDatabase2 : public IDatabase { public: void someMethod1() { } void someMethod2() { } };
template <typename T> class CDatabase: public T { typedef CDatabase<T> Database; public: CDatabase() { std::cout << "Constructor" << std::endl; if (_inst) throw 0; _inst = this; } ~CDatabase() { std::cout << "Destructor" << std::endl; _inst = NULL; } static Database *get_inst() {return _inst;} private: static Database *_inst; }; template <typename T> CDatabase<T> *CDatabase<T>::_inst=NULL;
int main() { CDatabase<CDatabase1> ehid;
return 0; }
Заметь, повторно класс с таким же интерфейсом базы данных создать нельзя, так как сработает исключение. И ты получаеш свою любимую функцию get_inst() .
|
|
« Последнее редактирование: 27-02-2009 23:08 от Finch »
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
RuNTiME
|
|
« Ответ #15 : 27-02-2009 23:19 » |
|
Finch, Вот! Это уже похоже на то, что мне нужно! Завтра попробую реализовать...
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #16 : 27-02-2009 23:28 » |
|
А нет, вру. Будет лажа. Лучше шаблон убрать. typedef enum _DB_TYPE { DATABASE_1, DATABASE_2 }DB_TYPE;
class CDatabase { public: CDatabase() { std::cout << "Constructor" << std::endl; if (_inst) throw 0; switch(db_type) { case DATABASE_1: _inst = new CDatabase1(); break; case DATABASE_2: _inst = new CDatabase2(); break; } }
~CDatabase() { std::cout << "Destructor" << std::endl; delete _inst; _inst = NULL; } static IDatabase *get_inst() {return _inst;} private: static IDatabase *_inst; };
IDatabase *CDatabase::_inst=NULL;
Я конечно не вижу полной картины твоей программы. Исхожу из того что ты сказал. 1) Держать все время открытым соединение с базой данных не рекомендуют. Соединение делается только по мере надобности. Так ты во первых уменьшаеш нагрузку на сервер баз данных, во вторых держиш соединение в актуальном состоянии. 2) Так сильно сцеплять классы между собой также не рекомендуют. Классы должны выражать собой черные яшики. Известно что входит и выходит. Но что внутри, не известно. Также минимальная зависимость от внешних факторов. Таким образом достигается почти безболезненая переносимость кода. И возможность расширения системы.
|
|
« Последнее редактирование: 28-02-2009 06:33 от Finch »
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
RuNTiME
|
|
« Ответ #17 : 28-02-2009 08:32 » |
|
Finch, Да пожалуй без шаблонов выглядит лучше Что касается подключения, то основной БД в программе будет SqLite, а она вообще встраеваемая и не имеет сервера. Да и можно ввести еще одну статическую функцию dbClose(); чтобы иметь возможность закрывать БД, но только там где это необходимо.
|
|
« Последнее редактирование: 28-02-2009 08:37 от RuNTiME »
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #18 : 28-02-2009 08:37 » |
|
Реализуйте модуль DatabaseConnectionModule, состоящий из заголовочного файла - интерфейса модуля, и файла реализации - внутренней части модуля, недоступной другим пользователям.
Создайте класс DatabaseConnectionProvider со статическим методом getConnection и закрытыми конструкторами и деструкторами. Он будет отвечать за доступность соединения во всей программе. Соединение будет описываться классом IDatabaseConnection, но внутри класса должно быть выбран определённый класс DatabaseConnection для создания экземпляра.
Создайте класс IDatabaseСonnection с открытыми абстрактными методами.
Объявите вышеперечисленные классы в заголовочном файле.
Создайте разные классы DatabaseConnection, открыто наследующие IDatabaseConnection, реализуйте в них абстрактные методы.
Эти классы не объявляйте в заголовочном файле, чтобы другие пользователи не получили к ним доступ.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
RuNTiME
|
|
« Ответ #19 : 28-02-2009 08:41 » |
|
dimka, все реализовано точно так как ты и пишешь, за исключением объявления служебных классов в .h файле... мысль хорошая убрать их оттуда так и поступим.
|
|
|
Записан
|
Любимая игрушка - debugger ...
|
|
|
|