初心者必見!Qt QSettings beginWriteArrayで学ぶ実践的な設定管理

2025-05-27

QtにおけるQSettings::beginWriteArray()は、アプリケーションの設定を保存する際に、配列形式のデータを書き込むための機能です。

QSettings::beginWriteArray()の役割

通常、QSettingsを使って設定を保存する場合、キーと値のペアでデータを保存します。しかし、同じ種類の複数の設定(例:最近開いたファイルの一覧、ユーザーが定義したカスタムカラーのリストなど)を保存したい場合、1つずつ個別のキーで保存するのは非効率的で管理が大変になります。

  • 読み込みとの連携
    beginReadArray()と組み合わせることで、書き込んだ配列データを簡単に読み出すことができます。
  • 一貫した処理
    ループを使って配列の要素を簡単に書き込むことができます。
  • 構造化されたデータの保存
    関連する複数の設定を論理的にグループ化して保存できます。

beginWriteArray()は、引数として配列の「プレフィックス」と「サイズ」を取ります。

void QSettings::beginWriteArray(const QString &prefix, int size = -1)
  • size: 配列の要素数です。指定しない場合(-1)は、要素を書き込むたびに自動的にインクリメントされます。
  • prefix: 配列の各要素のキーの先頭に付加される文字列です。例えば、"colors"と指定すると、colors/0/red, colors/1/greenのようなキーが生成されます。

beginWriteArray()を呼び出した後、setArrayIndex()を使って現在の配列のインデックスを設定し、そのインデックスに対応する値を書き込みます。そして、配列の書き込みが完了したら、必ずendArray()を呼び出す必要があります。


複数の色を保存する場合

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

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

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

    // 保存する色のリスト
    QList<QColor> colors;
    colors << Qt::red << Qt::green << Qt::blue;

    // 配列の書き込み開始
    settings.beginWriteArray("customColors", colors.size());
    for (int i = 0; i < colors.size(); ++i) {
        settings.setArrayIndex(i); // 現在の配列インデックスを設定
        settings.setValue("value", colors.at(i)); // "customColors/i/value" として保存
    }
    settings.endArray(); // 配列の書き込み終了

    qDebug() << "設定を保存しました。";

    // 保存された設定を読み込む例
    settings.beginGroup("customColors"); // 配列のグループに移動
    int size = settings.size(); // 配列の要素数を取得

    qDebug() << "保存された色の数:" << size;

    for (int i = 0; i < size; ++i) {
        settings.setArrayIndex(i); // 現在の配列インデックスを設定
        QVariant colorValue = settings.value("value"); // "customColors/i/value" から値を読み込み
        QColor loadedColor = colorValue.value<QColor>();
        qDebug() << "Color" << i << ":" << loadedColor;
    }
    settings.endGroup(); // グループを終了 (beginWriteArray/endArrayで作成されたグループは、beginGroup/endGroupでもアクセス可能)

    return 0;
}

この例では、"customColors"というプレフィックスで配列を開始し、それぞれの色を"value"というキーで保存しています。内部的には、QSettingsは以下のようなキーでデータを保存します(Windowsレジストリの場合の例):

MyCompany\MyApp\customColors\
    0\
        value = (0, 0, 255) // 赤
    1\
        value = (0, 128, 0) // 緑
    2\
        value = (0, 0, 255) // 青
    size = 3 // 配列の要素数


QSettingsは非常に便利ですが、特に配列を扱う際にはいくつかの注意点があります。

endArray()の呼び忘れ

エラーの原因
beginWriteArray()を呼び出した後、対応するendArray()を呼び忘れると、設定が正しく保存されなかったり、設定ファイルが破損したりする可能性があります。Qtは内部的にグループや配列の状態をスタックで管理しており、endArray()はそのスタックから現在の配列のコンテキストを解除する役割を担っています。

トラブルシューティング

  • 例外が発生した場合にendArray()が呼び出されない可能性がある場合は、RAII(Resource Acquisition Is Initialization)パターンを使用して、スコープを抜ける際に自動的にendArray()が呼び出されるようにスマートポインタやカスタムクラスを検討することもできます。
  • beginWriteArray()を呼び出したら、その処理の最後に必ずendArray()を呼び出してください。特に、ループの中で配列要素を書き込む場合は、ループの外でendArray()を呼び出すように注意してください。

