QList::indexOf()の落とし穴?Qtプログラミングでよくあるエラーと解決策

2025-06-06

この関数は、QListクラスのメンバー関数で、リスト内に特定の値が最初に現れるインデックス(位置)を検索するために使用されます。テンプレート関数であるため、ATというプレースホルダは、検索したい要素の型を表します。

各部分の説明

  • indexOf(): この関数は、QList内に特定の要素が最初に現れるインデックスを検索します。

  • QList: Qtの提供する汎用的なコンテナクラスの一つで、要素のリストを格納します。内部的には配列のように動作し、要素の挿入、削除、検索などの操作が可能です。

  • qsizetype: これは関数の戻り値の型です。qsizetypeはQtが定義する型で、通常は符号なし整数型(size_tなど)にマッピングされ、コレクションのサイズやインデックスを表すのに適しています。 indexOf()が要素を見つけた場合、その要素の0から始まるインデックスが返されます。 もし要素が見つからなかった場合、-1が返されます。

  • template <typename AT>: これは、この関数がテンプレート関数であることを示しています。ATは型パラメータ(または型引数)で、関数を呼び出す際に具体的な型(例: int, QString, MyClassなど)に置き換えられます。これにより、様々な型の要素を格納するQListに対して、汎用的にindexOf()を呼び出すことができます。

使い方

indexOf()関数は、引数として検索したい要素の値を取ります。

#include <QList>
#include <QString>
#include <QDebug>

int main() {
    QList<QString> list;
    list << "apple" << "banana" << "orange" << "apple" << "grape";

    // "banana" のインデックスを検索
    qsizetype index1 = list.indexOf("banana");
    qDebug() << "Index of 'banana':" << index1; // 出力: Index of 'banana': 1

    // "apple" の最初のインデックスを検索
    qsizetype index2 = list.indexOf("apple");
    qDebug() << "Index of 'apple':" << index2; // 出力: Index of 'apple': 0

    // "kiwi" (存在しない要素) のインデックスを検索
    qsizetype index3 = list.indexOf("kiwi");
    qDebug() << "Index of 'kiwi':" << index3; // 出力: Index of 'kiwi': -1

    QList<int> intList;
    intList << 10 << 20 << 30 << 20 << 40;

    // 20 の最初のインデックスを検索
    qsizetype index4 = intList.indexOf(20);
    qDebug() << "Index of 20:" << index4; // 出力: Index of 20: 1

    // 100 (存在しない要素) のインデックスを検索
    qsizetype index5 = intList.indexOf(100);
    qDebug() << "Index of 100:" << index5; // 出力: Index of 100: -1

    return 0;
}


要素が見つからない(戻り値が -1 になる)

