QtのQMap::lowerBound()でつまずかない!エラー例と解決策

2025-05-27

<Key, T>::iterator QMap::lowerBound(const Key &key) とは

QMapは、キーと値のペアを格納し、キーに基づいて要素をソートして保持するQtのコンテナクラスです。std::mapに似た機能を提供しますが、Qt独自の機能や最適化が含まれています。

lowerBound() メソッドは、指定されたkey以上である最初の要素を指すイテレータを返します。つまり、以下の条件を満たす要素を検索します。

  1. キーがkeyと等しい要素があれば、その中で最初の要素を指すイテレータを返します。
  2. キーがkeyと等しい要素がなければ、keyよりも大きいキーを持つ最初の要素を指すイテレータを返します。

もし、指定されたkey以上のキーを持つ要素がQMap内に一つも存在しない場合、QMap::end()と同じイテレータを返します。

テンプレート引数について

  • iterator: QMapの要素を指すためのSTLスタイルのイテレータです。lowerBound()は非constなイテレータを返すため、指し示された要素の値を変更できます。もし要素の値を変更するつもりがなく、constQMapに対して呼び出す場合は、const_iteratorが返されます。
  • T: QMapに格納される値の型です。
  • Key: QMapに格納されるキーの型です。この型はoperator<()を実装している必要があります。QMapはこの演算子を使って要素をソートします。

戻り値

  • 条件を満たす要素が存在しない場合は、QMap::end()(またはQMap::constEnd())と同じイテレータ。
  • 条件を満たす要素を指すQMap::iterator(またはQMap::const_iterator)。

時間計算量 (Complexity)

QMap::lowerBound()の平均的な時間計算量は、O(logN) です。ここで、N はQMap内の要素数です。これは、QMapが内部的に平衡二分探索木(Red-Black Treeなど)に似たデータ構造を使用しているため、効率的な検索が可能です。

使用例

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

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

    QMap<int, QString> students;
    students.insert(101, "Alice");
    students.insert(105, "Bob");
    students.insert(110, "Charlie");
    students.insert(115, "David");

    qDebug() << "--- lowerBound() の使用例 ---";

    // ケース1: キーが完全に一致する場合
    int searchKey1 = 105;
    QMap<int, QString>::iterator it1 = students.lowerBound(searchKey1);
    if (it1 != students.end()) {
        qDebug() << "キー" << searchKey1 << "以上の最初の要素:"
                 << "Key =" << it1.key() << ", Value =" << it1.value();
    } else {
        qDebug() << "キー" << searchKey1 << "以上の要素は見つかりませんでした。";
    }
    // 出力例: キー 105 以上の最初の要素: Key = 105 , Value = "Bob"

    // ケース2: キーが一致せず、より大きいキーが見つかる場合
    int searchKey2 = 107;
    QMap<int, QString>::iterator it2 = students.lowerBound(searchKey2);
    if (it2 != students.end()) {
        qDebug() << "キー" << searchKey2 << "以上の最初の要素:"
                 << "Key =" << it2.key() << ", Value =" << it2.value();
    } else {
        qDebug() << "キー" << searchKey2 << "以上の要素は見つかりませんでした。";
    }
    // 出力例: キー 107 以上の最初の要素: Key = 110 , Value = "Charlie"

    // ケース3: 検索キーがすべての要素より大きい場合
    int searchKey3 = 120;
    QMap<int, QString>::iterator it3 = students.lowerBound(searchKey3);
    if (it3 != students.end()) {
        qDebug() << "キー" << searchKey3 << "以上の最初の要素:"
                 << "Key =" << it3.key() << ", Value =" << it3.value();
    } else {
        qDebug() << "キー" << searchKey3 << "以上の要素は見つかりませんでした。";
    }
    // 出力例: キー 120 以上の要素は見つかりませんでした。 (it3 は end() と等しい)

    // ケース4: 複数の同じキーがある場合 (QMapは通常1つのキーに1つの値ですが、QMultiMap の場合は異なる)
    // QMapではキーは一意なので、このケースは QMap では通常発生しませんが、
    // QMultiMap::lowerBound() の場合は、同じキーを持つ最初の要素を返します。
    // QMapでは、同じキーをinsertすると古い値が新しい値で上書きされます。

    return a.exec();
}
  • equal_range(const Key &key): lowerBound(key)upperBound(key)のペアをQPairで返します。これにより、特定のキーを持つすべての要素(QMultiMapの場合)または特定のキー以上の要素の範囲を効率的に取得できます。
  • upperBound(const Key &key): keyよりも大きい最初の要素を指すイテレータを返します。lowerBound()が「以上」であるのに対し、upperBound()は「より大きい」という厳密な条件です。lowerBound()upperBound()の間に存在する要素は、キーが指定されたkeyと等しい(またはkeyよりも大きいがupperBoundが指す要素より小さい)範囲を示します。
  • find(const Key &key): keyと完全に一致する要素を検索します。見つからなければend()を返します。lowerBound()とは異なり、「以上」の条件ではなく「完全一致」を求めます。