setArrayIndex()の呼び忘れ、または不正なインデックス

エラーの原因
beginWriteArray()を呼び出した後、各配列要素の書き込みを行う前にsetArrayIndex()を呼び出すのを忘れたり、存在しないインデックス(負の値など)を指定したりすると、意図しない場所にデータが保存されたり、データが上書きされたりします。

トラブルシューティング

  • ループを使用している場合は、ループ変数(iなど)をそのままsetArrayIndex(i)に渡すのが一般的です。
  • 配列の各要素を書き込む直前に、正しいインデックス(0から始まる整数)を指定してsetArrayIndex()を呼び出すことを確認してください。

異なるプラットフォーム間での設定ファイルの互換性問題

エラーの原因
QSettingsは、OSによって異なる設定保存メカニズムを使用します(Windowsではレジストリ、macOSではplistファイル、LinuxではINIファイルなど)。beginWriteArray()で生成されるキーの構造はプラットフォームによって若干異なる場合があります。特にINIファイル形式で設定を保存する場合、スラッシュ(/)やバックスラッシュ(\)の使用に注意が必要です。Qtのドキュメントには「セクション名やキー名にスラッシュやバックスラッシュを使用しないでください」と明記されています。バックスラッシュはサブキーの区切り文字として使われ、Windowsでは/に変換されることがあります。

トラブルシューティング

  • 特定のフォーマットを指定する
    異なるOS間で設定ファイルを共有したい場合は、QSettings::IniFormatのように特定のフォーマットを明示的に指定することを検討してください。これにより、プラットフォームごとの差異をある程度吸収できます。

    QSettings settings("MyCompany", "MyApp", this); // ネイティブフォーマット
    // または
    QSettings settings("path/to/settings.ini", QSettings::IniFormat, this); // INIファイル形式
    
  • キー名にスラッシュやバックスラッシュを使用しない
    これはQSettingsの基本的な推奨事項です。beginWriteArray()が内部的に生成するキーには通常問題ありませんが、自分でキー名を構築する際に注意してください。

QSettingsオブジェクトのスコープと永続化

エラーの原因
QSettingsオブジェクトがスタック上に作成され、そのスコープを抜ける前にデータが永続化されない場合があります。QSettingsは変更を即座に永続ストレージに書き込むとは限りません。

トラブルシューティング

  • アプリケーションが終了する際に設定が保存されるように、QSettingsオブジェクトをアプリケーションのライフサイクル全体で有効な状態に保つ(例えば、メインウィンドウのメンバー変数にする、シングルトンとして管理するなど)ことを検討してください。
  • 設定の変更をすぐに保存したい場合は、QSettings::sync()を明示的に呼び出してください。これにより、保留中の変更が強制的にストレージに書き込まれます。

権限の問題

エラーの原因
アプリケーションが設定ファイルを書き込むための適切な権限を持っていない場合、beginWriteArray()を含む書き込み操作は失敗します。これは特に、システム全体の設定や、読み取り専用のディレクトリに設定を保存しようとした場合に発生します。

トラブルシューティング

  • 設定ファイルの保存場所が適切か確認してください。ユーザー固有の設定は、通常、ユーザーのホームディレクトリ内のアプリケーションデータフォルダに保存する必要があります。システム全体の設定は、管理者権限が必要な場合があります。QStandardPathsクラスを使用して、プラットフォームに依存しない適切なパスを取得できます。
  • QSettings::status()をチェックして、書き込み操作後にエラーが発生していないか確認してください。QSettings::AccessErrorが返される場合は、権限の問題を示しています。

配列の読み込み時の不一致

エラーの原因
beginWriteArray()で書き込んだ配列を、beginReadArray()で正しく読み出せない場合があります。これは、書き込み時と読み込み時でprefixが異なっていたり、書き込んだ要素数と読み出そうとしている要素数が異なっていたりする場合に起こります。