これはエラーというよりは、indexOf() が要素を見つけられなかったことを示す正常な挙動です。しかし、開発者が「要素は存在すると確信していたのに!」となる場合は、以下の原因が考えられます。

  • 異なるオブジェクトインスタンス(ポインタ型やカスタムクラス): QList<MyClass*> のようにポインタを格納している場合、indexOf() はポインタのアドレス自体を比較します。つまり、異なるオブジェクトだが内容が同じ、という場合は見つけることができません。

    class MyObject {
    public:
        int id;
        MyObject(int i) : id(i) {}
    };
    
    QList<MyObject*> list;
    list << new MyObject(1) << new MyObject(2);
    
    MyObject* searchObj = new MyObject(1);
    qsizetype index = list.indexOf(searchObj); // -1 になる (アドレスが異なるため)
    delete searchObj; // 忘れずに解放
    

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

    • ポインタではなく、オブジェクトの値そのものを比較したい場合は、QList<MyClass> のように値渡しで格納することを検討します。ただし、MyClass が軽量な値型である必要があります。
    • ポインタリストの場合、カスタムの比較ロジックを持つループを使用して、オブジェクトの内容を比較します。
      qsizetype findMyObjectById(const QList<MyObject*>& list, int id) {
          for (qsizetype i = 0; i < list.size(); ++i) {
              if (list.at(i)->id == id) {
                  return i;
              }
          }
          return -1;
      }
      // ...
      qsizetype index = findMyObjectById(list, 1); // 0 になる
      
  • 隠れた空白文字や改行コード: 文字列の末尾や先頭に目に見えない空白文字(スペース、タブ、改行コード \n、キャリッジリターン \r など)が含まれている場合があります。

    QList<QString> list;
    list << "item1" << "item2\n" << "item3 "; // item2 に改行、item3 にスペース
    qsizetype index1 = list.indexOf("item2"); // -1 になる
    qsizetype index2 = list.indexOf("item3"); // -1 になる
    

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

    • デバッガを使用して、リスト内の文字列と検索文字列の正確な内容を確認します。
    • QString::trimmed() を使って、文字列の先頭と末尾の空白文字を削除することを検討します。
      QString searchString = "item2";
      for (qsizetype i = 0; i < list.size(); ++i) {
          if (list.at(i).trimmed() == searchString) {
              // 見つかった
          }
      }
      
  • 大文字・小文字の不一致 (QStringなどの文字列型): QString::indexOf() とは異なり、QList<QString>::indexOf() はデフォルトで大文字・小文字を区別します。

    QList<QString> list;
    list << "Apple" << "Banana";
    qsizetype index = list.indexOf("apple"); // -1 になる
    

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

    • 検索する文字列とリスト内の文字列が完全に一致しているか確認します。
    • 大文字・小文字を区別しない検索が必要な場合は、QList を直接 indexOf() で検索するのではなく、リストをループして各要素の QString::indexOf()Qt::CaseInsensitive オプション付きで呼び出す必要があります。
      qsizetype findCaseInsensitive(const QList<QString>& list, const QString& value) {
          for (qsizetype i = 0; i < list.size(); ++i) {
              if (list.at(i).compare(value, Qt::CaseInsensitive) == 0) {
                  return i;
              }
          }
          return -1;
      }
      // ...
      qsizetype index = findCaseInsensitive(list, "apple"); // 0 になる
      

カスタムクラスでのコンパイルエラー: operator== が定義されていない

QList::indexOf() は、リスト内の要素と検索する値を比較するために、operator==() を使用します。もしカスタムのクラスを QList に格納し、そのクラスに operator==() が適切に定義されていない場合、コンパイルエラーが発生します。

// エラー例: operator== がない MyCustomClass
class MyCustomClass {
public:
    int value;
    MyCustomClass(int v) : value(v) {}
};

QList<MyCustomClass> list;
list << MyCustomClass(10);
qsizetype index = list.indexOf(MyCustomClass(10)); // コンパイルエラー

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

  • operator==()const 正しく定義されている必要があります(つまり、比較対象のオブジェクトを変更しない)。コンパイルエラーで「const ではない引数が期待されている」のようなメッセージが出た場合、const 修飾子を正しく付与しているか確認してください。
  • QList に格納するカスタムクラスに対して、operator==() を非メンバ関数またはメンバ関数として定義します。
    // 解決策: operator== を定義
    class MyCustomClass {
    public:
        int value;
        MyCustomClass(int v) : value(v) {}
    
        // 非メンバ関数として operator== を定義する場合(推奨されることが多い)
        friend bool operator==(const MyCustomClass& lhs, const MyCustomClass& rhs) {
            return lhs.value == rhs.value;
        }
        // メンバ関数として定義する場合
        // bool operator==(const MyCustomClass& other) const {
        //     return value == other.value;
        // }
    };
    
    QList<MyCustomClass> list;
    list << MyCustomClass(10);
    qsizetype index = list.indexOf(MyCustomClass(10)); // 正常に動作
    

パフォーマンスの問題