イテレータが無効な位置を指している

エラー/症状
lowerBound()が返したイテレータをdereference(*itit.key()it.value()など)しようとした際に、クラッシュ(Segmentation Faultなど)が発生する。

原因
lowerBound()は、指定されたキー以上の要素が見つからなかった場合、QMap::end()(またはQMap::constEnd())を返します。このend()イテレータは、マップの「1つ後ろ」の仮想的な位置を指しており、dereferenceすることはできません。

トラブルシューティング
lowerBound()の戻り値を常にQMap::end()と比較して、有効なイテレータが返されたかどうかを確認する必要があります。

QMap<int, QString> myMap;
// ... マップにデータを追加 ...

int searchKey = 100;
QMap<int, QString>::iterator it = myMap.lowerBound(searchKey);

if (it != myMap.end()) { // ★ここが重要!
    // 有効な要素が見つかった場合のみ、アクセスする
    qDebug() << "Found: Key =" << it.key() << ", Value =" << it.value();
} else {
    qDebug() << "Key" << searchKey << "以上の要素は見つかりませんでした。";
}

キーの比較演算子(operator<)が正しく実装されていない

エラー/症状
QMapは内部的にキーをソートして保持するため、キーの型には比較演算子operator<()が実装されている必要があります。これが正しく実装されていない場合、

  • マップの内部構造が壊れ、クラッシュや不正な動作を引き起こす可能性がある。
  • コンパイルは通るが、lowerBound()を含む検索操作の結果が予期しないものになる。
  • コンパイルエラー(no match for 'operator<'など)が発生する。

原因
カスタムクラスや構造体をQMapのキーとして使用する場合、その型に対してグローバルなoperator<()関数、またはメンバー関数としてoperator<()を定義する必要があります。QMapは、Key型のオブジェクト同士が厳密な弱順序(Strict Weak Ordering)を満たすことを期待しています。

トラブルシューティング
カスタムキー型MyKeyがある場合、以下のようにoperator<()を実装します。

// 例: カスタムキー構造体
struct MyKey {
    int id;
    QString name;

    // operator< の実装
    bool operator<(const MyKey& other) const {
        if (id != other.id) {
            return id < other.id;
        }
        return name < other.name; // IDが同じ場合は名前で比較
    }
};

QMap<MyKey, int> myCustomMap;
MyKey key1 = {1, "Alpha"};
MyKey key2 = {2, "Beta"};

myCustomMap.insert(key1, 10);
myCustomMap.insert(key2, 20);

MyKey searchKey = {1, "Bravo"};
QMap<MyKey, int>::iterator it = myCustomMap.lowerBound(searchKey);
// searchKey ({1, "Bravo"}) は {1, "Alpha"} より大きいので、{2, "Beta"} が見つかる
if (it != myCustomMap.end()) {
    qDebug() << "Found custom key: ID =" << it.key().id
             << ", Name =" << it.key().name
             << ", Value =" << it.value();
}

