QSettings::endArray()でハマらない!Qt設定保存のベストプラクティス

2025-05-27

QSettings は、アプリケーションの設定をプラットフォームに依存しない形で保存・読み込みするためのクラスです。レジストリ(Windows)、INIファイル(Unix)、Carbon preferences API(macOS)など、各プラットフォームに適した方法で設定を管理します。

配列形式の設定を扱う場合、QSettings には以下の3つの関連する関数があります。

  1. QSettings::beginReadArray(const QString &prefix):

    • 配列形式の設定を読み始める際に呼び出します。
    • prefix は配列の名前です。
    • この関数を呼び出すと、QSettingsの内部的な「現在のパス」が指定された配列の最初の要素(インデックス0)に設定されます。
    • 戻り値として、配列の要素数(サイズ)を返します。
  2. QSettings::beginWriteArray(const QString &prefix, int size = -1):

    • 配列形式の設定を書き込む際に呼び出します。
    • prefix は配列の名前です。
    • size は書き込む配列の要素数です。通常、配列を書き込む前に要素数が分かっている場合は指定します。省略(デフォルトの-1)した場合、endArray()が呼び出された時点での最大のインデックスが配列のサイズとなります。
    • この関数を呼び出すと、QSettingsの内部的な「現在のパス」が指定された配列の最初の要素(インデックス0)に設定されます。
  3. QSettings::setArrayIndex(int i):

    • beginReadArray() または beginWriteArray() の後、配列の特定の要素にアクセスするために使用します。
    • i はアクセスしたい配列のインデックスです。
    • この関数を呼び出すことで、QSettingsの現在のパスが $prefix/i/ のように設定され、そのインデックスのキーと値にアクセスできるようになります。
  4. QSettings::endArray():

    • 配列の読み書きを終了する際に呼び出します。
    • beginReadArray() または beginWriteArray() で開始された配列のセッションを閉じます。
    • この関数を呼び出すと、QSettingsの内部的な「現在のパス」が、beginArray が呼び出される前の状態に戻ります。これにより、配列外の他の設定に再びアクセスできるようになります。

具体的な使用例(書き込み)

QSettings settings("MyCompany", "MyApp");

settings.beginWriteArray("users", 3); // "users"という配列を3つの要素で書き込む
for (int i = 0; i < 3; ++i) {
    settings.setArrayIndex(i); // i番目の要素に移動
    settings.setValue("name", QString("User%1").arg(i + 1));
    settings.setValue("age", 20 + i);
}
settings.endArray(); // 配列の書き込みを終了

具体的な使用例(読み込み)

QSettings settings("MyCompany", "MyApp");

int size = settings.beginReadArray("users"); // "users"という配列を読み始める
for (int i = 0; i < size; ++i) {
    settings.setArrayIndex(i); // i番目の要素に移動
    QString name = settings.value("name").toString();
    int age = settings.value("age").toInt();
    qDebug() << "User:" << name << ", Age:" << age;
}
settings.endArray(); // 配列の読み込みを終了

endArray() を呼び出すことの重要性

endArray() を呼び出すことは非常に重要です。これを呼び出さないと、QSettingsの内部状態が配列内に留まったままになり、その後の設定の読み書きが予期せぬ動作をする可能性があります。また、リソースの解放や、書き込みの場合には実際にデータが保存されるトリガーとなることもあります。