QList::indexOf() は、要素を線形に(最初から順番に)検索します。そのため、リストのサイズが大きくなると検索に時間がかかり、アプリケーションのパフォーマンスに影響を与える可能性があります。

  • ポインタ型のリストでのデリファレンス: QList<MyClass*> のようにポインタを格納している場合、indexOf() はポインタのアドレスを比較します。しかし、前述のように要素の内容で検索したい場合は、手動でループして各ポインタをデリファレンスして比較する必要があります。この手動ループのオーバーヘッドがパフォーマンスに影響を与えることがあります。

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

    • これもQHashQMapで、キーをオブジェクトの内容から生成する(例えば、QHash<QString, MyObject*>QStringをIDの文字列にするなど)ことを検討します。
  • 大規模なリストでの頻繁な検索: 数千、数万といった要素を持つQListに対して、indexOf() を頻繁に呼び出すと、処理が遅くなります。これはO(N)の操作であるため、要素数に比例して検索時間が延びるからです。

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

    • QHash または QMap の使用を検討: 要素をキーとして高速に検索したい場合(O(1)またはO(logN))、QHash (ハッシュマップ) や QMap (ソートされたマップ) の使用が適しています。これらのコンテナはキーに基づいて高速に値をルックアップできます。
      // MyObject* を ID で検索したい場合
      QHash<int, MyObject*> idMap;
      MyObject* obj1 = new MyObject(1);
      MyObject* obj2 = new MyObject(2);
      idMap.insert(obj1->id, obj1);
      idMap.insert(obj2->id, obj2);
      
      if (idMap.contains(1)) {
          MyObject* foundObj = idMap.value(1); // 非常に高速
      }
      
    • 検索の開始位置の指定: indexOf(const T &value, qsizetype from = 0)from パラメータを使用して、検索を開始するインデックスを指定することで、不要な部分の検索をスキップできます。ただし、これは特定のユースケースに限られます。
    • リストのソートと二分探索: もしリストがソートされていることが保証できるなら、二分探索アルゴリズム(std::lower_bound など)を実装することで、より高速な検索(O(logN))が可能です。ただし、要素の挿入/削除が頻繁に行われる場合、ソート状態を維持するためのコストがかかります。


基本的な使い方(QString型の場合)

最も一般的な使い方は、QStringのリストで特定の文字列を探す場合です。

#include <QCoreApplication>
#include <QList>
#include <QString>
#include <QDebug> // デバッグ出力用

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

    QList<QString> fruits;
    fruits << "apple" << "banana" << "cherry" << "apple" << "date";

    qDebug() << "--- QStringの例 ---";

    // "banana" のインデックスを検索
    qsizetype indexBanana = fruits.indexOf("banana");
    if (indexBanana != -1) {
        qDebug() << "'banana' はインデックス" << indexBanana << "で見つかりました。"; // 出力: 'banana' はインデックス 1 で見つかりました。
    } else {
        qDebug() << "'banana' は見つかりませんでした。";
    }

    // "apple" の最初のインデックスを検索
    qsizetype indexApple = fruits.indexOf("apple");
    if (indexApple != -1) {
        qDebug() << "'apple' の最初の出現はインデックス" << indexApple << "です。"; // 出力: 'apple' の最初の出現はインデックス 0 です。
    } else {
        qDebug() << "'apple' は見つかりませんでした。";
    }

    // "grape" (存在しない要素) のインデックスを検索
    qsizetype indexGrape = fruits.indexOf("grape");
    if (indexGrape != -1) {
        qDebug() << "'grape' はインデックス" << indexGrape << "で見つかりました。";
    } else {
        qDebug() << "'grape' は見つかりませんでした。"; // 出力: 'grape' は見つかりませんでした。
    }

    // 検索開始位置を指定する例
    // インデックス1から"apple"を検索 (2つ目の"apple"を見つける)
    qsizetype indexAppleFrom1 = fruits.indexOf("apple", 1);
    if (indexAppleFrom1 != -1) {
        qDebug() << "インデックス1以降の'apple'の出現はインデックス" << indexAppleFrom1 << "です。"; // 出力: インデックス1以降の'apple'の出現はインデックス 3 です。
    } else {
        qDebug() << "インデックス1以降に'apple'は見つかりませんでした。";
    }

    return a.exec();
}