特に、浮動小数点数(float, double)をキーに使う場合は注意が必要です。浮動小数点数の比較は精度問題を引き起こす可能性があるため、通常は推奨されません。

constと非constイテレータの不一致

エラー/症状
QMapconst参照で渡された場合や、constメンバ関数内でlowerBound()を呼び出すと、QMap::const_iteratorが返されます。これを非constQMap::iterator変数に代入しようとすると、コンパイルエラーが発生します。

void processMap(const QMap<int, QString>& dataMap) {
    int searchKey = 50;
    // QMap::lowerBound()はconst QMapに対して呼び出されるとconst_iteratorを返す
    // QMap<int, QString>::iterator it = dataMap.lowerBound(searchKey); // ★コンパイルエラー!
    QMap<int, QString>::const_iterator it = dataMap.lowerBound(searchKey); // OK
    // ...
}

void modifyMap(QMap<int, QString>& dataMap) {
    int searchKey = 50;
    QMap<int, QString>::iterator it = dataMap.lowerBound(searchKey); // OK
    // ...
    // it->setValue("New Value"); // 非constイテレータなので値を変更できる
}

トラブルシューティング

  • autoキーワードを使用すると、コンパイラが自動的に正しいイテレータ型を推論してくれるため、この種のエラーを回避できます。
  • constオブジェクトに対してlowerBound()を呼び出す場合は、戻り値の型をQMap::const_iteratorで受ける。
void processMap(const QMap<int, QString>& dataMap) {
    int searchKey = 50;
    auto it = dataMap.lowerBound(searchKey); // const_iterator が推論される
    // ...
}

void modifyMap(QMap<int, QString>& dataMap) {
    int searchKey = 50;
    auto it = dataMap.lowerBound(searchKey); // iterator が推論される
    // ...
}

範囲の誤解

エラー/症状
lowerBound()が返したイテレータから反復処理を開始したが、期待する要素が含まれていなかったり、範囲がずれていたりする。

原因
lowerBound(key)は「key以上」の最初の要素を指します。もし「key完全に一致する要素のみ」を探している場合は、find()を使うべきです。また、「keyより大きい要素」を探している場合は、upperBound()を使うべきです。

トラブルシューティング

  • 特定のキーより大きい要素から始まる範囲の反復
    upperBound(key)からend()までをループします。
  • 特定のキーから始まる範囲の反復
    lowerBound(key)からend()までをループします。
  • 特定のキーの存在確認やその要素へのアクセス
    map.contains(key)map.find(key)を使用します。
QMap<int, QString> myMap = {{10, "A"}, {20, "B"}, {30, "C"}, {40, "D"}};

// キー20以上から最後までをイテレート
qDebug() << "--- Key >= 20 ---";
for (auto it = myMap.lowerBound(20); it != myMap.end(); ++it) {
    qDebug() << it.key() << ":" << it.value();
}
// 出力:
// 20 : "B"
// 30 : "C"
// 40 : "D"

// キー20より大きい要素から最後までをイテレート
qDebug() << "--- Key > 20 ---";
for (auto it = myMap.upperBound(20); it != myMap.end(); ++it) {
    qDebug() << it.key() << ":" << it.value();
}
// 出力:
// 30 : "C"
// 40 : "D"

QMapが空である場合

エラー/症状
空のQMapに対してlowerBound()を呼び出した後、イテレータをdereferenceしようとしてクラッシュする。

原因
空のQMapに対してlowerBound()を呼び出すと、常にQMap::end()が返されます。これは有効な要素を指していないため、dereferenceしてはいけません。

トラブルシューティング
これも「イテレータが無効な位置を指している」ケースと同様に、it != myMap.end()で確認すれば問題ありません。ただし、マップが空かどうかを事前にmyMap.isEmpty()で確認することもできます。

