C ++(Qt)で構成ファイルを操作する方法の1つ

ほとんどすべてのプロジェクトで、永続的な読み取り/書き込み構成のタスクが発生します。 この問題を解決するための既製のライブラリが多数あることは秘密ではありません。 それらのいくつかは単純であり、いくつかはもう少し使いにくいです。

プロジェクトが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. キー名がコードに明示的に記述されている場合、将来、新しい構成キーの削除/追加が困難になる状況が発生する可能性があります。 つまり このアプローチでは、ここでの問題は、コンパイル段階で無効なキーをキャッチできないことです。
  2. 問題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を呼び出すたびに、文字列を結合する必要があります。



上記の問題をすべて注意深く確認すると、結論を出すことができます。キーは文字列で表されます-これが主な問題です。 実際、 enumenum )をキーとして使用すると、上記のすべてがすぐに消えます。



列挙はもちろん便利ですが、 QSettingsにはキーパラメーターとして文字列が必要です。 つまり 列挙の値を文字列に変換できる(列挙の要素の文字列値を抽出する)メカニズムを必要とします。 たとえば、次の列挙から:

 enum Key{ One, Two, Three };
      
      





何らかの形で「1」、「2」、「3」の3行を抽出する必要があります。

残念ながら、標準のC ++ツールを使用すると、これは不可能です。 しかし、何をすべきか?

ここで、Qtはメタオブジェクトモデル、またはむしろQMetaEnum救助に来ます。 これは別のトピックなので、 QMetaEnumについては書きません。 リンクを提供できるのは、 onetwoのみです。



実装



QMetaEnumを使用すると、上記のすべての欠点を排除し、デフォルト設定を指定する機能を提供するSettingsクラスを実装できます。 Settingsクラスはシングルトンマイヤーズです。これにより、設定と使用が簡単になります。



settings.h(拡張スポイラー)
 /* 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(スポイラーの展開)
 /* 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; }
      
      





この実装では、 QSettingsクラスは、設定へのクロスプラットフォームアクセス専用に使用されます。 もちろん、必要に応じて、 QSettginsSQLiteなどの他のメカニズムに置き換えることができます。



使用例



Settingsクラスは、3つの静的メソッドのみで構成される非常にシンプルで便利なインターフェイスを提供します。

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(拡張スポイラー)
 Proxy/URI: http://proxy_uri; User: unixod;
      
      





ご覧のとおり、フォーマットはシンプルです:

[section name]/key : value;







おわりに



この設定クラスは簡単に拡張できることに注意してください。 つまり 必要に応じて、キーまたはセクションを追加 / 削除 / 名前 変更し、対応する列挙型を変更するだけです!



読者は、何らかの方法で一般的な論理を「括弧の外に」出すことが可能かどうかを尋ねるかもしれません。

回答:それは可能ですが、良くはありません。 Qtメタオブジェクトモデルはテンプレートでは機能しないため、マクロを使用する必要があり、これには既知の問題が伴います。



ビルドするときは、C ++ 11サポートを有効にすることを忘れないでください。



ご清聴ありがとうございました。 )



All Articles