int型などの組み込み型の場合

intdoubleなどの組み込み型でも同様に使用できます。

#include <QCoreApplication>
#include <QList>
#include <QDebug>

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

    QList<int> numbers;
    numbers << 10 << 20 << 30 << 20 << 40;

    qDebug() << "\n--- intの例 ---";

    // 20 のインデックスを検索
    qsizetype index20 = numbers.indexOf(20);
    qDebug() << "20 の最初の出現はインデックス" << index20 << "です。"; // 出力: 20 の最初の出現はインデックス 1 です。

    // 100 (存在しない要素) のインデックスを検索
    qsizetype index100 = numbers.indexOf(100);
    qDebug() << "100 の出現はインデックス" << index100 << "です。"; // 出力: 100 の出現はインデックス -1 です。

    return a.exec();
}

カスタムクラスでindexOf()を使用する

カスタムクラスをQListに格納し、indexOf()を使用するには、そのカスタムクラスにoperator==()を定義する必要があります。indexOf()は内部でこのoperator==()を使用して要素を比較するからです。

#include <QCoreApplication>
#include <QList>
#include <QString>
#include <QDebug>

// カスタムクラスの定義
class MyObject {
public:
    int id;
    QString name;

    MyObject(int _id, const QString& _name) : id(_id), name(_name) {}

    // operator==() の定義
    // QList::indexOf() が内部でこの演算子を呼び出します。
    // 同じ id と name を持つ MyObject を「等しい」とみなすように定義します。
    friend bool operator==(const MyObject& lhs, const MyObject& rhs) {
        return lhs.id == rhs.id && lhs.name == rhs.name;
    }

    // operator!=() も定義しておくと良い(通常は operator==() の逆)
    friend bool operator!=(const MyObject& lhs, const MyObject& rhs) {
        return !(lhs == rhs);
    }
};

// MyObject を QDebug で出力するためのオーバーロード (オプションだが便利)
QDebug operator<<(QDebug debug, const MyObject& obj) {
    QDebugStateSaver saver(debug);
    debug.nospace() << "MyObject(id=" << obj.id << ", name='" << obj.name << "')";
    return debug;
}


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

    QList<MyObject> myObjects;
    myObjects << MyObject(1, "Alice")
              << MyObject(2, "Bob")
              << MyObject(3, "Charlie")
              << MyObject(2, "Bob"); // 同じ内容のオブジェクト

    qDebug() << "\n--- カスタムクラスの例 ---";

    // 検索するオブジェクト
    MyObject searchBob(2, "Bob");
    MyObject searchDavid(4, "David");

    // "Bob" のインデックスを検索
    qsizetype indexBob = myObjects.indexOf(searchBob);
    if (indexBob != -1) {
        qDebug() << searchBob << " はインデックス" << indexBob << "で見つかりました。"; // 出力: MyObject(id=2, name='Bob') はインデックス 1 で見つかりました。
    } else {
        qDebug() << searchBob << " は見つかりませんでした。";
    }

    // "David" のインデックスを検索
    qsizetype indexDavid = myObjects.indexOf(searchDavid);
    if (indexDavid != -1) {
        qDebug() << searchDavid << " はインデックス" << indexDavid << "で見つかりました。";
    } else {
        qDebug() << searchDavid << " は見つかりませんでした。"; // 出力: MyObject(id=4, name='David') は見つかりませんでした。
    }

    // 同じ内容だが異なるインスタンスで検索
    // operator==() が正しく定義されていれば動作する
    MyObject anotherBob(2, "Bob");
    qsizetype indexAnotherBob = myObjects.indexOf(anotherBob);
    qDebug() << "別のインスタンスの" << anotherBob << " のインデックスは" << indexAnotherBob << "です。"; // 出力: 別のインスタンスの MyObject(id=2, name='Bob') のインデックスは 1 です。

    return a.exec();
}