QSettings::endArray() の一般的なエラーと問題

    • エラーメッセージ: Qt のデバッグ出力に「QSettings::endArray: Expected beginReadArray() (or beginWriteArray()) instead」のようなメッセージが表示されることがあります。これは、beginReadArray() または beginWriteArray() が呼び出されていないにもかかわらず endArray() が呼び出された場合に発生します。
    • 原因:
      • 配列の読み書きを開始する beginReadArray() または beginWriteArray() の呼び出しを忘れている。
      • 配列の読み書きを開始したものの、途中で条件分岐などにより endArray() に到達しないパスが存在する。
      • 複数の QSettings オブジェクトを使用している場合、異なるオブジェクトに対して beginArrayendArray を呼び出している。
      • beginGroup()endGroup() の処理と混同している。
  1. setArrayIndex() の呼び出し忘れまたは誤ったインデックス

    • エラーメッセージ: 明示的なエラーメッセージは出にくいですが、期待した設定値が読み書きされない、あるいは空の値が返されることがあります。
    • 原因:
      • beginReadArray() または beginWriteArray() の後、各要素にアクセスする前に setArrayIndex(i) を呼び出すのを忘れている。
      • ループのインデックスが配列の実際のサイズを超えている、または負のインデックスを使用している。特に読み込み時、beginReadArray() が返したサイズを正しく利用していない場合。
  2. データ型の不一致

    • エラーメッセージ: 明示的な QSettings::endArray() のエラーではなく、value().toInt()value().toString() などで予期せぬ値が返される、あるいは変換失敗を示す値が返される。
    • 原因:
      • 設定を書き込む際に保存したデータ型と、読み込む際に期待するデータ型が一致しない。QSettingsQVariant を介してデータを保存するため、型変換で問題が発生することがあります。
  3. 設定ファイルへのアクセス権限の問題

    • エラーメッセージ: QSettings::status() をチェックしない限り、明示的なエラーメッセージは出にくいですが、設定が保存されない、または読み込めない場合があります。
    • 原因:
      • アプリケーションが設定ファイルを書き込むための適切な権限を持っていない(例: システム設定のパスにユーザー権限で書き込もうとしている)。
      • 設定ファイルが破損している、または不正な形式である。
  4. 複数のスレッドからのアクセス

    • エラーメッセージ: データ競合やクラッシュが発生することがありますが、QSettings::endArray() に直接関連する特定のエラーメッセージはありません。
    • 原因: QSettings オブジェクトは再入可能(reentrant)ですが、完全にスレッドセーフではありません。異なるスレッドから同じ QSettings オブジェクトに同時にアクセスして読み書きを行うと、問題が発生する可能性があります。
  1. beginArray()endArray() のペアを確認する

    • 最も一般的なエラーは、これらのペアのミスマッチです。コードをレビューし、すべての beginReadArray() または beginWriteArray() の呼び出しに対して、対応する endArray() の呼び出しが必ずあることを確認してください。
    • 特に、例外が発生する可能性のあるコードブロック内で配列を扱っている場合、try...finally (C++11以降のRAII的なアプローチが望ましい) や、関数の最後に必ず endArray() が呼び出されるように設計することを検討してください。
  2. qDebug() を使って状態を追跡する

    • beginReadArray() / beginWriteArray() の呼び出し前、setArrayIndex() の呼び出し後、endArray() の呼び出し後に、QSettings::group()QSettings::allKeys() を使って現在のパスや利用可能なキーをデバッグ出力してみると、QSettingsの内部状態がどのように変化しているかを確認できます。
    • 例えば、qDebug() << "Current Group:" << settings.group(); のような出力を追加します。
  3. QSettings::status() をチェックする

    • QSettings の操作後、settings.status() を呼び出すことで、エラーが発生したかどうかを確認できます。
    • QSettings::NoError (0) 以外が返された場合、ファイルアクセスエラー (QSettings::AccessError) やフォーマットエラー (QSettings::FormatError) などが発生している可能性があります。
  4. 設定ファイルの場所を確認する

    • QSettings::fileName() を使用して、実際に設定が保存されているファイルパスを確認します。
    • Windows のレジストリ、macOS の plist、Unix の INI ファイルなど、プラットフォームによって保存場所が異なります。手動でファイルを開いて内容を確認できる場合は、期待通りのデータが保存されているか、または読み込み時にファイルが存在するかを確認します。
    • QSettings のコンストラクタで指定した organizationapplication の名前が、読み書きの両方で一貫していることを確認してください。
  5. setArrayIndex() の使用を再確認する

    • 配列内の各要素にアクセスするループ内で、settings.setArrayIndex(i); が正しく呼び出されていることを確認してください。
    • 特に読み込み時、beginReadArray() が返す配列サイズをループの条件に正しく使用していることを確認してください。
  6. スレッドセーフティを考慮する

    • 複数のスレッドから QSettings を使用する場合は、各スレッドで独自の QSettings オブジェクトを作成するか、ミューテックスなどを使用して QSettings オブジェクトへのアクセスを同期することを検討してください。
    • ただし、通常、QSettingsはアプリケーションのメインスレッドで操作され、アプリケーションの起動時や終了時に設定を読み書きすることが多いため、マルチスレッドでの競合は稀です。
  7. キー名の規則を確認する

    • キー名に /\ を使用していないか、大文字小文字の区別が一貫しているかを確認します。これにより、予期せぬキーの解決やプラットフォーム間の互換性の問題を防ぐことができます。


