QSettings::SettingsMapだけじゃない!Qt設定保存の代替手段と選び方

2025-05-27

QSettings::SettingsMap とは

QSettings::SettingsMap は、Qtフレームワークの QSettings クラス内で定義されている型エイリアス(typedef)です。これは実際には QMap<QString, QVariant> のことです。

  • QMap<QString, QVariant>: キーと値のペアを格納するためのコンテナクラスです。
    • QString: 設定項目の「キー」(名前)を表します。例えば、「ウィンドウの幅」や「ユーザー名」など、設定を一意に識別するための文字列です。
    • QVariant: 設定項目の「値」を表します。QVariant は、Qtの多くのデータ型(整数、文字列、ブール値、QSize、QRectなど)を汎用的に扱うことができる型です。これにより、異なる種類のデータを単一のコンテナに格納できます。

QSettings クラスと SettingsMap の関係

QSettings クラスは、アプリケーションの設定を永続的に保存および読み込むための、プラットフォーム非依存のインターフェースを提供します。Windowsではレジストリ、macOSではplistファイル、Unix/LinuxではINIファイルなど、OSネイティブな方法で設定を管理してくれます。

通常、QSettings を直接使用する場合、setValue() メソッドでキーと値を設定し、value() メソッドで値を読み出します。

QSettings settings("MyCompany", "MyApplication");
settings.setValue("editor/wrapMargin", 80);
int margin = settings.value("editor/wrapMargin", 68).toInt();

QSettings::SettingsMap は、主に QSettingsカスタムストレージ形式を実装する際に内部的に利用されます。

QSettings は、INIファイル形式やネイティブな設定形式以外にも、独自の形式で設定を保存・読み込みする機能を提供しています。このカスタム形式を登録する際には、QSettings::registerFormat() 関数を使用し、設定の読み込みと書き込みを行うカスタム関数を渡します。

これらのカスタム関数のシグネチャは以下のようになります。

bool readFunc(QIODevice &device, QSettings::SettingsMap &map);
bool writeFunc(QIODevice &device, const QSettings::SettingsMap &map);

ここで QSettings::SettingsMap が登場します。

  • writeFunc:

    • この関数は、QSettings クラスが内部的に持っている設定データ(map 引数として渡される)を、指定された QIODevice に書き込みます。
    • map には、アプリケーションが setValue() で設定したすべてのキーと値のペアが含まれています。
  • readFunc:

    • この関数は、指定された QIODevice (ファイルなど) から設定データを読み込みます。
    • 読み込んだデータを、QSettings::SettingsMap 型の map オブジェクトにキーと値のペアとして格納します。
    • QSettings クラスは、この map からデータを取得し、アプリケーションの他の部分で value() メソッドを通じて利用できるようにします。

つまり、QSettings::SettingsMap は、カスタムフォーマットを実装する際に、QSettings の内部的なデータ構造(キーと値のペアの集合)と、カスタム形式で保存されるデータの間の橋渡しをする役割を担っています。これにより、開発者はファイル形式の解析や生成の部分に集中でき、QSettingsが提供する便利なインターフェース(setValue(), value() など)を維持したまま、独自のストレージメカニズムを組み込むことができます。

  • カスタムの読み書き関数を実装する際に、QSettings とデータのやり取りを行うためのコンテナとして機能する。
  • 主に QSettings クラスのカスタムストレージ形式を実装する際に、設定データの内部表現として利用される。
  • QString がキー、QVariant が値を表す、設定データのキーと値のペアの集合である。
  • QSettings::SettingsMapQMap<QString, QVariant> の型エイリアスである。


QSettings::SettingsMap 自体が直接エラーの原因になることは稀ですが、これが使われるカスタム設定形式の読み書き関数(QSettings::ReadFunc および QSettings::WriteFunc)の実装において、様々な問題が発生する可能性があります。

カスタムフォーマットの読み込み/書き込み関数におけるエラー