QMap<int, QString> emptyMap;
auto it = emptyMap.lowerBound(10);
if (it != emptyMap.end()) {
    // このブロックは実行されない
    qDebug() << "Something found in empty map!";
} else {
    qDebug() << "Empty map, lowerBound returns end().";
}

QMap::lowerBound()を使う上でのトラブルシューティングの要点は、以下の点に集約されます。

  1. 常にend()イテレータとの比較を行うこと。
  2. カスタムキーを使用する場合は、operator<()が厳密な弱順序を満たすように正しく実装されていることを確認すること。
  3. const性を意識し、適切なイテレータ型(iteratorまたはconst_iterator)を使用すること。autoの活用を検討すること。
  4. lowerBound()が「以上」の意味であることを理解し、必要に応じてfind()upperBound()と使い分けること。


例1:基本的な使用法と存在チェック

最も基本的なlowerBound()の使用法です。指定したキー以上の要素が見つかる場合と見つからない場合の両方を示します。

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

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

    QMap<int, QString> scores;
    scores.insert(50, "Alice");
    scores.insert(65, "Bob");
    scores.insert(70, "Charlie");
    scores.insert(85, "David");
    scores.insert(90, "Eve");

    qDebug() << "--- 基本的な lowerBound() の使用例 ---";

    // ケースA: キーがQMapに存在する (50点以上の最初の要素)
    int searchKeyA = 50;
    QMap<int, QString>::iterator itA = scores.lowerBound(searchKeyA);
    if (itA != scores.end()) {
        qDebug() << "検索キー" << searchKeyA << "点以上: Key =" << itA.key() << ", Value =" << itA.value();
    } else {
        qDebug() << "検索キー" << searchKeyA << "点以上の要素は見つかりませんでした。";
    }
    // 出力: 検索キー 50 点以上: Key = 50 , Value = "Alice"

    // ケースB: キーがQMapに存在しないが、より大きい要素が存在する (60点以上の最初の要素)
    int searchKeyB = 60;
    QMap<int, QString>::iterator itB = scores.lowerBound(searchKeyB);
    if (itB != scores.end()) {
        qDebug() << "検索キー" << searchKeyB << "点以上: Key =" << itB.key() << ", Value =" << itB.value();
    } else {
        qDebug() << "検索キー" << searchKeyB << "点以上の要素は見つかりませんでした。";
    }
    // 出力: 検索キー 60 点以上: Key = 65 , Value = "Bob"

    // ケースC: 検索キーがすべての要素より大きい (95点以上の最初の要素)
    int searchKeyC = 95;
    QMap<int, QString>::iterator itC = scores.lowerBound(searchKeyC);
    if (itC != scores.end()) {
        qDebug() << "検索キー" << searchKeyC << "点以上: Key =" << itC.key() << ", Value =" << itC.value();
    } else {
        qDebug() << "検索キー" << searchKeyC << "点以上の要素は見つかりませんでした。";
    }
    // 出力: 検索キー 95 点以上の要素は見つかりませんでした。

    return a.exec();
}

例2:特定のキー以上の範囲をイテレートする

