📜 ⬆️ ⬇️

One of the methods of working with configuration files in C ++ (Qt)

In virtually every project, the task of persistent reading / writing configuration arises. It is no secret that there are a large number of ready-made libraries to solve this problem. Some of them are simple, some are a bit harder to use.
If the project is developed using Qt, I think it makes no sense to link the additional library, since Qt has all the tools to create a very simple, flexible and cross-platform solution.
Just about such a decision I want to tell you in this post.

Introduction


Qt has a very convenient QSettings class. In principle, it is very easy to use:
/* 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(); } 

From the above example, the usual use of QSettings , the problems of extensibility and code support are immediately visible:
  1. If the names of the keys are prescribed explicitly in the code, then in the future we may face a situation when it will be difficult to delete / add new configuration keys. Those. with this approach, the problem here is that at the compilation stage it is impossible to catch the invalid keys.
  2. To avoid problem # 1, we could write all the keys into a separate header file, and access them via string constants. To improve the modularity of the code and clear the global scope, it would also be worth putting all the keys in a separate namespace.
     namespace Settings{ const char * const key1 = "key1"; const char * const section1_key1 = "section1/key1"; const char * const section1_key2 = "section1/key2"; } 

    But here we have another not very nice detail:
    * Firstly it’s too verbose information is duplicated (key1 -> "key1", etc.). In principle, this is not surprising, since we somehow have to describe the serialization of key names. Yes, we could write a macro, but for obvious reasons, macros should be avoided, especially if there are alternatives.
    * secondly, with a sufficient number of keys and sections, it is likely that constants will have to be written for all combinations, which is not very convenient. Of course, we can get constants for keys and sections separately, but then, each time we use QSettings , we will have to merge strings.

If you carefully review all the problems described above, then we can conclude: the key is represented by a string - this is the main problem. Indeed, if we use enumerations ( enum s) as the key, all of the above will disappear at once.

Enumerations are of course convenient, but QSettings requires a string as a key parameter. Those. we need some mechanism that would allow us to translate the values ​​of enums into strings (extract the string values ​​of the elements of enumerations). For example from the following listing:
 enum Key{ One, Two, Three }; 

you need to somehow extract 3 lines: "One", "Two", "Three".
Unfortunately, it is impossible to do this using standard C ++ tools. But how to be?
This is where Qt comes to the rescue with its meta-object model, or rather, QMetaEnum . I will not write about QMetaEnum , since this is a separate topic. I can only give references: one , two .

Implementation


Having QMetaEnum in service, we can now implement the Settings class, which is devoid of all the above disadvantages, as well as providing the ability to set default settings. The Settings class is a Meyers singleton, which gives us ease of setup and use:
')
settings.h (uncover spoiler)
 /* settings.h */ #ifndef SETTINGS_H #define SETTINGS_H #include <QVariant> #include <QSettings> #include <QMetaEnum> /** @brief      Usage: @code ... ... //  (  -  main) QApplication::setOrganizationName("Organization name"); QApplication::setApplicationName("App name"); ... ... //    (   ) Settings::setDefaults("SomeKey: value1; SomeSection/SomeKey: value2"); //  QFile f(":/defaults/config"); f.open(QIODevice::ReadOnly); Settings::setDefaults(f.readAll()); ... ... void fun(){ ... QVariant val1 = Settings::get(Settings::SomeKey); Settings::set(Settings::SomeKey) = "new val1"; ... QVariant val2 = Settings::get(Settings::SomeKey, Settings::SomeSection); Settings::set(Settings::SomeKey, Settings::SomeSection) = "new val2"; ... } @endcode */ class Settings{ Q_GADGET Q_ENUMS(Section) Q_ENUMS(Key) public: enum Section{ General, Network, Proxy }; enum Key{ URI, Port, User, Password }; class ValueRef{ public: ValueRef(Settings &st, const QString &kp) : parent(st), keyPath(kp){} ValueRef & operator = (const QVariant &d); private: Settings &parent; const QString keyPath; }; static void setDefaults(const QString &str); static QVariant get(Key, Section /*s*/ = General); static ValueRef set(Key, Section /*s*/ = General); private: QString keyPath(Section, Key); static Settings & instance(); QMetaEnum keys; QMetaEnum sections; QMap<QString, QVariant> defaults; QSettings conf; Settings(); Settings(const Settings &); Settings & operator = (const Settings &); }; #endif // SETTINGS_H 

settings.cpp (uncover spoiler)
 /* settings.cpp */ #include "settings.h" #include <QSettings> #include <QMetaEnum> #include <QRegExp> #include <QStringList> Settings::Settings(){ const QMetaObject &mo = staticMetaObject; int idx = mo.indexOfEnumerator("Key"); keys = mo.enumerator(idx); idx = mo.indexOfEnumerator("Section"); sections = mo.enumerator(idx); } QVariant Settings::get(Key k, Section s){ Settings &self = instance(); QString key = self.keyPath(s, k); return self.conf.value(key, self.defaults[key]); } Settings::ValueRef Settings::set(Key k, Section s){ Settings &self = instance(); return ValueRef(self, self.keyPath(s, k)); } void Settings::setDefaults(const QString &str){ Settings &self = instance(); //section/key : value //section - optional QRegExp rxRecord("^\\s*(((\\w+)/)?(\\w+))\\s*:\\s*([^\\s].{0,})\\b\\s*$"); auto kvs = str.split(QRegExp(";\\W*"), QString::SkipEmptyParts); //key-values for(auto kv : kvs){ if(rxRecord.indexIn(kv) != -1){ QString section = rxRecord.cap(3); QString key = rxRecord.cap(4); QString value = rxRecord.cap(5); int iKey = self.keys.keyToValue(key.toLocal8Bit().data()); if(iKey != -1){ int iSection = self.sections.keyToValue(section.toLocal8Bit().data()); if(section.isEmpty() || iSection != -1){ self.defaults[rxRecord.cap(1)] = value; } } } } } //Settings::ValueRef----------------------------------------------------------- Settings::ValueRef & Settings::ValueRef::operator = (const QVariant &data){ parent.conf.setValue(keyPath, data); return *this; } //PRIVATE METHODS-------------------------------------------------------------- QString Settings::keyPath(Section s, Key k){ auto szSection = sections.valueToKey(s); auto szKey = keys.valueToKey(k); return QString(s == General ? "%1" : "%2/%1").arg(szKey).arg(szSection); } Settings & Settings::instance(){ static Settings singleton; return singleton; } 

In this implementation, the QSettings class is used exclusively for cross-platform access to settings. Of course, if desired, QSettgins can be replaced by any other mechanism, such as SQLite .

Usage example


The Settings class provides a very simple and user-friendly interface consisting of only three static methods:
void setDefaults(const QString &str); - setting default parameters
QVariant get(Key, Section); - read value (section can be omitted)
ValueRef set(Key, Section); - write value (section can be omitted)

 /* 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; } 

Here is an example of the default description syntax:

default.cfg (Uncover spoiler)
 Proxy/URI: http://proxy_uri; User: unixod; 

As you can see the format is simple:
[section name]/key : value;

Conclusion


It is worth noting that this class Settings easily expands. Those. if you wish, add / delete / rename any keys or sections, just need to change the corresponding enum !

The reader may be asked whether it is possible to somehow endorse the general logic "beyond the brackets".
Answer: it is possible but not worth it. Since the Qt meta-object model does not work with templates, it is necessary to use macros, which in turn leads to known problems:

When building, do not forget to include support for C ++ 11:

Thanks for attention. )

Source: https://habr.com/ru/post/149085/


All Articles