【Qt入門】QMap::insert()で学ぶKey-Valueマップの基本と応用

2025-05-27

QMap とは

QMapは、Qtフレームワークが提供する汎用コンテナクラスの一つで、キーと値のペア(Key-Value pairs)を格納するために使用されます。C++標準ライブラリのstd::mapに似ていますが、Qt独自の機能や最適化が施されています。

QMapの主な特徴は以下の通りです。

  • 高速な検索/挿入: 要素の検索や挿入の計算量は、通常O(logn)です(nはマップ内の要素数)。
  • キーの一意性: 通常のQMapでは、同じキーを複数回挿入することはできません。もし、既に存在するキーでinsert()を呼び出すと、古い値が新しい値で上書きされます。 複数の値を同じキーに関連付けたい場合は、QMultiMapを使用します。
  • キーによる自動ソート: QMapに格納された要素は、常にキーの昇順にソートされて保持されます。これにより、要素へのアクセスやイテレート(繰り返し処理)がキーの順序で行われます。
  • キーと値のペア: 各要素は一意のキーとそれに対応する値から構成されます。

QMap::insert() メソッドの役割

QMap::insert()は、QMapに新しいキーと値のペアを追加するための基本的なメソッドです。

シグネチャ(関数宣言)

一般的なシグネチャは以下のようになります。

iterator insert(const Key &key, const T &value);
  • T: 値の型です。
  • Key: キーの型です。

動作

  1. 新しいペアの追加: 指定されたkeyvalueのペアをQMapに追加します。
  2. 既存のキーの上書き: もし、すでにマップ内に同じkeyが存在する場合、そのkeyに関連付けられていた既存のvalueは、新しく指定されたvalueで上書きされます。
  3. 戻り値: 挿入されたアイテムを指すイテレータを返します。

#include <QCoreApplication>
#include <QDebug>
#include <QMap>

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

    QMap<QString, int> ages;

    // 新しいキーと値のペアを挿入
    ages.insert("Alice", 30);
    ages.insert("Bob", 25);
    ages.insert("Charlie", 35);

    qDebug() << "初期のQMap:";
    for (auto it = ages.constBegin(); it != ages.constEnd(); ++it) {
        qDebug() << it.key() << ":" << it.value();
    }
    // 出力例:
    // Alice : 30
    // Bob : 25
    // Charlie : 35

    // 既存のキーでinsert()を呼び出す (値が上書きされる)
    ages.insert("Bob", 26); // Bobの年齢を25から26に更新

    qDebug() << "\nBobの年齢を更新後:";
    for (auto it = ages.constBegin(); it != ages.constEnd(); ++it) {
        qDebug() << it.key() << ":" << it.value();
    }
    // 出力例:
    // Alice : 30
    // Bob : 26 // ここが更新されている
    // Charlie : 35

    // 新しいキーを再度追加
    ages.insert("David", 40);

    qDebug() << "\nDavidを追加後:";
    for (auto it = ages.constBegin(); it != ages.constEnd(); ++it) {
        qDebug() << it.key() << ":" << it.value();
    }
    // 出力例:
    // Alice : 30
    // Bob : 26
    // Charlie : 35
    // David : 40

    return a.exec();
}

QMapに要素を挿入する方法として、operator[]を使用することもできます。

map["key"] = value;

このoperator[]を使った挿入とinsert()メソッドには、いくつかの違いがあります。

  • insert():
    • キーが存在しない場合、新しいキーと指定された値を挿入します。
    • キーが存在する場合、既存の値を指定された値で上書きします。
    • 挿入された要素を指すイテレータを返します。
  • operator[]:
    • キーが存在しない場合、新しいキーとデフォルト構築された値を挿入し、その値への参照を返します。
    • キーが存在する場合、その値への参照を返し、その値を変更できます。
    • 常に変更可能な値への参照を返します。

一般的に、値を単に設定・更新したい場合はoperator[]が便利ですが、既存の値を上書きしたくない場合はcontains()などでキーの存在を確認してからinsert()を使用するか、QMultiMapを検討するのが良いでしょう。



キーの重複による値の上書き

問題: QMap::insert()は、同じキーが既に存在する場合、既存の値を新しい値で上書きします。意図せず既存のデータが失われることがあります。