トラブルシューティング

  • beginReadArray()を呼び出した後に、size()メソッドを使って実際に保存されている配列の要素数を取得し、その要素数に基づいてループを回すようにしてください。これにより、要素数の不一致によるエラーを防ぐことができます。

    settings.beginReadArray("customColors");
    int size = settings.size(); // 保存された要素数を取得
    for (int i = 0; i < size; ++i) {
        settings.setArrayIndex(i);
        // ... 値の読み込み ...
    }
    settings.endArray();
    
  • 書き込み時に使用したprefixと、読み込み時に使用するprefixが完全に一致していることを確認してください。



例1: 文字列のリストを保存・読み込み

最も基本的な例として、最近開いたファイルの名前のリストなど、文字列の配列を保存・読み込みます。

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

// 設定を保存する関数
void saveRecentFiles(const QStringList& files) {
    QSettings settings("MyCompany", "MyApp"); // 組織名とアプリケーション名を指定

    // 配列の書き込みを開始。プレフィックスは "recentFiles"、サイズはリストの要素数
    settings.beginWriteArray("recentFiles", files.size());
    for (int i = 0; i < files.size(); ++i) {
        settings.setArrayIndex(i); // 現在の配列インデックスを設定
        settings.setValue("path", files.at(i)); // "path" というキーでファイルパスを保存
    }
    settings.endArray(); // 配列の書き込みを終了

    qDebug() << "最近開いたファイルを保存しました:";
    for (const QString& file : files) {
        qDebug() << "  " << file;
    }
}

// 設定を読み込む関数
QStringList loadRecentFiles() {
    QSettings settings("MyCompany", "MyApp");

    QStringList files;
    // 配列の読み込みを開始。プレフィックスは "recentFiles"
    int size = settings.beginReadArray("recentFiles");
    for (int i = 0; i < size; ++i) {
        settings.setArrayIndex(i); // 現在の配列インデックスを設定
        files.append(settings.value("path").toString()); // "path" キーの値を文字列として読み込み
    }
    settings.endArray(); // 配列の読み込みを終了

    qDebug() << "最近開いたファイルを読み込みました:";
    if (files.isEmpty()) {
        qDebug() << "  (ファイルなし)";
    } else {
        for (const QString& file : files) {
            qDebug() << "  " << file;
        }
    }
    return files;
}

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

    // 設定を保存する例
    QStringList myFiles;
    myFiles << "/home/user/document1.txt" << "/home/user/image.png" << "C:\\Users\\user\\report.pdf";
    saveRecentFiles(myFiles);

    qDebug() << "\n--- 設定を再読み込み ---";

    // 保存された設定を読み込む例
    QStringList loadedFiles = loadRecentFiles();

    // 別の設定を保存し、上書きされることを確認
    qDebug() << "\n--- 別の設定を保存し、上書きを確認 ---";
    QStringList newFiles;
    newFiles << "new_doc.txt" << "another_image.jpg";
    saveRecentFiles(newFiles);

    qDebug() << "\n--- 新しい設定を再読み込み ---";
    loadedFiles = loadRecentFiles();


    return 0;
}

解説

  • 読み込み時も同様に、beginReadArray()で開始し、size()で要素数を取得し、setArrayIndex()でインデックスを指定してvalue()で値を読み込みます。
  • settings.endArray();: 配列の書き込みを終了します。これは非常に重要です。
  • settings.setValue("path", files.at(i));: 現在のインデックスの下に"path"というキーでファイルパスの値を保存します。
  • settings.setArrayIndex(i);: ループ内で、現在書き込む配列の要素のインデックスをiに設定します。これにより、内部的にはrecentFiles/0/path, recentFiles/1/pathのようなキーが生成されます。
  • settings.beginWriteArray("recentFiles", files.size());: "recentFiles"というプレフィックスで配列の書き込みを開始します。files.size()で配列の要素数を指定しています。

例2: カスタム構造体(またはクラス)のリストを保存・読み込み