lowerBound()は、特定のキーから始まる範囲の要素を反復処理するのに非常に便利です。

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

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

    QMap<QString, double> productPrices;
    productPrices.insert("Apple", 1.20);
    productPrices.insert("Banana", 0.75);
    productPrices.insert("Cherry", 2.50);
    productPrices.insert("Date", 3.00);
    productPrices.insert("Elderberry", 4.50);
    productPrices.insert("Fig", 1.80);

    qDebug() << "--- 'C'以降のプロダクトをリストアップ ---";

    QString startKey = "C";
    // lowerBound() で 'C' 以上の最初の要素を見つける
    QMap<QString, double>::iterator it = productPrices.lowerBound(startKey);

    qDebug() << "製品名 (アルファベット順 C から):";
    while (it != productPrices.end()) {
        qDebug() << "  " << it.key() << ": $" << it.value();
        ++it; // 次の要素へ進む
    }
    // 出力:
    // 製品名 (アルファベット順 C から):
    //   Cherry: $ 2.5
    //   Date: $ 3
    //   Elderberry: $ 4.5
    //   Fig: $ 1.8

    qDebug() << "--- 'B'から'E'までのプロダクトをリストアップ (upperBoundと組み合わせて) ---";

    QString rangeStartKey = "B";
    QString rangeEndKey = "E"; // 'E'より小さい要素までを対象にするため、upperBoundと組み合わせる

    // 'B'以上の最初の要素
    QMap<QString, double>::iterator itStart = productPrices.lowerBound(rangeStartKey);
    // 'E'より大きい最初の要素 (この手前までが範囲)
    QMap<QString, double>::iterator itEnd = productPrices.upperBound(rangeEndKey);

    qDebug() << "製品名 ('B'から'E'まで):";
    while (itStart != itEnd) {
        qDebug() << "  " << itStart.key() << ": $" << itStart.value();
        ++itStart;
    }
    // 出力:
    // 製品名 ('B'から'E'まで):
    //   Banana: $ 0.75
    //   Cherry: $ 2.5
    //   Date: $ 3

    return a.exec();
}

例3:カスタムクラスをキーとするQMapでの使用

カスタムクラスをQMapのキーとして使用する場合、そのクラスにoperator<()を実装する必要があります。

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

// 従業員情報を表すカスタム構造体
struct EmployeeId {
    int departmentId;
    int employeeNumber;

    // operator< を実装する (QMapがキーをソートするために必要)
    bool operator<(const EmployeeId& other) const {
        if (departmentId != other.departmentId) {
            return departmentId < other.departmentId;
        }
        return employeeNumber < other.employeeNumber;
    }

    // デバッグ表示用の関数(オプション)
    QString toString() const {
        return QString("Dept:%1, EmpNo:%2").arg(departmentId).arg(employeeNumber);
    }
};

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

    QMap<EmployeeId, QString> employees;
    employees.insert({10, 101}, "Alice Smith");
    employees.insert({10, 105}, "Bob Johnson");
    employees.insert({20, 201}, "Charlie Brown");
    employees.insert({20, 203}, "David Lee");
    employees.insert({30, 301}, "Eve Davis");

    qDebug() << "--- カスタムキーでの lowerBound() 使用例 ---";

    // 検索キー: 部門10、従業員番号103以上
    EmployeeId searchId = {10, 103};
    QMap<EmployeeId, QString>::iterator it = employees.lowerBound(searchId);

    if (it != employees.end()) {
        qDebug() << "検索ID" << searchId.toString() << "以上の最初の従業員:";
        qDebug() << "  ID:" << it.key().toString() << ", 名前:" << it.value();
    } else {
        qDebug() << "検索ID" << searchId.toString() << "以上の従業員は見つかりませんでした。";
    }
    // 出力:
    // 検索ID Dept:10, EmpNo:103 以上の最初の従業員:
    //   ID: Dept:10, EmpNo:105 , 名前: Bob Johnson

    qDebug() << "\n--- 特定の部門の従業員をリストアップ (lowerBoundとupperBoundの組み合わせ) ---";

    int targetDepartment = 20;
    // 部門IDが20の最初の従業員(実際には存在しなくても、その位置を指す)
    EmployeeId deptStart = {targetDepartment, 0}; // 従業員番号は最小値で設定
    // 部門IDが20の最後の従業員の次(部門IDが21の最初の従業員の位置)
    EmployeeId deptEnd = {targetDepartment + 1, 0};

    auto itDeptStart = employees.lowerBound(deptStart);
    auto itDeptEnd = employees.lowerBound(deptEnd); // あるいは employees.upperBound({targetDepartment, INT_MAX});

    qDebug() << "部門" << targetDepartment << "の従業員:";
    while (itDeptStart != itDeptEnd) {
        qDebug() << "  ID:" << itDeptStart.key().toString() << ", 名前:" << itDeptStart.value();
        ++itDeptStart;
    }
    // 出力:
    // 部門 20 の従業員:
    //   ID: Dept:20, EmpNo:201 , 名前: Charlie Brown
    //   ID: Dept:20, EmpNo:203 , 名前: David Lee

    return a.exec();
}