QMap<QString, int> scores;
scores.insert("John", 100);
scores.insert("Jane", 90);
scores.insert("John", 110); // ここで"John"の100が110に上書きされる

qDebug() << scores.value("John"); // 出力: 110

トラブルシューティング:

  • QMultiMapの使用: 一つのキーに複数の値を関連付けたい場合は、QMultiMapを使用します。QMultiMap::insert()は、同じキーで複数回呼び出しても既存の値を上書きせず、新しい値を追加します。

    QMultiMap<QString, int> highScores;
    highScores.insert("John", 100);
    highScores.insert("Jane", 90);
    highScores.insert("John", 110); // "John"に関連する値が二つになる
    
    qDebug() << highScores.values("John"); // 出力: (100, 110)
    
  • キーの存在チェック: 上書きを避けたい場合は、QMap::contains()メソッドで事前にキーの存在を確認します。

    if (!scores.contains("John")) {
        scores.insert("John", 100);
    } else {
        qDebug() << "Warning: 'John' already exists. Value not inserted to avoid overwrite.";
    }
    
  • 意図的な上書きか確認: コードのロジックが意図的に上書きを目的としているか確認します。

キーまたは値の型の要件不足

問題: QMapのキーと値の型には特定の要件があります。これらの要件を満たさない型を使用すると、コンパイルエラーやランタイムエラーが発生することがあります。

  • 非ポインタ型としてのQWidgetなど: QWidgetなどのQtのオブジェクトは、その性質上、値型として直接QMapに格納することは推奨されません。これらはポインタ(例: QWidget*)として格納する必要があります。
  • 値の型: 値の型は、代入可能(assignable)である必要があります。コピーコンストラクタと代入演算子が適切に定義されている必要があります。
  • キーの型: QMapのキーの型は、全順序(total order)を提供するoperator<()を実装している必要があります。これは、QMapがキーに基づいて要素をソートするためです。
// エラー例: カスタムクラスにoperator<()がない場合
class MyKey {
public:
    int id;
    QString name;
    // operator<() がないとQMapのキーとして使えない
};

QMap<MyKey, QString> myMap; // コンパイルエラーまたは未定義動作

トラブルシューティング:

  • Qtオブジェクトのポインタ格納: QWidgetのようなQObjectを継承するクラスのインスタンスを格納する場合は、ポインタ(例: QMap<QString, QWidget*>)として格納します。メモリ管理(new/delete)に注意するか、QScopedPointerQSharedPointerなどのスマートポインタの使用を検討してください。
  • 値の型が代入可能か確認: カスタムクラスを値として使用する場合、コピーコンストラクタと代入演算子が暗黙的に生成されるか、または明示的に定義されているか確認します。
  • キーのoperator<()の実装: カスタムクラスをキーとして使用する場合は、operator<()を適切にオーバーロードします。
    class MyKey {
    public:
        int id;
        QString name;
    
        bool operator<(const MyKey &other) const {
            if (id != other.id) {
                return id < other.id;
            }
            return name < other.name;
        }
    };
    
    QMap<MyKey, QString> myMap; // OK
    

constメソッド内でのinsert()呼び出し

問題: constメンバ関数内でQMap::insert()を呼び出そうとすると、コンパイルエラーが発生します。insert()はマップの内容を変更するため、const(定数)なオブジェクトに対しては呼び出せません。

class MyClass {
    QMap<QString, int> dataMap;
public:
    void addData(const QString &key, int value) const {
        dataMap.insert(key, value); // コンパイルエラー: 'this' pointer has type 'const MyClass', but method 'insert' is not const
    }
};

トラブルシューティング:

  • mutableキーワード: QMap自体は変更されるが、その変更がオブジェクトの論理的な状態を変更しないと見なせる場合に限り、QMapメンバをmutableとして宣言します。これは稀なケースであり、通常は推奨されません。
    class MyClass {
        mutable QMap<QString, int> dataMap; // dataMapはconstメソッドでも変更可能になる
    public:
        void addData(const QString &key, int value) const {
            dataMap.insert(key, value); // OK
        }
    };
    
  • constの削除: QMapを変更するメソッドはconstにすべきではありません。addDataメソッドからconstを削除します。
    class MyClass {
        QMap<QString, int> dataMap;
    public:
        void addData(const QString &key, int value) { // constを削除
            dataMap.insert(key, value); // OK
        }
    };
    