QVariantの型変換機能を利用して、より複雑なデータ型(例えば、ユーザー定義の「ログイン情報」)のリストを保存・読み込みます。QVariant にカスタムタイプを格納するには、そのタイプが Q_DECLARE_METATYPE マクロで宣言され、qRegisterMetaType() で登録されている必要があります。

#include <QCoreApplication>
#include <QSettings>
#include <QDebug>
#include <QList>
#include <QVariant> // QVariant を使用するため

// ログイン情報を表す構造体
struct LoginInfo {
    QString username;
    QString password; // 通常は平文で保存しない!これはデモンストレーション目的
    bool rememberMe;

    // QVariant に変換するために必要なコンストラクタと代入演算子
    // これらは Q_DECLARE_METATYPE で利用される
    LoginInfo() : rememberMe(false) {}
    LoginInfo(const QString& user, const QString& pass, bool remember)
        : username(user), password(pass), rememberMe(remember) {}
};

// QVariant にカスタム型を格納できるようにするためのマクロ
Q_DECLARE_METATYPE(LoginInfo)

// 設定を保存する関数
void saveLoginInfos(const QList<LoginInfo>& logins) {
    QSettings settings("MyCompany", "MyApp");

    settings.beginWriteArray("logins", logins.size());
    for (int i = 0; i < logins.size(); ++i) {
        settings.setArrayIndex(i);
        // 各メンバーを個別のキーで保存
        settings.setValue("username", logins.at(i).username);
        settings.setValue("password", logins.at(i).password);
        settings.setValue("rememberMe", logins.at(i).rememberMe);
    }
    settings.endArray();

    qDebug() << "ログイン情報を保存しました。";
}

// 設定を読み込む関数
QList<LoginInfo> loadLoginInfos() {
    QSettings settings("MyCompany", "MyApp");

    QList<LoginInfo> logins;
    int size = settings.beginReadArray("logins");
    for (int i = 0; i < size; ++i) {
        settings.setArrayIndex(i);
        LoginInfo info;
        info.username = settings.value("username").toString();
        info.password = settings.value("password").toString();
        info.rememberMe = settings.value("rememberMe").toBool();
        logins.append(info);
    }
    settings.endArray();

    qDebug() << "ログイン情報を読み込みました:";
    if (logins.isEmpty()) {
        qDebug() << "  (ログイン情報なし)";
    } else {
        for (const LoginInfo& info : logins) {
            qDebug() << "  User:" << info.username << ", Pass:" << info.password << ", Remember:" << info.rememberMe;
        }
    }
    return logins;
}

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

    // Q_DECLARE_METATYPE を使用したカスタムタイプを QVariant で扱うために登録
    qRegisterMetaType<LoginInfo>("LoginInfo");

    // ログイン情報を保存する例
    QList<LoginInfo> myLogins;
    myLogins << LoginInfo("user1", "pass123", true);
    myLogins << LoginInfo("admin", "secure_pass", false);
    saveLoginInfos(myLogins);

    qDebug() << "\n--- 設定を再読み込み ---";

    // 保存されたログイン情報を読み込む例
    QList<LoginInfo> loadedLogins = loadLoginInfos();

    return 0;
}

解説

  • LoginInfoオブジェクトは、そのメンバー(username, password, rememberMe)を配列の各インデックスの下に個別のキーで保存しています。読み込み時も同様に、個別のキーから各メンバーを読み出してLoginInfoオブジェクトを再構築します。
  • Q_DECLARE_METATYPE(LoginInfo)qRegisterMetaType<LoginInfo>("LoginInfo"); は、カスタム型をQVariantで扱うために必要です。これにより、QVariantLoginInfo型を認識できるようになります。
  • この例では、LoginInfoというカスタム構造体を定義し、そのインスタンスのリストを保存しています。

beginWriteArray()size 引数を省略すると、setArrayIndex() を呼び出すたびに配列のサイズが自動的にインクリメントされます。これは、事前に配列のサイズが分からない場合に便利です。

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