QSettings::SettingsMap は、カスタムフォーマットの readFunc および writeFunc 関数を通じてQSettingsとデータのやり取りをします。これらの関数の実装に問題があると、設定の保存や読み込みが正しく行われません。

一般的なエラー

  • パフォーマンス問題: 巨大な設定ファイルを効率的に処理できない場合、アプリケーションの起動や終了が遅くなる可能性があります。
  • 空のキー/値: SettingsMap に空のキーや不正な値が格納され、それが原因でファイル形式が崩れたり、パースエラーが発生したりする。
  • データ型の不一致: QVariant に格納されたデータと、アプリケーションで期待されるデータ型が一致しない場合、value().toInt()value().toString() などの変換が失敗する可能性があります。特にカスタム型を QVariant に格納する場合に発生しやすいです。
  • 部分的なデータの読み込み/書き込み: readFunc がファイル全体を読み込まなかったり、writeFuncSettingsMap 内のすべてのデータを書き込まなかったりする場合、一部の設定が失われたり、正しく読み込まれなかったりします。
  • アクセス権エラー(QSettings::AccessError: writeFunc が、書き込み権限のないファイルやディレクトリに書き込もうとした場合に発生します。
  • 不正なデータ形式(QSettings::FormatError: readFunc が、予期しない形式のファイルを読み込もうとしたり、不正なデータをパースしようとしたりすると発生します。

トラブルシューティング

  • カスタム型の登録: QVariant にカスタムのデータ型を格納する場合、そのカスタム型が Q_DECLARE_METATYPE で宣言され、かつ qRegisterMetaType() および qRegisterMetaTypeStreamOperators() (または QDataStreamoperator<<, operator>> のオーバーロード) で登録されていることを確認します。これを忘れると、QVariant がカスタム型を適切にシリアライズ/デシリアライズできず、不正なデータが保存されたり、空のデータが読み込まれたりします。

    • // MyCustomType.h
      struct MyCustomType {
          int value1;
          QString value2;
          // ...
      };
      Q_DECLARE_METATYPE(MyCustomType) // QVariantに格納するために必要
      
      QDataStream &operator<<(QDataStream &out, const MyCustomType &data);
      QDataStream &operator>>(QDataStream &in, MyCustomType &data);
      
      // MyCustomType.cpp
      QDataStream &operator<<(QDataStream &out, const MyCustomType &data) {
          out << data.value1 << data.value2;
          return out;
      }
      QDataStream &operator>>(QDataStream &in, MyCustomType &data) {
          in >> data.value1 >> data.value2;
          return in;
      }
      
      // main.cpp など、アプリケーションの起動時
      qRegisterMetaType<MyCustomType>("MyCustomType"); // QVariantで型の名前を扱うために必要
      // QDataStreamのオペレーターを登録
      qRegisterMetaTypeStreamOperators<MyCustomType>("MyCustomType");
      
      もしこれらが欠けていると、QSettings::setValue()MyCustomType を保存しても正しくシリアライズされず、QSettings::value().value<MyCustomType>() で読み込んでもデフォルト値や不正なデータになってしまうことがあります。
  • テストファイルの利用: 小さな設定ファイルを作成し、それを読み書きするテストコードを書き、問題の切り分けを行います。
  • エラー処理の追加: readFunc および writeFunc の中で、ファイル読み書きのエラー(QIODevice::error())やパースエラーを適切に処理し、false を返すようにします。
  • readFunc / writeFunc のデバッグ:
    • これらの関数内にログ出力(qDebug() など)を追加し、QIODevice から読み込まれるバイトデータや、SettingsMap に格納されるキーと値のペアを詳細に確認します。
    • カスタムファイル形式の仕様と、実際に読み書きされるデータが一致しているかを検証します。
    • 特に QVariant の型変換に注意し、QVariant::type()QVariant::canConvert() を使って予期せぬ型変換が行われていないか確認します。
  • QSettings::status() の確認: QSettings オブジェクトの status() メソッドを呼び出して、現在のエラー状態を確認します。
    • QSettings::NoError: エラーなし
    • QSettings::AccessError: ファイルアクセスエラー
    • QSettings::FormatError: フォーマットエラー
    • これを確認することで、エラーの発生源がファイルアクセスか、データ形式のパースにあるのかを特定できます。

QSettings オブジェクトの初期化に関する問題

QSettings::SettingsMap は、QSettings オブジェクトが適切に初期化されて、正しいカスタムフォーマットが登録されていることを前提としています。

一般的なエラー

  • 組織名/アプリケーション名の不一致: QCoreApplication::setOrganizationName()QCoreApplication::setApplicationName() を呼び出す前に QSettings オブジェクトをデフォルトコンストラクタで作成すると、予期しない場所に設定ファイルが作成されることがあります。これは SettingsMap 自体の問題ではないですが、設定の保存場所が想定と異なるため、読み書きができないという形で現れます。
  • ファイルパスの問題: カスタムフォーマットでファイルを指定する場合、そのパスが適切でない(存在しないディレクトリ、権限がないなど)と、ファイルが作成/読み込みできません。
  • カスタムフォーマットの未登録: QSettings::registerFormat() を呼び出す前に、そのフォーマットを使用する QSettings オブジェクトを作成してしまうと、デフォルトのフォーマットが使用されてしまい、カスタムフォーマットが適用されません。
  • 組織名/アプリケーション名の設定: アプリケーションの起動時に、QCoreApplication::setOrganizationName()QCoreApplication::setApplicationName() を適切に設定します。これにより、デフォルトの QSettings コンストラクタが正しいパスで設定ファイルを扱うようになります。
  • ファイルパスの確認: QSettings コンストラクタで指定したファイルパスや、QSettings::setPath() で設定したパスが正しいことを確認します。必要に応じて QDir::mkpath() などでディレクトリを作成します。
  • registerFormat() の呼び出し順序: QCoreApplication オブジェクトが作成された後、かつカスタムフォーマットを使用する QSettings オブジェクトを作成するQSettings::registerFormat() を呼び出すようにします。通常、main 関数内でアプリケーションの初期化と同時に行います。
  • エラーメッセージの不足: カスタムフォーマットの読み書き関数内で、発生した具体的なエラー情報をQSettings に通知する標準的なメカニズムは直接提供されていません。デバッグにはログ出力が重要になります。
  • スレッドセーフティ: QSettings オブジェクトはスレッドセーフですが、カスタムの readFuncwriteFunc 関数は、複数のスレッドから同時にアクセスされる可能性がある場合、開発者が明示的にスレッドセーフティを考慮する必要があります(例: QMutex を使用するなど)。


以下に、QSettings::SettingsMap を使用したカスタム設定形式の読み書き関数の例を示します。ここでは、設定をシンプルなプレーンテキストファイルに保存する例を考えます。各設定項目は「キー=値」の形式で1行に記述され、行頭に # がある場合はコメントとみなします。

例1: プレーンテキスト形式のカスタム設定

この例では、QSettings のカスタムフォーマットとして、.txtsettings という拡張子のファイルを定義し、その読み書き関数を実装します。

main.cpp

#include <QCoreApplication>
#include <QSettings>
#include <QDebug>
#include <QMap>
#include <QStringList>
#include <QFile>
#include <QTextStream>
#include <QVariant>

// カスタム設定形式の読み込み関数
// QIODevice からデータを読み込み、QSettings::SettingsMap に格納します
bool readCustomSettings(QIODevice &device, QSettings::SettingsMap &map)
{
    QTextStream in(&device);
    in.setCodec("UTF-8"); // UTF-8エンコーディングを使用

    while (!in.atEnd()) {
        QString line = in.readLine().trimmed(); // 行を読み込み、前後の空白を削除
        if (line.isEmpty() || line.startsWith("#")) { // 空行またはコメント行はスキップ
            continue;
        }

        int equalsIndex = line.indexOf('=');
        if (equalsIndex > 0) {
            QString key = line.left(equalsIndex).trimmed();
            QString valueStr = line.mid(equalsIndex + 1).trimmed();

            // QVariant は多くの型を自動的に推論できますが、
            // ここではシンプルにQStringとして格納します。
            // 必要に応じて、ここで型変換ロジックを追加できます。
            map[key] = valueStr;
        } else {
            // フォーマットエラーとして処理することも可能ですが、ここでは無視
            qDebug() << "Warning: Invalid line format:" << line;
        }
    }
    return true; // 読み込み成功
}

// カスタム設定形式の書き込み関数
// QSettings::SettingsMap のデータを QIODevice に書き込みます
bool writeCustomSettings(QIODevice &device, const QSettings::SettingsMap &map)
{
    QTextStream out(&device);
    out.setCodec("UTF-8"); // UTF-8エンコーディングを使用

    // ヘッダーコメント
    out << "# My Custom Settings File" << "\n";
    out << "# Created by QSettings Custom Format Example" << "\n";
    out << "\n";

    // SettingsMap の内容を書き込む
    QMapIterator<QString, QVariant> i(map);
    while (i.hasNext()) {
        i.next();
        out << i.key() << "=" << i.value().toString() << "\n";
    }

    // 書き込みエラーをチェック
    return device.error() == QIODevice::NoError;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // アプリケーション情報の設定 (QSettingsで使用される)
    QCoreApplication::setOrganizationName("MyCompany");
    QCoreApplication::setApplicationName("CustomSettingsApp");

    // 1. カスタムフォーマットの登録
    // "txtsettings" という拡張子に関連付けます
    const QSettings::Format CustomTextFormat =
        QSettings::registerFormat("txtsettings", readCustomSettings, writeCustomSettings);

    if (CustomTextFormat == QSettings::InvalidFormat) {
        qDebug() << "Failed to register custom settings format!";
        return 1;
    }

    // 2. カスタムフォーマットを使用してQSettingsオブジェクトを作成
    // ファイル名を明示的に指定する場合
    QString settingsFilePath = "my_custom_settings.txtsettings";
    QSettings settings(settingsFilePath, CustomTextFormat);

    qDebug() << "Settings file path:" << settings.fileName();

    // 3. 設定値の書き込み
    qDebug() << "Writing settings...";
    settings.setValue("user/name", "Taro Yamada");
    settings.setValue("user/email", "[email protected]");
    settings.setValue("app/version", "1.0.0");
    settings.setValue("window/geometry", QRect(100, 100, 800, 600)); // QVariantはQRectも保持可能
    settings.setValue("debugMode", true);

    // 設定をファイルに同期 (明示的に呼び出すか、QSettingsのデストラクタが自動で行う)
    settings.sync();
    if (settings.status() == QSettings::NoError) {
        qDebug() << "Settings written successfully.";
    } else {
        qDebug() << "Error writing settings:" << settings.status();
    }

    // 4. 設定値の読み込み
    qDebug() << "\nReading settings...";
    QString userName = settings.value("user/name").toString();
    QString userEmail = settings.value("user/email").toString();
    QString appVersion = settings.value("app/version").toString();
    QRect windowGeometry = settings.value("window/geometry").toRect();
    bool debugMode = settings.value("debugMode").toBool(); // 文字列"true"からbool値に変換可能

    qDebug() << "User Name:" << userName;
    qDebug() << "User Email:" << userEmail;
    qDebug() << "App Version:" << appVersion;
    qDebug() << "Window Geometry:" << windowGeometry;
    qDebug() << "Debug Mode:" << debugMode;

    // 存在しないキーの読み込み(デフォルト値が返される)
    QString nonexistentKey = settings.value("nonexistent/key", "Default Value").toString();
    qDebug() << "Non-existent Key:" << nonexistentKey;

    // 5. 設定ファイルの削除 (テスト用)
    // QFile::remove(settingsFilePath);
    // qDebug() << "Settings file removed for next run.";

    return 0;
}

CMakeLists.txt (または Qmake プロジェクトファイル)

CMakeLists.txt:

cmake_minimum_required(VERSION 3.14)
project(CustomSettingsApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 REQUIRED COMPONENTS Core)

add_executable(CustomSettingsApp main.cpp)
target_link_libraries(CustomSettingsApp PRIVATE Qt6::Core)

ビルドと実行

  1. 上記コードを main.cpp として保存し、CMakeLists.txt を作成します。
  2. ターミナルでプロジェクトのルートディレクトリに移動し、ビルドディレクトリを作成して移動します。
    mkdir build
    cd build
    
  3. CMake を実行してビルドファイルを生成します。
    cmake ..
    
  4. ビルドを実行します。
    cmake --build .
    
  5. アプリケーションを実行します。
    ./CustomSettingsApp
    

出力例

Settings file path: "my_custom_settings.txtsettings"
Writing settings...
Settings written successfully.

Reading settings...
User Name: "Taro Yamada"
User Email: "[email protected]"
App Version: "1.0.0"
Window Geometry: QRect(100, 100, 800, 600)
Debug Mode: true
Non-existent Key: "Default Value"
# My Custom Settings File
# Created by QSettings Custom Format Example

user/name=Taro Yamada
user/[email protected]
app/version=1.0.0
window/geometry=QRect(100, 100, 800, 600)
debugMode=true
  1. readCustomSettings 関数:

    • QIODevice &device: 読み込み元のデバイス(通常は QFile)を受け取ります。
    • QSettings::SettingsMap &map: 読み込んだキーと値のペアを格納する QMap<QString, QVariant> です。
    • QTextStream を使ってデバイスから1行ずつ読み込みます。
    • 空行や # で始まるコメント行はスキップします。
    • key=value の形式で = の位置を特定し、キーと値を抽出して map に挿入します。
    • QVariant は多くの標準C++型やQtの型を自動的に変換できるため、ここでは文字列として読み込んだ値をそのままQVariantに格納しています。後でsettings.value().toType()で取り出す際に適切な型に変換されます。
  2. writeCustomSettings 関数:

    • QIODevice &device: 書き込み先のデバイスを受け取ります。
    • const QSettings::SettingsMap &map: 書き込むデータを含む QMap<QString, QVariant> です。const なので変更できません。
    • QTextStream を使ってデバイスに1行ずつ書き込みます。
    • map の内容をイテレータで走査し、各キーと値(toString() で文字列化)を「キー=値」の形式で書き出します。
  3. main 関数:

    • QCoreApplication の初期化と、setOrganizationName()setApplicationName() でアプリケーション情報を設定します。これは QSettings のデフォルト動作に影響しますが、この例では明示的にファイル名を指定しているため直接的な影響は少ないです。
    • QSettings::registerFormat() を呼び出し、カスタムの読み書き関数を登録します。この関数は、登録されたフォーマットの識別子(QSettings::Format 型の整数値)を返します。この識別子を使って QSettings オブジェクトを作成します。
    • QSettings settings(settingsFilePath, CustomTextFormat); のように、登録したフォーマットを指定して QSettings オブジェクトを初期化します。
    • settings.setValue() で設定値を書き込みます。QVariant は多くのQt型(QRectQSizeQPoint など)を自動的に変換して保持できるため、明示的な変換は不要です。
    • settings.sync() を呼び出して、設定をファイルに強制的に書き込みます。通常はアプリケーション終了時に自動的に書き込まれますが、安全のために明示的に呼び出すこともあります。
    • settings.value() で設定値を読み込みます。取り出す際には toString()toRect()toBool() などの適切な変換関数を使用します。


しかし、多くのQtアプリケーションでは、そこまで低レベルな設定の仕組みを自作する必要はありません。Qtは、より直接的で便利な設定管理の方法をいくつか提供しています。これらの方法は、QSettings::SettingsMapを直接扱うことなく、アプリケーションの設定を永続化できます。

代替方法は、大きく分けて以下の2つのカテゴリに分類できます。

  1. QSettingsの標準機能を使用する: ほとんどのケースで推奨される方法です。
  2. Qtが提供する他のシリアライズ機構を使用する: より複雑なデータ構造や、設定以外のデータ永続化に適しています。

QSettings の標準機能を使用する(最も一般的で推奨される方法)

QSettingsクラスは、プラットフォームネイティブな設定保存メカニズム(Windowsのレジストリ、macOSのplist、Unix/LinuxのINIファイルなど)を抽象化し、シンプルなAPIで設定を扱うことができます。通常、開発者がQSettings::SettingsMapを意識する必要はありません。

特徴

  • カスタム設定形式の登録は、特定のニーズがある場合にのみ検討すべきです。
  • 異なるデータ型(QString, int, bool, QRect, QSize など)をQVariant経由で透過的に扱える。
  • 簡単なAPI (setValue(), value()) でキーと値を操作できる。
  • OSネイティブな形式で設定を保存するため、ユーザーやシステム管理者が設定を探しやすい。

コード例

#include <QCoreApplication>
#include <QSettings>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // アプリケーション情報の設定 (QSettingsが設定ファイルの場所を決定する際に使用)
    QCoreApplication::setOrganizationName("MyCompany");
    QCoreApplication::setApplicationName("MyApp");

    // QSettingsオブジェクトの作成
    // デフォルトのコンストラクタを使用すると、OSネイティブな設定形式が使われる
    QSettings settings; // 例: Windowsならレジストリ、Linuxなら~/.config/MyCompany/MyApp.conf (INI形式)

    // 設定値の書き込み
    settings.setValue("user/name", "Alice");
    settings.setValue("app/version", 1.2);
    settings.setValue("window/geometry", QRect(100, 100, 800, 600));
    settings.setValue("flags/darkMode", true);

    // 設定値の読み込み (存在しない場合はデフォルト値を指定可能)
    QString userName = settings.value("user/name", "Guest").toString();
    double appVersion = settings.value("app/version", 1.0).toDouble();
    QRect windowGeometry = settings.value("window/geometry", QRect(0,0,640,480)).toRect();
    bool darkMode = settings.value("flags/darkMode", false).toBool();

    qDebug() << "User Name:" << userName;
    qDebug() << "App Version:" << appVersion;
    qDebug() << "Window Geometry:" << windowGeometry;
    qDebug() << "Dark Mode:" << darkMode;

    // 設定の削除
    // settings.remove("user/name"); // 特定のキーを削除
    // settings.clear(); // すべての設定を削除

    // QSettingsは通常、デストラクタで自動的に保存されるが、明示的に同期することも可能
    settings.sync();

    return 0;
}

考察
ほとんどのアプリケーションは、このQSettingsの標準機能で十分です。QSettings::SettingsMapを直接触る必要はありません。

Qtが提供する他のシリアライズ機構を使用する

より複雑なデータ構造を保存したい場合や、設定ファイルとは異なる目的でデータを永続化したい場合は、QSettings以外のQtのシリアライズ機構を使用することを検討できます。

a. QDataStream と QFile を使用する

QDataStreamは、バイナリ形式でデータを読み書きするためのクラスです。QFileと組み合わせることで、任意のバイナリファイルに複雑なデータ構造を効率的に保存できます。

特徴

  • Q_DECLARE_METATYPEqRegisterMetaTypeStreamOperatorsを使用することで、カスタム型もQDataStreamで扱えるようになる。
  • ファイル形式は開発者が完全に制御できるため、バージョンアップ時の互換性維持は開発者の責任となる。
  • 高速かつコンパクトなデータ保存が可能。
  • 任意のC++オブジェクトやQtのデータ型をバイナリ形式でシリアライズできる。

コード例

#include <QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDebug>
#include <QMap> // QSettings::SettingsMap と同様に QMap を使う例
#include <QVariant>
#include <QPoint>

// 保存するカスタムデータ型 (例として)
struct AppState {
    QString lastUser;
    int windowWidth;
    QPoint lastPosition;
};
Q_DECLARE_METATYPE(AppState) // QVariantに格納するために必要

// QDataStream で AppState を読み書きするためのオペレーターオーバーロード
QDataStream &operator<<(QDataStream &out, const AppState &data) {
    out << data.lastUser << data.windowWidth << data.lastPosition;
    return out;
}

QDataStream &operator>>(QDataStream &in, AppState &data) {
    in >> data.lastUser >> data.windowWidth >> data.lastPosition;
    return in;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // QDataStream でカスタム型を扱うための登録 (必要に応じて)
    qRegisterMetaType<AppState>("AppState");
    qRegisterMetaTypeStreamOperators<AppState>();

    QString fileName = "appdata.bin";

    // データ書き込み
    QFile outFile(fileName);
    if (outFile.open(QIODevice::WriteOnly)) {
        QDataStream out(&outFile);
        out.setVersion(QDataStream::Qt_6_0); // バージョン設定 (互換性維持のため重要)

        // QSettings::SettingsMap に似た QMap<QString, QVariant> を保存する例
        QMap<QString, QVariant> settingsMap;
        settingsMap["user/name"] = "Bob";
        settingsMap["app/theme"] = "dark";
        settingsMap["data/counter"] = 123;
        out << settingsMap;

        // カスタムデータ型を保存する例
        AppState stateToWrite = {"developer", 1024, QPoint(50, 50)};
        out << stateToWrite;

        outFile.close();
        qDebug() << "Data written to" << fileName;
    } else {
        qDebug() << "Error opening file for writing:" << outFile.errorString();
    }

    // データ読み込み
    QFile inFile(fileName);
    if (inFile.open(QIODevice::ReadOnly)) {
        QDataStream in(&inFile);
        in.setVersion(QDataStream::Qt_6_0); // 書き込み時と同じバージョンを設定

        // QMap<QString, QVariant> の読み込み
        QMap<QString, QVariant> loadedSettingsMap;
        in >> loadedSettingsMap;
        qDebug() << "\nLoaded settings map:" << loadedSettingsMap;
        qDebug() << "User name from map:" << loadedSettingsMap.value("user/name").toString();

        // カスタムデータ型の読み込み
        AppState loadedState;
        in >> loadedState;
        qDebug() << "\nLoaded AppState:";
        qDebug() << "  Last User:" << loadedState.lastUser;
        qDebug() << "  Window Width:" << loadedState.windowWidth;
        qDebug() << "  Last Position:" << loadedState.lastPosition;

        inFile.close();
        qDebug() << "Data read from" << fileName;
    } else {
        qDebug() << "Error opening file for reading:" << inFile.errorString();
    }

    // QFile::remove(fileName); // テスト後にファイルを削除

    return 0;
}

考察
QDataStreamは、アプリケーション固有のデータを独自の形式で保存する際に非常に強力です。QSettings::SettingsMapのようにキーと値のペアを保存するだけでなく、任意の複雑なオブジェクトグラフをシリアライズできます。しかし、ファイル形式の互換性管理は開発者の責任となります。

b. JSON / XML ファイルとして保存する

QJsonDocument, QJsonObject, QJsonArray (JSON) や QXmlStreamWriter, QXmlStreamReader (XML) を使用して、設定やデータを構造化されたテキスト形式で保存することも一般的です。

特徴

  • QVariantとJSON/XMLのデータ型は比較的容易にマッピングできる。
  • スキーマ定義やバリデーションを行うことで、データの一貫性を保ちやすい。
  • 異なるプログラミング言語やシステムとのデータ連携が容易。
  • 人間が読みやすく、デバッグしやすい(JSON/XML)。

コード例 (JSON)

#include <QCoreApplication>
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString fileName = "appsettings.json";

    // 設定データを作成 (QJsonObjectでキーと値を表現)
    QJsonObject settingsObject;
    settingsObject["userName"] = "Charlie";
    settingsObject["appVersion"] = 2.0;
    settingsObject["darkMode"] = true;

    // より複雑なデータ (配列)
    QJsonArray recentFilesArray;
    recentFilesArray.append("/home/user/doc1.txt");
    recentFilesArray.append("/home/user/project/main.cpp");
    settingsObject["recentFiles"] = recentFilesArray;

    // QJsonDocument に格納
    QJsonDocument doc(settingsObject);

    // JSONをファイルに書き込み
    QFile outFile(fileName);
    if (outFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
        outFile.write(doc.toJson(QJsonDocument::Indented)); // 整形して書き込む
        outFile.close();
        qDebug() << "JSON settings written to" << fileName;
    } else {
        qDebug() << "Error opening JSON file for writing:" << outFile.errorString();
    }

    // JSONをファイルから読み込み
    QFile inFile(fileName);
    if (inFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QByteArray jsonData = inFile.readAll();
        inFile.close();

        QJsonDocument loadedDoc = QJsonDocument::fromJson(jsonData);
        if (!loadedDoc.isNull() && loadedDoc.isObject()) {
            QJsonObject loadedSettings = loadedDoc.object();

            QString userName = loadedSettings["userName"].toString();
            double appVersion = loadedSettings["appVersion"].toDouble();
            bool darkMode = loadedSettings["darkMode"].toBool();

            qDebug() << "\nLoaded JSON Settings:";
            qDebug() << "  User Name:" << userName;
            qDebug() << "  App Version:" << appVersion;
            qDebug() << "  Dark Mode:" << darkMode;

            if (loadedSettings.contains("recentFiles") && loadedSettings["recentFiles"].isArray()) {
                QJsonArray loadedRecentFiles = loadedSettings["recentFiles"].toArray();
                qDebug() << "  Recent Files:";
                for (const QJsonValue &value : loadedRecentFiles) {
                    qDebug() << "    -" << value.toString();
                }
            }
        } else {
            qDebug() << "Error: Invalid JSON document or not an object.";
        }
    } else {
        qDebug() << "Error opening JSON file for reading:" << inFile.errorString();
    }

    // QFile::remove(fileName); // テスト後にファイルを削除

    return 0;
}

考察
JSONやXMLは、複雑な構造を持つ設定や、アプリケーション間で共有されるべきデータに適しています。QSettings::SettingsMapのように単純なキーと値のペアだけでなく、ネストされたオブジェクトや配列も自然に表現できます。

  • より複雑なデータ構造や、設定以外の目的でデータを永続化したい場合は、QDataStream(バイナリ)や QJsonDocument/QXmlStreamWriter/Reader(テキストベース)などの代替手段を検討すると良いでしょう。これらはQSettings::SettingsMapが提供する単純なキーバリューペア以上の柔軟性を提供します。
  • QSettings::SettingsMapは、QSettingsが提供する組み込みフォーマット(レジストリ、INI、plistなど)では対応できない、独自のファイル形式で設定を保存したい場合に、そのカスタム形式の読み書き関数を実装する際に必要となります。
  • ほとんどのQtアプリケーションの設定管理には、QSettingsの標準機能を使用するのが最も簡単で推奨されます。 QSettings::SettingsMapを直接扱う必要はありません。