配列形式の設定を書き込む例

この例では、ユーザーのリストとその情報を設定ファイルに保存します。

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

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

    // アプリケーション情報の設定 (QSettingsを使用する前に推奨)
    QCoreApplication::setOrganizationName("MyCompany");
    QCoreApplication::setApplicationName("MyApp");

    QSettings settings; // QSettingsオブジェクトの作成

    // --- 配列の書き込み ---
    QString arrayName = "users";
    int numberOfUsers = 3;

    qDebug() << "--- Writing array '" << arrayName << "' ---";

    // "users" という名前で配列の書き込みを開始。要素数を指定。
    settings.beginWriteArray(arrayName, numberOfUsers);

    for (int i = 0; i < numberOfUsers; ++i) {
        settings.setArrayIndex(i); // 現在の配列インデックスを設定

        // 各要素のキーと値を設定
        settings.setValue("name", QString("User%1").arg(i + 1));
        settings.setValue("age", 25 + i);
        settings.setValue("email", QString("user%[email protected]").arg(i + 1));
        qDebug() << "  Wrote user at index" << i;
    }

    // 配列の書き込みを終了
    settings.endArray();
    qDebug() << "--- Finished writing array '" << arrayName << "' ---";

    // 設定を強制的にディスクに同期 (通常は自動的に行われますが、明示的に行うことも可能)
    settings.sync();

    qDebug() << "Settings saved to:" << settings.fileName();

    return 0; // 例なので、実際にはここでアプリケーションを終了する
}

解説

  • settings.sync(); は、設定の変更をすぐにディスクに書き込むことを保証します。通常は必要ありませんが、アプリケーションのクラッシュに備えたり、すぐに設定ファイルを他のプロセスから読み込ませたい場合などに使用します。
  • settings.endArray(); が最も重要な部分です。これにより、配列の書き込みセッションが終了し、QSettings の内部状態が配列の外に戻ります。この呼び出しを忘れると、予期せぬ動作や設定の破損につながる可能性があります。
  • ループ内で settings.setArrayIndex(i); を呼び出すことで、現在の書き込み対象が配列の i 番目の要素であることを QSettings に伝えます。これにより、後続の setValue() がその要素内のキーに対して行われます。
  • settings.beginWriteArray("users", numberOfUsers);"users" という名前の配列の書き込みを開始します。numberOfUsers を指定することで、配列のサイズをあらかじめ宣言しています。
  • QCoreApplication::setOrganizationName()QCoreApplication::setApplicationName() は、QSettings が設定ファイルを保存する際のパスを決定するために重要です。

配列形式の設定を読み込む例