operator[]との混同

問題: QMap::insert()QMap::operator[]はどちらも要素を追加できますが、振る舞いが異なります。operator[]は、キーが存在しない場合にデフォルトコンストラクタで値を構築して挿入します。これにより、意図しないオブジェクトの生成や、デフォルトコンストラクタを持たない型での問題が発生することがあります。

QMap<QString, MyComplexObject> myMap;
// MyComplexObjectはデフォルトコンストラクタを持たないとする

// 意図しないオブジェクト生成の可能性
// キーが存在しない場合、MyComplexObject()が呼び出される
myMap["some_key"].doSomething();

// デフォルトコンストラクタがない場合、コンパイルエラー
myMap["another_key"];

トラブルシューティング:

  • 意図しないデフォルト構築の回避:
    • 挿入時: insert()を使用し、常に明示的な値を提供します。
    • 参照時: キーが存在するか確認してからアクセスする場合、contains()value()を使用します。
      if (myMap.contains("some_key")) {
          myMap.value("some_key").doSomething(); // const valueを返す
      }
      
    • 変更可能な参照が必要な場合: find()を使用してイテレータを取得し、そのイテレータを介して値にアクセス・変更します。
      QMap<QString, MyComplexObject>::iterator it = myMap.find("some_key");
      if (it != myMap.end()) {
          it.value().modifySomething();
      } else {
          // キーが存在しない場合の処理
          myMap.insert("some_key", MyComplexObject(/* ...コンストラクタ引数... */));
      }
      

メモリリーク(特にポインタを値として格納する場合)

問題: QMapがポインタ(例: MyObject*)を値として格納している場合、QMap::remove()QMap::insert()による上書き、またはQMap自体のデストラクタが呼び出されたときに、格納されたポインタが指すオブジェクトのメモリが自動的に解放されないことがあります。これはメモリリークの原因となります。

QMap<int, MyObject*> objMap;
objMap.insert(1, new MyObject("First"));
objMap.insert(2, new MyObject("Second"));

// 問題: "First"オブジェクトへのポインタが失われ、メモリリークする
objMap.insert(1, new MyObject("Overwritten First"));

トラブルシューティング:

  • 手動でのメモリ解放: スマートポインタが使えない場合、remove()insert()で上書きする前に、古い値のポインタをdeleteする必要があります。また、QMapが破棄される前(またはclear()が呼ばれる前)に、すべての値のポインタをdeleteするループを実行する必要があります。

    QMap<int, MyObject*> objMap;
    objMap.insert(1, new MyObject("First"));
    
    // 上書きする前に古い値をdelete
    if (objMap.contains(1)) {
        delete objMap.value(1); // または objMap[1]
    }
    objMap.insert(1, new MyObject("Overwritten First"));
    
    // QMapがスコープを抜ける前にすべてのオブジェクトをdelete
    for (MyObject* obj : objMap.values()) {
        delete obj;
    }
    objMap.clear(); // オブジェクト削除後にマップをクリア
    
  • スマートポインタの使用: QSharedPointerQScopedPointer(またはC++11以降のstd::shared_ptr/std::unique_ptr)を使用して、メモリ管理を自動化します。これが最も推奨されるアプローチです。

    QMap<int, QSharedPointer<MyObject>> objMap;
    objMap.insert(1, QSharedPointer<MyObject>(new MyObject("First")));
    
    // 古いオブジェクトは自動的に解放される
    objMap.insert(1, QSharedPointer<MyObject>(new MyObject("Overwritten First")));
    

問題: QMapに要素を挿入すると、既存のイテレータが無効になる可能性があります(特に要素が再配置された場合)。無効化されたイテレータを使用すると、未定義動作やクラッシュを引き起こします。

