unixod
Практически в каждом проекте, встает задача персистентного чтения/записи конфигурации. Не секрет что существует большое количество уже готовых библиотек для решения этой задачи. Некоторые из-них просты, некоторые чуть сложнее в использовании.
Если же проект разрабатывается с использованием Qt, думаю нет смысла линковать дополнительную библиотеку, так как в Qt есть все средства для создания очень простого, гибкого и кроссплатформенного решения.
Как раз о таком решении хочу расказать вам в этом посте.
Введение
В Qt есть очень удобный класс
QSettings . В принципе он очень прост в использовании:
/*
main.cpp
*/
int main(int argc, char *argv[]){
// эти настройки используются (неявно) классом QSettgins для
// определения имени и местоположения конфига
QCoreApplication::setOrganizationName("org");
QCoreApplication::setApplicationName("app");
...
return 0;
}
/*
some.cpp
*/
void func(){
QSettings conf;
...
// запись в конфиг
conf.setValue("section1/key1", someData); // запись в секцию section1
conf.setValue("key2", someData2); // запись в секцию General
...
// чтение из конфига
QString strData = conf.value("section1/key1").toString();
}
Из приведенного выше примера, обычного использования QSettings , сразу становятся видны проблемы расширяемости и поддержки кода:
- Если имена ключей прописывать явно в коде, то в дальнейшем мы можем столкнуться с ситуацией когда будет сложно удалять/добавлять новые ключи конфигурации. Т.е. при таком подходе, тут проблема в том что на этапе компиляции невозможно выловить инвалидные ключи.
- Чтобы избежать проблемы #1 мы могли бы выписать все ключи в отдельный заголовочный файл, и обращаться к ним через строковые константы. Для улучшения модульности кода и очистки глобальной области видимости, также стоило бы поместить все ключи в отдельное пространство имен.
namespace Settings{
const char * const key1 = "key1";
const char * const section1_key1 = "section1/key1";
const char * const section1_key2 = "section1/key2";
}
Но тут у нас появляется другая не очень приятная деталь:
* во первых слишком многословно, т.е. информация дублируется (key1 -> "key1", и т.д.). В принципе это не удивительно, так как мы же как-то должны описать сериализацию имен ключей. Да мы могли бы написать макрос, но, по известным причинам, макросы стоит избегать, тем более если есть альтернативные варианты.
* во вторых при достаточном количестве ключей и секций, велика вероятность, что придется прописывать константы для всех комбинаций, что не очень удобно. Конечно же мы можем завести константы для ключей и для секций отдельно, но тогда, при каждом обращении в QSettings , придется производить объединение строк.
Если внимательно еще раз просмотреть все вышеописанные проблемы, то можно сделать вывод: ключ представлен строкой - это и есть основная проблема. Ведь действительно, если в качестве ключа мы будем использовать перечисления (
enums), то все вышеперечисленное разом улетучивается.
Перечисления конечно же удобны, но QSettings требует, в качестве параметра ключа - строку. Т.е. нам нужен некоторый механизм, который давал бы нам возможность транслировать значения перечислений в строки (извлекать строковые значения элементов перечислений). Например из следующего перечисления:
enum Key{
One,
Two,
Three
};
нужно как-то извлечь 3 строки: "One", "Two", "Three".
К сожалению стандартными средствами C++ это сделать невозможно. Но как же быть?
Тут нам на помощь приходит Qt со своей метаобъектной моделью, а если точнее QMetaEnum . Про QMetaEnum писать не буду, так как это уже отдельная тема. Могу лишь дать ссылки:
раз,
два.
Реализация
Имея на вооружении QMetaEnum , теперь мы можем реализовать класс
Settings, лишенный всех вышеперечисленных недостатков, а также предоставляющий возможность задания дефолтных настроек. Класс
Settings представляет из себя синглтон Мейерса, это нам дает простоту настройки и его использования:
settings.h (Раскрыть спойлер)
settings.cpp (Раскрыть спойлер)
В данной реализации, класс QSettings , используется исключительно для кроссплатформенного доступа к настройкам. Конечно же по желанию QSettgins может быть заменен любым другим механизмом, например SQLite .
Пример использования
Класс
Settings предоставляет очень простой и удобный интерфейс, состоящий всего из трех статических методов:
void setDefaults(const QString &str);
- установка параметров поумолчанию
QVariant get(Key, Section);
- чтение значения (секция может быть опущена)
ValueRef set(Key, Section);
- запись значения (секция может быть опущена)
/*
main.cpp
*/
#include <QtCore/QCoreApplication>
#include <QUrl>
#include <QFile>
#include "settings.h"
void doSome(){
//чтение из секции General
QString login = Settings::get(Settings::User).toString(); // login == "unixod"
QUrl proxyUrl = Settings::get(Settings::URI, Settings::Proxy).toUrl(); // http://proxy_uri
QString generalUrl = Settings::get(Settings::URI).toString(); // пусто
if(generalUrl.isEmpty())
Settings::set(Settings::URI) = "http://some_uri";
}
int main(int argc, char *argv[]){
//данные параметры используются QSettings для определения куда сохранять конфигурацию
QCoreApplication::setOrganizationName("unixod");
QCoreApplication::setApplicationName("app");
//по желанию можем установить дефолтную конфигурацию:
QFile cfgDefaults(":/config/default.cfg"); // я обычно дефолтовые настройки помещаю в ресурсы
cfgDefaults.open(QIODevice::ReadOnly);
Settings::setDefaults(cfgDefaults.readAll());
//...
doSome();
//...
return 0;
}
вот пример синтаксиса описания настроек по умолчанию:
default.cfg (Раскрыть спойлер)
как можно заметить формат - простой:
[section name]/key : value;
Заключение
Стоит заметить что данный класс
Settings легко расширяется. Т.е. при желании, добавить/удалить/переименовать какие-нибудь ключи или секции, всего лишь надо изменить соответствующий enum!
У читающего может возникнуть вопрос а нельзя ли как нибудь вынести общую логику "за скобки".
Ответ: можно но лучше не стоит. Так как метаобъектная модель Qt не работает с шаблонами, придется использовать макросы, что в свою очередь влечет за собой известные проблемы:
- Сложность отладки
- Затруднение анализа кода для IDE
- Сложность восприятия, читающим, кода
- и т.д.
При сборке не забываем включить поддержку С++11:
- GCC:
-std=с++0x
- Qt project file:
QMAKE_CXXFLAGS += -std=c++0x
Спасибо за внимание. )
Ссылки по теме