次に、先ほど保存した設定を読み込む例を示します。

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

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

    QCoreApplication::setOrganizationName("MyCompany");
    QCoreApplication::setApplicationName("MyApp");

    QSettings settings; // QSettingsオブジェクトの作成

    // --- 配列の読み込み ---
    QString arrayName = "users";

    qDebug() << "--- Reading array '" << arrayName << "' ---";

    // "users" という名前で配列の読み込みを開始し、配列の要素数を取得
    int size = settings.beginReadArray(arrayName);

    if (size == -1) { // 配列が見つからない場合やエラー
        qDebug() << "Array '" << arrayName << "' not found or empty.";
    } else {
        qDebug() << "Found array '" << arrayName << "' with " << size << " elements.";
        for (int i = 0; i < size; ++i) {
            settings.setArrayIndex(i); // 現在の配列インデックスを設定

            // 各要素のキーと値を読み込み
            QString name = settings.value("name").toString();
            int age = settings.value("age").toInt();
            QString email = settings.value("email").toString();

            qDebug() << "  Element" << i << ": Name=" << name << ", Age=" << age << ", Email=" << email;
        }
    }

    // 配列の読み込みを終了
    settings.endArray();
    qDebug() << "--- Finished reading array '" << arrayName << "' ---";

    return 0; // 例なので、実際にはここでアプリケーションを終了する
}

解説

  • settings.endArray(); は、読み込みセッションを終了するために不可欠です。これにより、QSettings の内部ポインタが配列の外に戻り、他の設定を読み書きできるようになります。
  • ここでも settings.setArrayIndex(i); を呼び出すことで、現在の読み込み対象が配列の i 番目の要素であることを QSettings に伝えます。
  • ループの条件にこの size を使用し、各要素を順番に処理します。
  • settings.beginReadArray("users");"users" 配列の読み込みを開始し、戻り値として配列の要素数(サイズ)を取得します。配列が存在しない場合や空の場合は -1 を返します。

QSettings では、グループと配列を組み合わせてより複雑な構造を表現することもできます。

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

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

    QCoreApplication::setOrganizationName("MyCompany");
    QCoreApplication::setApplicationName("MyApp");

    QSettings settings;

    // --- 書き込み例 ---
    qDebug() << "--- Writing settings with groups and arrays ---";
    settings.beginGroup("GeneralSettings"); // グループ開始
    settings.setValue("language", "Japanese");
    settings.endGroup(); // グループ終了

    settings.beginGroup("UserList"); // 別のグループ開始
    int numActiveUsers = 2;
    settings.beginWriteArray("activeUsers", numActiveUsers); // 配列開始
    for (int i = 0; i < numActiveUsers; ++i) {
        settings.setArrayIndex(i);
        settings.setValue("id", 100 + i);
        settings.setValue("status", (i == 0) ? "online" : "offline");
    }
    settings.endArray(); // 配列終了
    settings.endGroup(); // グループ終了
    qDebug() << "--- Finished writing ---";
    settings.sync();

    // --- 読み込み例 ---
    qDebug() << "--- Reading settings with groups and arrays ---";
    settings.beginGroup("GeneralSettings"); // グループ開始
    QString lang = settings.value("language", "English").toString();
    qDebug() << "Language:" << lang;
    settings.endGroup(); // グループ終了

    settings.beginGroup("UserList"); // 別のグループ開始
    int size = settings.beginReadArray("activeUsers"); // 配列開始
    if (size == -1) {
        qDebug() << "No active users found.";
    } else {
        qDebug() << "Active users count:" << size;
        for (int i = 0; i < size; ++i) {
            settings.setArrayIndex(i);
            int id = settings.value("id").toInt();
            QString status = settings.value("status").toString();
            qDebug() << "  User ID:" << id << ", Status:" << status;
        }
    }
    settings.endArray(); // 配列終了
    settings.endGroup(); // グループ終了
    qDebug() << "--- Finished reading ---";

    return 0;
}
  • QSettings は、プラットフォームによって設定の保存場所が異なります。Windows ではレジストリ、macOS では plist ファイル、Unix では INI ファイルなどが使用されます。settings.fileName() で実際の保存パスを確認できます。
  • beginGroup() / endGroup()beginArray() / endArray() はネストして使用できます。それぞれの end 関数が対応する begin 関数とペアになるように注意してください。
  • setArrayIndex() は、配列の個々の要素にアクセスするために beginArray の後に呼び出す必要があります。
  • beginReadArray() / beginWriteArray()endArray() は常にペアで使用する必要があります。