例4:const QMapconst_iterator

constQMapオブジェクトに対してlowerBound()を呼び出すと、QMap::const_iteratorが返されます。autoキーワードを使うと、コンパイラが適切な型を推論してくれます。

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

void printGreaterThanOrEqual(const QMap<int, QString>& dataMap, int searchVal) {
    qDebug() << "\n--- const QMap での検索 (キー" << searchVal << "以上) ---";
    // const QMapに対してlowerBound()を呼び出すと、const_iteratorが返される。
    // auto を使うと型推論で自動的に const_iterator になる。
    auto it = dataMap.lowerBound(searchVal);

    if (it != dataMap.constEnd()) { // const_iteratorの場合はconstEnd()と比較
        qDebug() << "見つかった要素: Key =" << it.key() << ", Value =" << it.value();
    } else {
        qDebug() << "要素は見つかりませんでした。";
    }

    qDebug() << "--- ループで表示 ---";
    for (auto loopIt = dataMap.lowerBound(searchVal); loopIt != dataMap.constEnd(); ++loopIt) {
        qDebug() << "  " << loopIt.key() << ":" << loopIt.value();
        // loopIt->setValue("新しい値"); // const_iterator なので変更不可 (コンパイルエラー)
    }
}

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

    QMap<int, QString> myMap;
    myMap.insert(10, "First");
    myMap.insert(30, "Third");
    myMap.insert(50, "Fifth");
    myMap.insert(70, "Seventh");

    printGreaterThanOrEqual(myMap, 40); // 40以上の要素を検索
    printGreaterThanOrEqual(myMap, 100); // 100以上の要素(存在しない)を検索

    return a.exec();
}


QMap::lowerBound()の代替を考える際、以下の点が重要になります。

  • メモリ使用量は?
  • パフォーマンス要件は? (検索速度、挿入速度など)
  • コンテナの順序性が必要か? (キーによるソート順が重要か)
  • 何を探しているか? (完全一致か、範囲か、最近傍か)

QMap の他のメンバー関数を使用する

QMap自身が提供する他のメソッドで、lowerBound()と似たような目的を達成できるものがあります。

a. QMap::find(const Key &key)
  • 使い分け
    特定のキーを持つ要素がマップに存在するかどうかを確認し、存在すればその要素にアクセスしたい場合に最適です。
  • 特徴
    lowerBound()が「以上」であるのに対し、find()は「完全一致」です。要素が見つかればその要素を指すイテレータを、見つからなければend()を返します。
  • 目的
    指定されたキーと完全に一致する要素を検索します。
QMap<int, QString> myMap = {{10, "A"}, {20, "B"}, {30, "C"}};

// キー20と完全に一致する要素を探す
auto it = myMap.find(20);
if (it != myMap.end()) {
    qDebug() << "Found (find):" << it.key() << ":" << it.value();
} else {
    qDebug() << "Not found (find): 20";
}

// キー25を探す(存在しない)
it = myMap.find(25);
if (it != myMap.end()) {
    qDebug() << "Found (find):" << it.key() << ":" << it.value();
} else {
    qDebug() << "Not found (find): 25";
}
b. QMap::upperBound(const Key &key)
  • 使い分け
    • 特定のキーを持つ要素の範囲を特定する際にlowerBound()と組み合わせて使用します。[lowerBound(key), upperBound(key))の範囲には、キーがkeyと等しいすべての要素が含まれます(QMapは通常キーが一意ですが、QMultiMapでは有効)。
    • 特定のキーより大きい最初の要素からループを開始したい場合。
  • 特徴
    lowerBound()が「以上」であるのに対し、upperBound()は「より大きい」という厳密な条件です。
  • 目的
    指定されたキーより大きい最初の要素を検索します。