トラブルシューティング:

  • Hint付きのinsert()オーバーロード: insert(QMap::const_iterator pos, const Key &key, const T &value)のように、挿入位置のヒントを与えるオーバーロードを使用すると、特定の場合にパフォーマンスが向上し、イテレータの無効化が一部緩和されることがありますが、一般的なイテレータの取り扱いにおいては注意が必要です。
  • 挿入後にイテレータを再取得: 挿入操作を行った後は、イテレータを再取得するようにします。


例1: 基本的な使用法とキーの重複による上書き

この例では、QMapに文字列(名前)をキー、整数(年齢)を値として挿入します。同じキーを挿入すると、古い値が新しい値で上書きされることを示します。

#include <QCoreApplication>
#include <QDebug>
#include <QMap>

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

    // QMapを宣言: キーはQString、値はint
    QMap<QString, int> ages;

    qDebug() << "--- 初期挿入 ---";
    // 新しいキーと値のペアを挿入
    ages.insert("Alice", 30);
    ages.insert("Bob", 25);
    ages.insert("Charlie", 35);

    // マップの内容を出力 (キーでソートされて表示される)
    qDebug() << "現在のQMap (初期):" << ages;
    // 出力例: QMap(("Alice", 30), ("Bob", 25), ("Charlie", 35))

    qDebug() << "\n--- 既存のキーを上書き ---";
    // 既存のキー "Bob" でinsert()を呼び出す
    // これにより、Bobの年齢が25から26に上書きされる
    ages.insert("Bob", 26);

    qDebug() << "Bobの年齢を更新後:" << ages;
    // 出力例: QMap(("Alice", 30), ("Bob", 26), ("Charlie", 35))
    qDebug() << "Bobの新しい年齢:" << ages.value("Bob"); // 26

    qDebug() << "\n--- 新しいキーを追加 ---";
    ages.insert("David", 40);

    qDebug() << "Davidを追加後:" << ages;
    // 出力例: QMap(("Alice", 30), ("Bob", 26), ("Charlie", 35), ("David", 40))

    return a.exec();
}

解説:

  • ages.insert("Bob", 26);の行では、既に存在するキー "Bob" に対して再度insert()を呼び出しています。この場合、QMapのルールにより、以前の値25が新しい値26上書きされます。
  • ages.insert("Alice", 30);のように、キーと値を指定して要素を挿入します。

例2: QMap::insert()QMap::operator[] の比較

この例では、insert()operator[]の挙動の違いを示します。

#include <QCoreApplication>
#include <QDebug>
#include <QMap>

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

    QMap<QString, int> scores;

    qDebug() << "--- insert() を使用 ---";
    scores.insert("Eve", 85);
    scores.insert("Frank", 92);
    qDebug() << "insert() 後のマップ:" << scores;

    qDebug() << "\n--- operator[] を使用 (新しいキー) ---";
    // "Grace" はマップに存在しないため、新しい要素が追加される
    scores["Grace"] = 78;
    qDebug() << "operator[] (新規) 後のマップ:" << scores;

    qDebug() << "\n--- operator[] を使用 (既存のキーを更新) ---";
    // "Frank" はマップに存在するため、値が更新される
    scores["Frank"] = 95;
    qDebug() << "operator[] (更新) 後のマップ:" << scores;

    qDebug() << "\n--- insert() と operator[] の違いを示す例 ---";
    // insert() は常に新しい値を設定しようとする(既存なら上書き)
    scores.insert("Eve", 80); // Eveのスコアが85から80に上書きされる

    // operator[] は値への参照を返すので、直接値を変更できる
    scores["Grace"]++; // Graceのスコアが78から79になる

    qDebug() << "最終的なマップの状態:" << scores;
    // 出力例: QMap(("Eve", 80), ("Frank", 95), ("Grace", 79))

    return a.exec();
}

解説:

  • operator[]は、キーが存在しない場合はデフォルトコンストラクタで値を構築し、その参照を返します。キーが存在する場合は、その値への参照を返します。このため、scores["Grace"]++;のように値を直接変更するような操作が可能です。
  • insert()は、キーが存在する場合は値を上書きします。
  • 新しい要素を追加する点ではどちらも同じように見えます。

例3: QMultiMap::insert() との比較

QMapと異なり、QMultiMapでは同じキーに対して複数の値を格納できます。