以下に、いくつかの代替方法と、それぞれのメリット・デメリットを説明します。

QVariantList や QVariantMap を直接保存する

Qt の QVariant は非常に強力で、多くのデータ型を扱うことができます。QList<QVariant> (QVariantList) や QMap<QString, QVariant> (QVariantMap) も QVariant に格納できるため、これらを直接 QSettings に保存・読み込みすることが可能です。

方法

  • 読み込む際は value().toList()value().toMap()QVariantListQVariantMap として取得し、そこから元の型に変換します。
  • QList<T> (TはQVariantに変換可能な型) を保存する場合、一度 QVariantList に変換してから setValue() で保存します。

コード例

#include <QCoreApplication>
#include <QSettings>
#include <QDebug>
#include <QVariantList>
#include <QVariantMap>

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

    QCoreApplication::setOrganizationName("MyCompany");
    QCoreApplication::setApplicationName("MyApp");

    QSettings settings;

    // --- QVariantList を保存する例 ---
    QStringList fruitList;
    fruitList << "Apple" << "Banana" << "Orange";
    settings.setValue("FruitSettings/fruits", QVariant::fromValue(fruitList)); // QStringList は QVariantList に変換可能

    QList<int> numberList;
    numberList << 10 << 20 << 30;
    settings.setValue("NumberSettings/numbers", QVariant::fromValue(numberList));

    // --- QVariantMap を保存する例 (オブジェクトのようなデータ) ---
    QVariantMap user1;
    user1["name"] = "Alice";
    user1["age"] = 30;
    user1["city"] = "Tokyo";

    QVariantMap user2;
    user2["name"] = "Bob";
    user2["age"] = 25;
    user2["city"] = "Osaka";

    QVariantList userList; // 複数のユーザーをリストとして保存
    userList.append(user1);
    userList.append(user2);
    settings.setValue("UsersDirect/user_data", userList);

    settings.sync();
    qDebug() << "Settings saved to:" << settings.fileName();

    // --- QVariantList を読み込む例 ---
    qDebug() << "\n--- Reading QVariantList/QVariantMap directly ---";

    QStringList loadedFruitList = settings.value("FruitSettings/fruits").toStringList();
    qDebug() << "Loaded Fruits:" << loadedFruitList;

    QList<int> loadedNumberList;
    QVariant variantNumbers = settings.value("NumberSettings/numbers");
    if (variantNumbers.canConvert<QList<int>>()) { // 型変換が可能かチェック
        loadedNumberList = variantNumbers.value<QList<int>>();
    }
    qDebug() << "Loaded Numbers:" << loadedNumberList;

    QVariantList loadedUserList = settings.value("UsersDirect/user_data").toList();
    qDebug() << "Loaded Users:";
    for (const QVariant& userVariant : loadedUserList) {
        if (userVariant.canConvert<QVariantMap>()) {
            QVariantMap userMap = userVariant.toMap();
            qDebug() << "  Name:" << userMap["name"].toString()
                     << ", Age:" << userMap["age"].toInt()
                     << ", City:" << userMap["city"].toString();
        }
    }

    return 0;
}

メリット

  • カスタムデータ型を QVariant に格納できるように qRegisterMetaType()qRegisterMetaTypeStreamOperators() を適切に設定していれば、QList<MyCustomObject> のようなリストもそのまま保存できる可能性があります。
  • beginArray() / endArray() の呼び出しが不要になり、コードが少しシンプルになる。