// 設定を保存する関数 (サイズを明示的に指定しない)
void addLogEntry(const QString& entry) {
    QSettings settings("MyCompany", "MyApp");

    settings.beginWriteArray("logEntries"); // size 引数を省略
    int currentSize = settings.size(); // 現在の配列のサイズを取得 (新しく追加されるインデックス)
    settings.setArrayIndex(currentSize); // 次の空いているインデックスを設定
    settings.setValue("timestamp", QDateTime::currentDateTime());
    settings.setValue("message", entry);
    settings.endArray(); // 配列の書き込みを終了

    qDebug() << "ログエントリを追加しました:" << entry;
}

// 設定を読み込む関数
void loadLogEntries() {
    QSettings settings("MyCompany", "MyApp");

    qDebug() << "ログエントリを読み込みました:";
    int size = settings.beginReadArray("logEntries");
    if (size == 0) {
        qDebug() << "  (ログエントリなし)";
    } else {
        for (int i = 0; i < size; ++i) {
            settings.setArrayIndex(i);
            QDateTime timestamp = settings.value("timestamp").toDateTime();
            QString message = settings.value("message").toString();
            qDebug() << "  [" << timestamp.toString(Qt::ISODate) << "] " << message;
        }
    }
    settings.endArray();
}

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

    // ログエントリを追加する例
    addLogEntry("アプリケーション起動");
    addLogEntry("ユーザーが設定を変更");
    addLogEntry("ファイルの保存に成功");

    qDebug() << "\n--- 設定を再読み込み ---";
    loadLogEntries();

    // もう一度追加してみる
    qDebug() << "\n--- 追加のログエントリ ---";
    addLogEntry("アプリケーション終了");

    qDebug() << "\n--- 最新の設定を再読み込み ---";
    loadLogEntries();

    return 0;
}
  • settings.setArrayIndex(currentSize);: 既存の配列の末尾に新しい要素を追加するために、現在のサイズをインデックスとして設定します。
  • int currentSize = settings.size();: beginWriteArray()を呼び出した後、settings.size()は現在書き込み中の配列の「現在の要素数」を返します。これは、新しく追加する要素のインデックスとして使用できます。
  • settings.beginWriteArray("logEntries");: size引数を省略して呼び出しています。


QSettings::beginWriteArray() はシンプルでプラットフォームに依存しない設定保存に適していますが、より複雑なデータ構造、パフォーマンス要件、または特定の設定形式が必要な場合には、以下の方法が考えられます。

QSettings::setValue() と独自のキー命名規則

beginWriteArray() を使わずに、QSettings::setValue() を直接使用し、独自のキー命名規則で配列を模倣する方法です。

メリット

  • 柔軟なキー命名が可能。
  • シンプルな配列であれば、beginWriteArray() / endArray() の呼び出しが不要でコードが簡潔になる場合があります。

デメリット

  • 内部的な整合性のチェックが QSettings によって行われない。
  • beginReadArray() / endArray() のような便利な読み込みヘルパーがないため、読み込みコードが複雑になる可能性がある。
  • 配列の要素数を知るための追加のキー(例: arraySize = 5)を自分で管理する必要がある。


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

void saveRecentFilesManual(const QStringList& files) {
    QSettings settings("MyCompany", "MyApp");

    settings.setValue("recentFiles/size", files.size()); // 要素数を保存
    for (int i = 0; i < files.size(); ++i) {
        settings.setValue(QString("recentFiles/%1/path").arg(i), files.at(i)); // キーを自分で構築
    }
    qDebug() << "手動で最近開いたファイルを保存しました。";
}

QStringList loadRecentFilesManual() {
    QSettings settings("MyCompany", "MyApp");

    QStringList files;
    int size = settings.value("recentFiles/size", 0).toInt(); // 要素数を読み込み
    for (int i = 0; i < size; ++i) {
        files.append(settings.value(QString("recentFiles/%1/path").arg(i)).toString());
    }
    qDebug() << "手動で最近開いたファイルを読み込みました:";
    for (const QString& file : files) {
        qDebug() << "  " << file;
    }
    return files;
}

int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);
    QStringList myFiles;
    myFiles << "fileA.txt" << "fileB.doc";
    saveRecentFilesManual(myFiles);
    loadRecentFilesManual();
    return 0;
}