#include <QCoreApplication>
#include <QDebug>
#include <QMultiMap>

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

    // QMultiMapを宣言: キーはQString、値はint
    QMultiMap<QString, int> studentScores;

    qDebug() << "--- QMultiMapへの挿入 ---";
    studentScores.insert("Alice", 95);
    studentScores.insert("Bob", 80);
    studentScores.insert("Alice", 88); // "Alice" に関連する2つ目のスコア
    studentScores.insert("Charlie", 70);
    studentScores.insert("Bob", 92);   // "Bob" に関連する2つ目のスコア

    // QMultiMapの内容を出力
    // values()メソッドで特定のキーに関連するすべての値を取得できる
    qDebug() << "Aliceのスコア:" << studentScores.values("Alice"); // 出力: (95, 88)
    qDebug() << "Bobのスコア:" << studentScores.values("Bob");     // 出力: (80, 92)
    qDebug() << "Charlieのスコア:" << studentScores.values("Charlie"); // 出力: (70)

    // 全ての要素をイテレータで出力
    qDebug() << "\n--- 全ての要素 (イテレータ使用) ---";
    for (auto it = studentScores.constBegin(); it != studentScores.constEnd(); ++it) {
        qDebug() << it.key() << ": " << it.value();
    }
    // 出力順序は保証される (キーでソートされ、挿入順とは限らない)
    // Alice: 95
    // Alice: 88
    // Bob: 80
    // Bob: 92
    // Charlie: 70

    return a.exec();
}

解説:

  • values("キー")メソッドを使って、そのキーに関連するすべての値のリストを取得できます。
  • QMultiMapは、insert()を同じキーで複数回呼び出しても、値を上書きせず、新しい値を追加します。

例4: カスタムクラスを値として格納(ポインタ利用)

QWidgetのようなQtオブジェクトは、ポインタとしてQMapに格納することが一般的です。

#include <QCoreApplication>
#include <QDebug>
#include <QMap>
#include <QLabel> // QWidgetを継承したクラス

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

    // キーはQString、値はQLabelへのポインタ
    QMap<QString, QLabel*> labelsMap;

    qDebug() << "--- QLabelポインタの挿入 ---";
    QLabel* label1 = new QLabel("Hello, World!");
    QLabel* label2 = new QLabel("Qt Programming");
    QLabel* label3 = new QLabel("Maps in Qt");

    labelsMap.insert("label_hello", label1);
    labelsMap.insert("label_qt", label2);
    labelsMap.insert("label_map", label3);

    qDebug() << "マップ内のラベルテキスト:";
    qDebug() << "label_hello:" << labelsMap.value("label_hello")->text();
    qDebug() << "label_qt:" << labelsMap.value("label_qt")->text();

    qDebug() << "\n--- ポインタの上書きとメモリリークの可能性 ---";
    // label_hello を新しいQLabelで上書き
    QLabel* newLabel = new QLabel("New Hello Message");
    // QMap::insert() は古いポインタ (label1) を自動でdeleteしない!
    // ここで label1 が指すメモリはリークする可能性がある
    labelsMap.insert("label_hello", newLabel);

    qDebug() << "上書き後の label_hello:" << labelsMap.value("label_hello")->text(); // New Hello Message

    qDebug() << "\n--- QMap破棄前の手動メモリ解放 ---";
    // QMapがスコープを抜ける前に、格納された全てのQLabelオブジェクトをdeleteする必要がある
    for (QLabel* label : labelsMap.values()) {
        delete label;
    }
    labelsMap.clear(); // オブジェクトをdeleteした後、マップをクリア

    return a.exec();
}

解説:

  • このメモリリークの問題を避けるために、実際にはQSharedPointerstd::unique_ptrなどのスマートポインタを使用することが強く推奨されます。
  • 重要な注意点: ポインタを格納した場合、QMap::insert()で上書きしたり、QMapが破棄されたりしても、ポインタが指す先のメモリは自動的に解放されません。上記の例では、objMap.insert(1, new MyObject("Overwritten First"));の前に、古いオブジェクトをdeleteする必要があります。
  • QLabel* のようにポインタを格納しています。

前述のメモリリークの問題を解決するために、QSharedPointerを使用する例です。