QMap<int, QString> myMap = {{10, "A"}, {20, "B_1"}, {20, "B_2"}, {30, "C"}}; // QMapはキーが一意。QMultiMapの場合を想定

// キー20より大きい最初の要素
auto it = myMap.upperBound(20);
if (it != myMap.end()) {
    qDebug() << "Upper bound for 20:" << it.key() << ":" << it.value();
} else {
    qDebug() << "Upper bound not found for 20.";
}

// 20点のスコアを持つ生徒を探す (QMultiMapの例を想定)
// QMapではキーは一意なので、lowerBound(20)とupperBound(20)は同じ要素か、upperBoundが次の要素を指す
// QMapの場合:
// auto lower = myMap.lowerBound(20); // 20:"B_1" (もし20が複数あれば最初の20)
// auto upper = myMap.upperBound(20); // 30:"C"
// のため、[lower, upper) の範囲は 20:"B_1" の1つだけになる
c. QMap::equal_range(const Key &key) (Qt 5.0以降)
  • 特徴
    内部的にはlowerBound(key)upperBound(key)を呼び出した結果をペアで返します。
  • 目的
    指定されたキーと等しいすべての要素の範囲をQPair<iterator, iterator>として返します。これはstd::map::equal_range()と同じです。
QMap<int, QString> myMap = {{10, "A"}, {20, "B"}, {30, "C"}, {40, "D"}};

// キー20の範囲を検索
QPair<QMap<int, QString>::iterator, QMap<int, QString>::iterator> range = myMap.equal_range(20);

qDebug() << "--- equal_range() for key 20 ---";
for (auto it = range.first; it != range.second; ++it) {
    qDebug() << "  " << it.key() << ":" << it.value();
}
// 出力: 20 : "B" (QMapの場合)
d. QMap::contains(const Key &key)operator[]
  • 使い分け
    • 存在確認のみ
      contains()がシンプルで効率的です。

      if (myMap.contains(20)) {
          qDebug() << "Key 20 exists.";
      }
      
    • キーの存在が確実で、値にアクセスまたは変更したい場合
      operator[]は簡潔です。

      qDebug() << "Value for 20:" << myMap[20];
      myMap[20] = "New B"; // 値の変更
      
    • キーの存在が不確実で、新しい要素の挿入を避けたい場合
      find()またはvalue()オーバーロードを使用します。

      QString value = myMap.value(25, "Default Value"); // 25がなければ"Default Value"を返す
      qDebug() << "Value for 25 (using value()):" << value;
      
  • 特徴
    contains()はキーの存在をboolで返します。operator[]はキーに対応する値への参照を返しますが、キーが存在しない場合はデフォルトコンストラクトされた値を挿入してしまいます(非constマップの場合)。
  • 目的
    キーの存在確認と、キーによる値の取得。

他のQtコンテナを使用する

ユースケースによっては、QMapよりも他のコンテナが適している場合があります。

a. QHash (ハッシュテーブル)
  • 使い分け
    • キーの順序が重要ではない場合。
    • 最高のパフォーマンスで要素の検索や挿入を行いたい場合。
    • キーの型がoperator==()とグローバルなqHash()関数を実装している必要があります。
  • 特徴
    キーをソートして保持せず、ハッシュ値に基づいて要素を格納します。そのため、QMapに比べて平均的な検索、挿入、削除の時間がO(1) と非常に高速です。しかし、キーの順序は保証されません。lowerBound()のような順序に依存する操作は提供されません。
#include <QHash>
#include <QDebug>

int main() {
    QHash<QString, int> wordCounts;
    wordCounts.insert("apple", 5);
    wordCounts.insert("banana", 2);
    wordCounts.insert("cherry", 8);

    // QHashには lowerBound() はない
    // 特定のキーの存在確認
    if (wordCounts.contains("banana")) {
        qDebug() << "Banana count:" << wordCounts["banana"];
    }
    return 0;
}
b. QVector または QList をソートして使用する
  • 特徴
    内部で要素をソートされていない状態で保持し、必要に応じてソートできます。QVectorは連続したメモリに要素を格納し、QListは実装の詳細(配列ベースとリンクリストベースのハイブリッド)によってパフォーマンス特性が異なります。