QDataStream を使用したカスタムバイナリシリアライゼーション

QDataStream は、C++のデータ構造をバイナリ形式でファイルやデバイスに読み書きするための強力な方法です。

メリット

  • プラットフォーム独立
    QDataStream はエンディアンネスなどを自動的に処理するため、異なるプラットフォーム間でもバイナリデータを共有できます(ただし、データ構造の変更には注意が必要)。
  • カスタム型のシリアライズ
    operator<<operator>> をオーバーロードすることで、独自のカスタムクラスや構造体もシリアライズできます。
  • データ型の互換性
    QDataStream は、Qtの多くの組み込みデータ型(QString, QList, QMap など)を自動的にシリアライズできます。
  • 非常に高いパフォーマンス
    バイナリ形式のため、テキスト形式に比べて読み書きが高速です。

デメリット

  • 前方・後方互換性
    データ構造を変更した場合、古いバージョンで保存されたデータを新しいバージョンで読み込めなくなる可能性があります。バージョン管理のメカニズムを実装する必要があります。
  • 人間が読めない
    バイナリ形式なので、テキストエディタで開いても内容を理解できません。


#include <QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QList>
#include <QDebug>

struct MyData {
    QString name;
    int value;
    QList<double> scores;

    // QDataStream のためにストリーム演算子をオーバーロード
    friend QDataStream& operator<<(QDataStream& out, const MyData& data) {
        out << data.name << data.value << data.scores;
        return out;
    }
    friend QDataStream& operator>>(QDataStream& in, MyData& data) {
        in >> data.name >> data.value >> data.scores;
        return in;
    }
};

void saveMyDataList(const QList<MyData>& dataList, const QString& filename) {
    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly)) {
        qWarning() << "ファイルを書き込み用に開けません:" << file.errorString();
        return;
    }

    QDataStream out(&file);
    out << (quint32)dataList.size(); // リストのサイズを最初に保存
    for (const MyData& data : dataList) {
        out << data; // 各 MyData オブジェクトをシリアライズ
    }
    qDebug() << filename << "にデータを保存しました。";
}

QList<MyData> loadMyDataList(const QString& filename) {
    QList<MyData> dataList;
    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly)) {
        qWarning() << "ファイルを読み込み用に開けません:" << file.errorString();
        return dataList;
    }

    QDataStream in(&file);
    quint32 size;
    in >> size; // サイズを読み込み
    for (quint32 i = 0; i < size; ++i) {
        MyData data;
        in >> data; // 各 MyData オブジェクトをデシリアライズ
        dataList.append(data);
    }
    qDebug() << filename << "からデータを読み込みました:";
    for (const MyData& data : dataList) {
        qDebug() << "  Name:" << data.name << ", Value:" << data.value << ", Scores:" << data.scores;
    }
    return dataList;
}

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

    QList<MyData> myDataList;
    myDataList << MyData{"Item A", 10, {1.1, 2.2}}
               << MyData{"Item B", 20, {3.3, 4.4, 5.5}};

    saveMyDataList(QCoreApplication::applicationDirPath() + "/mydata.bin", myDataList);
    QList<MyData> loadedDataList = loadMyDataList(QCoreApplication::applicationDirPath() + "/mydata.bin");

    return 0;
}

JSON/XML などのテキスト形式のファイルに保存

QJsonDocument, QJsonObject, QJsonArray (JSONの場合) や QXmlStreamWriter, QXmlStreamReader (XMLの場合) を使用して、構造化されたテキスト形式でデータを保存します。

メリット

  • 柔軟性
    スキーマレスなデータ構造を扱うのが容易です。
  • データ交換
    他のプログラミング言語やシステムとのデータ交換に広く使われます。
  • 人間が読める
    テキスト形式なので、デバッグや手動での編集が容易です。

デメリット

  • ファイルサイズ
    バイナリ形式に比べてファイルサイズが大きくなる傾向があります。
  • パフォーマンス
    バイナリ形式に比べて読み書きが遅い場合があります。

例 (JSONの場合)

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

struct UserSetting {
    QString username;
    int level;
    QList<QString> achievements;
};