#include <QCoreApplication>
#include <QDebug>
#include <QMap>
#include <QSharedPointer> // スマートポインタ

// 簡単なカスタムクラス
class MyData {
public:
    QString name;
    int value;

    MyData(const QString& n, int v) : name(n), value(v) {
        qDebug() << "MyData created:" << name;
    }
    ~MyData() {
        qDebug() << "MyData destroyed:" << name;
    }
};

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

    // QMapの値をQSharedPointerでラップする
    QMap<int, QSharedPointer<MyData>> dataMap;

    qDebug() << "--- データ挿入 (QSharedPointer利用) ---";
    dataMap.insert(1, QSharedPointer<MyData>(new MyData("First Data", 100)));
    dataMap.insert(2, QSharedPointer<MyData>(new MyData("Second Data", 200)));

    qDebug() << "現在のマップ内容:";
    qDebug() << "Key 1 Name:" << dataMap.value(1)->name;
    qDebug() << "Key 2 Name:" << dataMap.value(2)->name;

    qDebug() << "\n--- 既存のキーを上書き (自動解放) ---";
    // キー1のデータを新しいオブジェクトで上書き
    // 古い "First Data" オブジェクトは、参照カウンタが0になるため自動的に破棄される
    dataMap.insert(1, QSharedPointer<MyData>(new MyData("Overwritten Data", 150)));

    qDebug() << "上書き後のマップ内容:";
    qDebug() << "Key 1 Name:" << dataMap.value(1)->name; // Overwritten Data

    qDebug() << "\n--- マップのクリア (残りのオブジェクトが自動解放) ---";
    // dataMapがクリアされると、残りのQSharedPointerの参照カウンタが0になり
    // それらが指すMyDataオブジェクトも自動的に破棄される
    dataMap.clear();

    qDebug() << "--- アプリケーション終了 ---";
    return a.exec();
}

解説:

  • dataMap.clear();が呼ばれたり、dataMapがスコープを抜けて破棄されたりする際にも、残りのMyDataオブジェクトが自動的に解放されます。
  • dataMap.insert(1, QSharedPointer<MyData>(new MyData("Overwritten Data", 150)));の行で古いデータが新しいデータで上書きされる際、古いQSharedPointerは参照を失い、参照カウンタが0になるため、自動的にMyData("First Data", 100)のデストラクタが呼ばれ、メモリが解放されます。
  • QSharedPointer<MyData>を値の型として使用することで、MyDataオブジェクトのメモリ管理をQSharedPointerに任せることができます。


QMap::operator[] を使用する

最も一般的な代替手段であり、非常に頻繁に使用されます。

どのような場合に使うか

  • コードを簡潔にしたい場合。
  • 新しいキーを追加したい場合: キーが存在しない場合、operator[]はそのキーでデフォルト構築された値を挿入し、その値への参照を返します。
  • 既存のキーの値を更新したい場合: キーが存在する場合、operator[]は値への参照を返すため、直接値を変更できます。

insert() との違い

  • operator[]は、キーが存在しない場合に値のデフォルトコンストラクタを呼び出します。デフォルトコンストラクタを持たないカスタム型を値として使用する場合、コンパイルエラーになることがあります。
  • insert()は常に新しい値を挿入しようとしますが、キーが既存の場合は上書きします。

コード例

#include <QCoreApplication>
#include <QDebug>
#include <QMap>

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

    QMap<QString, int> scores;

    // 新しいキーを追加
    scores["Alice"] = 90; // Aliceが存在しないため、新しい要素が追加される
    scores["Bob"] = 85;

    qDebug() << "初期のスコア:" << scores; // QMap(("Alice", 90), ("Bob", 85))

    // 既存のキーの値を更新
    scores["Alice"] = 95; // Aliceが存在するため、値が95に更新される

    qDebug() << "Aliceのスコア更新後:" << scores; // QMap(("Alice", 95), ("Bob", 85))

    return a.exec();
}

QMultiMap::insert() を使用する

一つのキーに対して複数の値を関連付けたい場合にQMapの代替としてQMultiMapが使われます。QMultiMapinsert()メソッドは、QMapのように既存の値を上書きしません。

  • 一つのキーに複数の値(例:ある学生の複数の試験の点数、ある商品の複数のタグなど)を格納したい場合。