デメリット

  • beginReadArray() で得られる配列のサイズ情報がないため、読み込み時には value().toList().size() のようにリストを取得してからサイズを確認する必要があります。
  • ネイティブ形式 (レジストリなど) での保存は問題ありませんが、INI形式などでは、複雑なオブジェクトのリストはバイナリエンコードされるため、手動での編集が困難になります。
  • QSettings の内部的な配列構造とは異なる形で保存されるため、INIファイルなどの人間が読みやすい形式では、@Variant( で始まるバイナリデータのように見えることがあり、可読性が低下する場合があります。

キーのプレフィックスと連番を自力で管理する

beginArray() / endArray() は、実際には内部で prefix/sizeprefix/0/keyprefix/1/key のようなキー名を生成しています。このロジックを自分で実装することも可能です。

方法

  1. 配列の要素数を保存するためのキー(例: myArray/size)を定義します。
  2. ループで各要素にアクセスする際に、myArray/0/namemyArray/1/age のようにキー名に連番を組み込みます。
  3. 読み込み時には、まず myArray/size を読み込み、そのサイズを使ってループを回し、各要素のキーを構築して読み込みます。

コード例

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

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

    QCoreApplication::setOrganizationName("MyCompany");
    QCoreApplication::setApplicationName("MyApp");

    QSettings settings;

    // --- 手動で配列を書き込む例 ---
    QString arrayPrefix = "ManualUsers";
    int numberOfUsers = 2;

    qDebug() << "--- Writing array '" << arrayPrefix << "' manually ---";
    settings.setValue(arrayPrefix + "/size", numberOfUsers); // 要素数を保存

    for (int i = 0; i < numberOfUsers; ++i) {
        QString currentPrefix = QString("%1/%2").arg(arrayPrefix).arg(i);
        settings.setValue(currentPrefix + "/name", QString("ManualUser%1").arg(i + 1));
        settings.setValue(currentPrefix + "/role", (i == 0) ? "Admin" : "Guest");
        qDebug() << "  Wrote manual user at index" << i;
    }

    settings.sync();
    qDebug() << "--- Finished writing manually ---";

    // --- 手動で配列を読み込む例 ---
    qDebug() << "\n--- Reading array '" << arrayPrefix << "' manually ---";

    int loadedSize = settings.value(arrayPrefix + "/size", 0).toInt();
    if (loadedSize > 0) {
        qDebug() << "Found manual array with " << loadedSize << " elements.";
        for (int i = 0; i < loadedSize; ++i) {
            QString currentPrefix = QString("%1/%2").arg(arrayPrefix).arg(i);
            QString name = settings.value(currentPrefix + "/name").toString();
            QString role = settings.value(currentPrefix + "/role").toString();
            qDebug() << "  Element" << i << ": Name=" << name << ", Role=" << role;
        }
    } else {
        qDebug() << "Manual array not found or empty.";
    }

    return 0;
}

メリット

  • 特定のインデックスの要素のみを更新したい場合など、より柔軟なアクセスが可能です。
  • QVariantList を直接保存する場合と異なり、個々の要素が明示的なキーとして保存されるため、外部ツールからの編集も比較的容易です。
  • QSettings::beginArray() / endArray() を使用した場合と、INIファイルなどでの見た目が似ており、人間が読みやすい形式になります。

デメリット

  • QSettings の提供する便利な配列機能の恩恵を受けられない。
  • beginArray() / endArray() が自動的に行ってくれる要素数の管理やインデックスの追跡を自分で実装する必要があり、コード量が増え、エラーが発生しやすくなります。

XMLファイルやJSONファイルにシリアライズする

QSettings は、アプリケーションの比較的小さな設定を保存するのに適しています。より複雑なデータ構造や、人間が編集しやすい形式、あるいは他のシステムとの互換性が必要な場合は、XML (QXmlStreamWriter, QXmlStreamReader) や JSON (QJsonDocument, QJsonObject, QJsonArray) を使ってデータをシリアライズし、ファイルとして保存する方が適切な場合があります。

方法

  1. 保存したいデータ構造を QJsonObjectQJsonArray に変換します。
  2. QJsonDocument を作成し、それをバイト配列に変換します (toJson())。
  3. そのバイト配列を QFile などを使ってファイルに書き込みます。
  4. 読み込み時は逆のプロセスを辿ります。