#include <QCoreApplication>
#include <QDebug>
#include <QVector>
#include <algorithm> // for std::lower_bound

struct Product {
    QString name;
    double price;

    // ソートのための比較演算子(名前でソート)
    bool operator<(const Product& other) const {
        return name < other.name;
    }
};

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

    QVector<Product> products;
    products.append({"Banana", 0.75});
    products.append({"Apple", 1.20});
    products.append({"Cherry", 2.50});
    products.append({"Date", 3.00});

    // QVectorをソートする
    std::sort(products.begin(), products.end());

    qDebug() << "--- ソートされた QVector を std::lower_bound で検索 ---";
    // 検索キー
    Product searchProduct = {"Cherry", 0.0}; // priceは比較に影響しない

    // std::lower_bound を使用して検索
    auto it = std::lower_bound(products.begin(), products.end(), searchProduct);

    if (it != products.end()) {
        qDebug() << "Found (std::lower_bound):" << it->name << ":" << it->price;
    } else {
        qDebug() << "Not found (std::lower_bound):" << searchProduct.name;
    }
    // 出力: Found (std::lower_bound): Cherry : 2.5

    return a.exec();
}

標準C++ライブラリのコンテナとアルゴリズムを使用する

Qtアプリケーションであっても、必要に応じて標準C++ライブラリのコンテナやアルゴリズムを使用することは一般的です。

a. std::map
  • 使い分け
    • Qt固有の機能(Implicit Sharingなど)が不要な場合。
    • Qt以外のC++ライブラリとの連携がしやすい。
    • 標準C++の慣習に沿ったコードを書きたい場合。
  • 特徴
    QMapと非常に似ており、内部的に平衡二分探索木(通常は赤黒木)によってキーをソートして保持します。std::map::lower_bound()も同様の機能を提供します。
#include <iostream>
#include <map>
#include <string>
#include <algorithm> // for std::lower_bound if used with non-map containers

int main() {
    std::map<int, std::string> ages;
    ages[10] = "Child";
    ages[30] = "Adult";
    ages[60] = "Senior";

    // std::map::lower_bound() を使用
    auto it = ages.lower_bound(30);
    if (it != ages.end()) {
        std::cout << "std::map::lower_bound(30): " << it->first << " -> " << it->second << std::endl;
    }

    it = ages.lower_bound(45);
    if (it != ages.end()) {
        std::cout << "std::map::lower_bound(45): " << it->first << " -> " << it->second << std::endl;
    } else {
        std::cout << "std::map::lower_bound(45): Not found (returns end())" << std::endl;
    }

    return 0;
}
b. std::unordered_map (ハッシュテーブル)
  • 使い分け
    QHashと同様の考慮事項が適用されます。最高のパフォーマンスと引き換えに順序性を犠牲にできる場合に選択します。
  • 特徴
    QHashに相当します。キーの順序は保証されませんが、平均O(1)の検索、挿入、削除が可能です。
  • Qtのフレームワークに強く依存しない、純粋なC++のコンテナが必要な場合
    • std::mapstd::unordered_map を使用します。
  • 要素数が少なく、特定のキーでのソートや検索がたまにしか行われない場合、またはカスタムなソート順が必要な場合
    • QVector<QPair<Key, T>>QList<QPair<Key, T>> を使用し、必要に応じてstd::sortstd::lower_boundを組み合わせる ことを検討します。
  • キーの順序は不要で、単に高速なキーによる検索や挿入/削除が必要な場合
    • QHash または std::unordered_map を検討します。これらはQMapよりも高速です。
  • キーによるソート順序が必須で、効率的な範囲検索や隣接要素へのアクセスが必要な場合
    • QMap::lowerBound() が最も直接的で適切な選択肢です。
    • 関連してQMap::upperBound()QMap::equal_range()も活用します。