QMap::insert() との違い

  • QMap::insert()はキーが重複した場合に値を上書きするのに対し、QMultiMap::insert()は重複したキーでも新しい値を既存の値のコレクションに追加します。
#include <QCoreApplication>
#include <QDebug>
#include <QMultiMap>

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

    QMultiMap<QString, int> studentGrades;

    studentGrades.insert("John", 80);
    studentGrades.insert("Jane", 90);
    studentGrades.insert("John", 75); // "John" に2つ目の成績を追加
    studentGrades.insert("Jane", 95); // "Jane" に2つ目の成績を追加

    qDebug() << "Johnの成績:" << studentGrades.values("John"); // (80, 75)
    qDebug() << "Janeの成績:" << studentGrades.values("Jane"); // (90, 95)

    return a.exec();
}

QMap::unite() を使用する(他のマップとの結合)

既存のQMapの要素を別のQMapに効率的に結合したい場合に使用します。

  • 複数のQMapの内容を一つのQMapに統合したい場合。
  • unite()は、結合元マップのキーが結合先マップに既に存在する場合、その値を上書きします(QMap::insert()と同じ挙動)。
  • insert()が単一のキーと値を扱うのに対し、unite()はマップ全体を対象とします。
#include <QCoreApplication>
#include <QDebug>
#include <QMap>

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

    QMap<QString, int> map1;
    map1.insert("A", 10);
    map1.insert("B", 20);

    QMap<QString, int> map2;
    map2.insert("B", 25); // map1にも存在するキー
    map2.insert("C", 30);

    qDebug() << "map1 (結合前):" << map1; // QMap(("A", 10), ("B", 20))
    qDebug() << "map2:" << map2; // QMap(("B", 25), ("C", 30))

    map1.unite(map2); // map2の要素をmap1に結合

    qDebug() << "map1 (結合後):" << map1;
    // 出力: QMap(("A", 10), ("B", 25), ("C", 30))
    // "B"の値がmap2の25で上書きされていることに注目

    return a.exec();
}

QMap::find() と QMap::insert() の組み合わせ

キーが存在する場合にのみ値を更新し、存在しない場合に新しい値を挿入したいが、operator[]のデフォルト構築の挙動を避けたい場合などに使います。

  • 値の型がデフォルトコンストラクタを持たない場合。
  • キーの存在を確認した上で、条件付きで値の更新または挿入を行いたい場合。
  • find()は、キーが存在する場合にイテレータを返します。これにより、値にアクセスして変更したり、新しい要素を挿入する前に既存の要素を削除したりできます。
  • insert()だけでは、キーの存在確認と条件分岐ができません。
#include <QCoreApplication>
#include <QDebug>
#include <QMap>

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

    QMap<QString, int> data;

    data.insert("Item1", 100);

    // "Item1" が存在するか確認し、存在すれば値を更新、なければ新しい値を挿入
    QMap<QString, int>::iterator it = data.find("Item1");
    if (it != data.end()) {
        // キーが存在する場合、値を更新
        it.value() = 120;
        qDebug() << "Item1 の値を更新しました。";
    } else {
        // キーが存在しない場合、新しい値を挿入
        data.insert("Item1", 50);
        qDebug() << "Item1 を新しく挿入しました。";
    }

    qDebug() << "data:" << data; // QMap(("Item1", 120))

    // "Item2" は存在しないため、新しく挿入される
    it = data.find("Item2");
    if (it != data.end()) {
        it.value() = 220;
        qDebug() << "Item2 の値を更新しました。";
    } else {
        data.insert("Item2", 200);
        qDebug() << "Item2 を新しく挿入しました。";
    }

    qDebug() << "data:" << data; // QMap(("Item1", 120), ("Item2", 200))

    return a.exec();
}

QHash::insert() を使用する(ハッシュマップ)