void saveUserSettings(const QList<UserSetting>& settingsList, const QString& filename) {
    QJsonArray usersArray;
    for (const UserSetting& setting : settingsList) {
        QJsonObject userObject;
        userObject["username"] = setting.username;
        userObject["level"] = setting.level;

        QJsonArray achievementsArray;
        for (const QString& achievement : setting.achievements) {
            achievementsArray.append(achievement);
        }
        userObject["achievements"] = achievementsArray;
        usersArray.append(userObject);
    }

    QJsonDocument doc(usersArray);
    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        qWarning() << "ファイルを書き込み用に開けません:" << file.errorString();
        return;
    }
    file.write(doc.toJson(QJsonDocument::Indented)); // 整形して保存
    qDebug() << filename << "にユーザー設定を保存しました。";
}

QList<UserSetting> loadUserSettings(const QString& filename) {
    QList<UserSetting> settingsList;
    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "ファイルを読み込み用に開けません:" << file.errorString();
        return settingsList;
    }

    QByteArray jsonData = file.readAll();
    QJsonDocument doc = QJsonDocument::fromJson(jsonData);

    if (!doc.isArray()) {
        qWarning() << "JSONデータが配列ではありません。";
        return settingsList;
    }

    QJsonArray usersArray = doc.array();
    for (const QJsonValue& value : usersArray) {
        if (!value.isObject()) continue;
        QJsonObject userObject = value.toObject();

        UserSetting setting;
        setting.username = userObject["username"].toString();
        setting.level = userObject["level"].toInt();

        QJsonArray achievementsArray = userObject["achievements"].toArray();
        for (const QJsonValue& achievementValue : achievementsArray) {
            setting.achievements.append(achievementValue.toString());
        }
        settingsList.append(setting);
    }
    qDebug() << filename << "からユーザー設定を読み込みました:";
    for (const UserSetting& setting : settingsList) {
        qDebug() << "  User:" << setting.username << ", Level:" << setting.level << ", Achievements:" << setting.achievements;
    }
    return settingsList;
}

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

    QList<UserSetting> mySettings;
    mySettings << UserSetting{"Alice", 5, {"Badge1", "Badge2"}}
               << UserSetting{"Bob", 8, {"Badge3"}};

    saveUserSettings(QCoreApplication::applicationDirPath() + "/users.json", mySettings);
    QList<UserSetting> loadedSettings = loadUserSettings(QCoreApplication::applicationDirPath() + "/users.json");

    return 0;
}

データベースの利用

大量の構造化データや、検索・ソートなどの機能が必要な場合は、SQLiteのような組み込みデータベース(QtではQSqlDatabaseを使用)を利用することを検討します。

メリット

  • スケーラビリティ
    より大規模なデータベースへの移行も比較的容易です。
  • トランザクション
    データの整合性を保つためのトランザクション処理が可能です。
  • 柔軟なクエリ
    SQLを使用してデータを柔軟に検索、フィルタリング、ソートできます。
  • 大規模データ
    大量のデータを効率的に管理できます。
  • オーバーヘッド
    シンプルな設定保存には過剰な場合がある。
  • 複雑性
    セットアップやクエリの記述など、学習コストがかかります。

どの方法を選ぶべきか?

  • 非常に大量のデータ、複雑なクエリ、データの整合性が必要
    データベース(SQLiteなど)が最も適切な選択肢です。
  • 人間が読める形式が必要、他のシステムとのデータ交換が必要、データ構造が複雑になりうる
    JSON (QJsonDocument) または XML (QXmlStreamWriter/Reader) が適しています。
  • 大量のデータ、パフォーマンスが重要、バイナリ互換性が必要、人間が読む必要がない
    QDataStream が最良の選択肢です。
  • 少量のリストデータだが、手動でキーを管理したい
    QSettings::setValue() と独自の命名規則も選択肢になりますが、通常は beginWriteArray() の方が推奨されます。
  • 簡単な設定や少量のリストデータ
    QSettings::beginWriteArray() は非常に適しています。プラットフォームのネイティブな方法で保存されるため、OSの推奨に従います。