重要な注意点
カスタムクラスをQListに値として格納する場合、そのクラスはデフォルトコンストラクタ、コピーコンストラクタ、代入演算子(operator=)を持つ必要があります。これらは通常、コンパイラによって自動的に生成されますが、クラスがポインタメンバーを持つなど、リソース管理が必要な場合は明示的に定義する必要があります。operator==()の定義はindexOf()を使う上での追加要件です。

ポインタのQListでのindexOf()の使用

QList<MyObject*> のようにポインタを格納している場合、indexOf()ポインタのアドレス自体を比較します。オブジェクトの内容を比較したい場合は、直接 indexOf() を使うことはできません。手動でループして比較するか、適切なデータ構造(QHashなど)を検討する必要があります。

#include <QCoreApplication>
#include <QList>
#include <QString>
#include <QDebug>

class Animal {
public:
    QString species;
    int age;

    Animal(const QString& s, int a) : species(s), age(a) {}

    // ポインタ比較では使われないが、もし Animal を値として格納するなら必要
    friend bool operator==(const Animal& lhs, const Animal& rhs) {
        return lhs.species == rhs.species && lhs.age == rhs.age;
    }
};

// Animal* を QDebug で出力するためのオーバーロード
QDebug operator<<(QDebug debug, const Animal* animal) {
    QDebugStateSaver saver(debug);
    if (animal) {
        debug.nospace() << "Animal(species='" << animal->species << "', age=" << animal->age << ")";
    } else {
        debug.nospace() << "nullptr";
    }
    return debug;
}

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

    QList<Animal*> animals;
    Animal* dog = new Animal("Dog", 3);
    Animal* cat = new Animal("Cat", 5);
    Animal* bird = new Animal("Bird", 1);

    animals << dog << cat << bird;

    qDebug() << "\n--- ポインタのQListの例 ---";

    // 1. ポインタのアドレスで検索 (indexOf() のデフォルトの挙動)
    // dog のアドレスとまったく同じポインタを渡しているので見つかる
    qsizetype indexDogPtr = animals.indexOf(dog);
    qDebug() << "dogポインタのインデックス:" << indexDogPtr; // 出力: dogポインタのインデックス: 0

    // 新しく作成した、内容が同じだがアドレスが異なるポインタで検索
    Animal* anotherDog = new Animal("Dog", 3);
    qsizetype indexAnotherDogPtr = animals.indexOf(anotherDog);
    qDebug() << "anotherDogポインタのインデックス:" << indexAnotherDogPtr; // 出力: anotherDogポインタのインデックス: -1 (アドレスが異なるため)
    delete anotherDog; // メモリリークを防ぐため解放

    // 2. 要素の内容で検索したい場合(手動ループが必要)
    QString searchSpecies = "Cat";
    qsizetype foundIndex = -1;
    for (qsizetype i = 0; i < animals.size(); ++i) {
        if (animals.at(i)->species == searchSpecies) {
            foundIndex = i;
            break;
        }
    }

    if (foundIndex != -1) {
        qDebug() << "'" << searchSpecies << "' (内容) はインデックス" << foundIndex << "で見つかりました。"; // 出力: 'Cat' (内容) はインデックス 1 で見つかりました。
    } else {
        qDebug() << "'" << searchSpecies << "' (内容) は見つかりませんでした。";
    }

    // メモリの解放
    qDeleteAll(animals); // QList<T*> の場合に便利
    animals.clear();

    return a.exec();
}


QList::contains() を使用する(存在チェックのみの場合)