キーの順序が重要でない場合、QMapよりもQHashの方が高速なルックアップ(検索)を提供します。QHash::insert()QMap::insert()と同様に、キーが重複した場合に値を上書きします。

  • 非常に高速な検索性能が求められる場合。
  • 要素の順序が重要ではない場合(キーでソートする必要がない場合)。
  • QHashはイテレート時の要素の順序が保証されません。
  • QMapはバランスの取れたバイナリツリー(スキップリスト)に基づいており、要素はキーによって常にソートされて保持されます。検索時間はO(logn)です。
  • QHashはハッシュテーブルに基づいており、通常QMapよりも高速な平均O(1)の検索時間を提供します(最悪ケースはO(n))。
#include <QCoreApplication>
#include <QDebug>
#include <QHash> // QHashを使用

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

    // QHashを宣言: キーはQString、値はint
    QHash<QString, int> settings;

    qDebug() << "--- QHashへの挿入 ---";
    settings.insert("FontSize", 12);
    settings.insert("Theme", 1); // 1: Dark, 2: Light
    settings.insert("Language", 0); // 0: English, 1: Japanese

    qDebug() << "現在のQHash:" << settings;
    // QMapと異なり、出力順序は予測できない (内部のハッシュ値に基づく)

    qDebug() << "\n--- 既存のキーを上書き (QHash) ---";
    settings.insert("FontSize", 14); // FontSizeが12から14に上書きされる

    qDebug() << "FontSize更新後:" << settings;
    qDebug() << "新しいFontSize:" << settings.value("FontSize"); // 14

    return a.exec();
}

Qtアプリケーションでも、Qt独自のコンテナではなく、C++標準ライブラリのコンテナを使用することが可能です。

  • Qt以外のプラットフォームでもコードの移植性を高めたい場合。
  • プロジェクト全体でC++標準ライブラリの使用を推奨している場合。
  • std::unordered_map::insert()は、QHash::insert()と同様に、キーが重複した場合に値を上書きします。
  • std::map::insert()は、キーが既に存在する場合、新しい値を挿入せず、既存の要素を指すイテレータとfalseのペアを返します。値を上書きしたい場合は、operator[]を使用するか、find()で要素を見つけてから値を変更する必要があります。

コード例 (std::map)

#include <QCoreApplication>
#include <QDebug>
#include <map> // std::mapを使用

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

    std::map<QString, int> inventory;

    // insert() は std::pair<iterator, bool> を返す
    // bool は挿入が成功したかどうか (true=新規挿入、false=既に存在)
    auto result1 = inventory.insert({"Apple", 10}); // C++11 initializer list
    if (result1.second) {
        qDebug() << "Apple を挿入しました。在庫:" << result1.first->second;
    }

    auto result2 = inventory.insert({"Banana", 20});
    if (result2.second) {
        qDebug() << "Banana を挿入しました。在庫:" << result2.first->second;
    }

    // 既存のキーを insert() で挿入しようとすると、挿入は行われない
    auto result3 = inventory.insert({"Apple", 15});
    if (!result3.second) {
        qDebug() << "Apple は既に存在します。上書きされませんでした。現在の在庫:" << result3.first->second; // 10
    }

    // std::mapで値を上書きする場合は operator[] を使う
    inventory["Apple"] = 15;
    qDebug() << "Apple の在庫を更新しました:" << inventory["Apple"]; // 15

    qDebug() << "現在の在庫:";
    for (const auto& pair : inventory) {
        qDebug() << pair.first << ":" << pair.second;
    }

    return a.exec();
}

QMap::insert()は特定のニーズには最適ですが、上記の代替方法も検討することで、より適切なコンテナ選択やコードの記述が可能になります。

  • std::map::insert() / std::unordered_map::insert(): C++標準ライブラリのコンテナを使用したい場合。特にstd::map::insert()は、既存のキーでは上書きを行わない点でQMap::insert()と異なります。
  • QHash::insert(): キーの順序が重要でなく、より高速な検索が必要な場合。
  • QMap::find() + QMap::insert(): キーの存在を確認し、条件に基づいて挿入または更新を制御したい場合。
  • QMap::unite(): 既存のマップの内容を別のマップに結合したい場合。
  • QMultiMap::insert(): 1つのキーに複数の値を関連付けたい場合。
  • QMap::operator[]: 最も手軽な挿入/更新方法。キーが存在しない場合にデフォルトコンストラクタが呼ばれる点に注意。