コード例 (JSON の例、XMLも同様の考え方)

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

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

    QString filePath = "settings.json";

    // --- JSON ファイルに書き込む例 ---
    QJsonArray usersArray;

    QJsonObject user1;
    user1["name"] = "Alice";
    user1["age"] = 30;
    user1["email"] = "[email protected]";
    usersArray.append(user1);

    QJsonObject user2;
    user2["name"] = "Bob";
    user2["age"] = 25;
    user2["email"] = "[email protected]";
    usersArray.append(user2);

    QJsonObject rootObject;
    rootObject["users"] = usersArray;
    rootObject["last_modified"] = QDateTime::currentDateTime().toString(Qt::ISODate);

    QJsonDocument doc(rootObject);

    QFile file(filePath);
    if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        file.write(doc.toJson(QJsonDocument::Indented)); // 整形して書き込み
        file.close();
        qDebug() << "Settings saved to" << filePath;
    } else {
        qDebug() << "Failed to open file for writing:" << filePath;
    }

    // --- JSON ファイルから読み込む例 ---
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        QByteArray jsonData = file.readAll();
        file.close();

        QJsonDocument loadedDoc = QJsonDocument::fromJson(jsonData);
        if (!loadedDoc.isNull() && loadedDoc.isObject()) {
            QJsonObject loadedRoot = loadedDoc.object();
            qDebug() << "\n--- Reading settings from JSON file ---";
            qDebug() << "Last Modified:" << loadedRoot["last_modified"].toString();

            if (loadedRoot.contains("users") && loadedRoot["users"].isArray()) {
                QJsonArray loadedUsersArray = loadedRoot["users"].toArray();
                qDebug() << "Users:";
                for (const QJsonValue& userValue : loadedUsersArray) {
                    if (userValue.isObject()) {
                        QJsonObject userObj = userValue.toObject();
                        qDebug() << "  Name:" << userObj["name"].toString()
                                 << ", Age:" << userObj["age"].toInt()
                                 << ", Email:" << userObj["email"].toString();
                    }
                }
            }
        } else {
            qDebug() << "Failed to parse JSON document.";
        }
    } else {
        qDebug() << "Failed to open file for reading:" << filePath;
    }

    return 0;
}

メリット

  • 柔軟性
    ファイルパスを自由に決定でき、アプリケーションのポータブル性を高めることができます。
  • 相互運用性
    他のプログラミング言語やシステムとのデータ交換が容易になります。
  • 複雑なデータ構造のサポート
    ネストされたリストやオブジェクトなど、より複雑なデータ構造を自然な形で表現できます。
  • 高い可読性
    JSONやXMLは人間が読み書きしやすく、デバッグが容易です。
  • 小さな設定値の頻繁な読み書きにはオーバーヘッドが大きくなる可能性があります。
  • ファイルの破損など、I/Oに関連するエラー処理をより詳細に考慮する必要があります。
  • ファイルI/Oの処理を自分で記述する必要があるため、コード量が増えます。
  • QSettings が提供するプラットフォームネイティブな保存メカニズム(レジストリなど)を利用できなくなります。
  • 高度な柔軟性、複雑なデータ構造、他のシステムとの互換性が必要な場合
    JSONやXMLによるシリアライズが最も強力な選択肢です。ただし、ファイルI/Oの管理は自前で行う必要があります。
  • 配列が単純な型のリスト (文字列リスト、数値リストなど) で、かつ設定ファイルの可読性も多少は欲しい場合
    QVariantList を直接保存する方法も検討できます。ただし、INIファイル形式などではバイナリエンコードされる可能性がある点に注意が必要です。
  • 最もシンプルで、QSettingsの恩恵を最大限に受けたい場合
    QSettings::beginReadArray() / beginWriteArray()endArray() の標準的な方法を強く推奨します。これが最もQtらしいやり方です。