template <typename Predicate> qsizetype QList::removeIf()

2025-06-06

QList::removeIf() は、Qt 6.1 で導入された QList クラスのメンバ関数で、指定された条件(述語 Predicate)を満たす要素をリストからすべて削除するために使用されます。

テンプレート関数であること (template <typename Predicate>)

この関数はテンプレート関数であり、Predicate という型パラメータを取ります。Predicate は、要素を削除するかどうかを決定するための「述語(predicate)」として機能するオブジェクト(関数オブジェクト、ラムダ式、関数ポインタなど)を指定します。

戻り値 (qsizetype)

この関数は、リストから削除された要素の数を qsizetype 型で返します。qsizetype は、Qt でコンテナのサイズやインデックスを表すために使用される符号なし整数型です。

引数 (Predicate pred)

pred は、各要素に対して呼び出される述語です。この述語は、QList の要素の型 T を引数に取り、bool 値を返します。

  • 述語が false を返した場合、その要素はリストに残ります。
  • 述語が true を返した場合、その要素はリストから削除されます。

Prediate の例

通常、ラムダ式(C++11以降)を使用するのが最も一般的で簡潔です。

// 例えば、QList<int> から偶数をすべて削除する場合
QList<int> myIntList = {1, 2, 3, 4, 5, 6};

// ラムダ式を Predicate として使用
qsizetype removedCount = myIntList.removeIf([](int value) {
    return value % 2 == 0; // 偶数であれば true を返し、削除対象とする
});

// myIntList は {1, 3, 5} になる
// removedCount は 3 になる

動作の仕組み

removeIf は、リスト内の各要素を順番に走査し、指定された述語を適用します。述語が true を返した要素はリストから取り除かれ、それ以外の要素は残されます。内部的には効率的な削除処理が行われます。

使用するメリット

  • 安全性
    イテレータの無効化などを意識する必要が少なく、安全に削除処理を行えます。
  • 効率性
    手動でイテレータを操作して要素を削除するよりも、一般的に効率的です。
  • 簡潔なコード
    条件に基づく要素の削除処理を非常に簡潔に記述できます。


文字列のリストから、"apple" という文字列を含む要素をすべて削除する例を考えます。

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

int main() {
    QList<QString> fruits;
    fruits << "apple_red" << "banana" << "apple_green" << "orange" << "grape" << "sweet_apple";

    qDebug() << "元のリスト:" << fruits;

    // "apple" を含む要素を削除する述語 (ラムダ式)
    qsizetype removedItems = fruits.removeIf([](const QString& fruit) {
        return fruit.contains("apple");
    });

    qDebug() << "削除後のリスト:" << fruits;
    qDebug() << "削除された要素の数:" << removedItems;

    return 0;
}
元のリスト: ("apple_red", "banana", "apple_green", "orange", "grape", "sweet_apple")
削除後のリスト: ("banana", "orange", "grape")
削除された要素の数: 3


Predicate の引数型が間違っている

  • トラブルシューティング
    • 述語の引数型を QList の要素の型と完全に一致させるか、const T& を使用してください。通常は const T& が安全で推奨されます。

    • QList<int> myList;
      // OK
      myList.removeIf([](int value) { return value > 5; });
      myList.removeIf([](const int& value) { return value > 5; });
      
      // QList<MyObject> の場合
      // OK
      myList.removeIf([](const MyObject& obj) { return obj.someCondition(); });
      
  • 原因
    removeIf の述語は、QList の要素の型(または const T&T& など)を引数として受け取る必要があります。例えば、QList<MyClass> に対して removeIf を使う場合、述語は MyClass または const MyClass& を引数に取る必要があります。
  • エラーの症状
    コンパイルエラーが発生し、「no matching function for call to 'QList&lt;T>::removeIf'」のようなメッセージが表示されることがあります。これは、ラムダ式や関数オブジェクトの引数型が QList に格納されている要素の型と一致しない場合に起こります。

ラムダ式のキャプチャ問題 (特にポインタや参照の場合)

  • トラブルシューティング
    • 値キャプチャ [=] または明示的な値キャプチャ [var] の利用
      ラムダ式が外部の変数を使用する場合、可能であれば値をコピーしてキャプチャ ([=] または [var]) することを検討してください。これにより、元の変数が無効になってもラムダ式内で安全にアクセスできます。
    • スマートポインタ (QSharedPointer, std::shared_ptr) の利用
      オブジェクトへのポインタをリストに格納している場合、そのオブジェクトのライフサイクル管理にスマートポインタを使用し、ラムダ式もスマートポインタをキャプチャすることで、オブジェクトが無効になることを防げます。
    • ラムダ式とライフサイクルの同期
      removeIf が呼び出される時点で、ラムダ式が必要とするすべての外部変数が有効であることを確認してください。

    • // 悪い例: 無効な参照キャプチャの可能性
      bool condition = true;
      // removeIfが後で呼び出される場合、conditionがスコープ外になる可能性がある
      // myList.removeIf([&condition](int value) { return value > 5 && condition; });
      
      // 良い例: 値キャプチャ
      bool condition = true;
      myList.removeIf([condition](int value) { return value > 5 && condition; });
      
      // もしオブジェクトのメンバー変数を使う場合
      // this をキャプチャすれば安全 (クラスのライフサイクル内で)
      class MyClass {
          bool m_filterEnabled = true;
          void filterList(QList<int>& list) {
              list.removeIf([this](int value) { return value > 10 && m_filterEnabled; });
          }
      };
      
  • 原因
    ラムダ式がキャプチャしたポインタや参照が、removeIf が実行される時点で無効になっている可能性があります。例えば、ローカル変数のアドレスをキャプチャして、その変数がスコープ外になった後に removeIf が呼び出されると未定義動作になります。
  • エラーの症状
    removeIf が期待通りに動作しない、またはクラッシュする(セグメンテーションフォールトなど)。特にラムダ式が外部の変数(ポインタや参照)をキャプチャしている場合に発生しやすいです。

述語がリストの要素を変更してしまう

  • トラブルシューティング
    • 述語の引数には const T& を使用し、要素の変更を避けてください。
    • もし要素の変更が必要な場合は、removeIf の前に別途要素を修正するか、またはremoveIf を使用せず、手動でイテレータを操作して要素を削除し、同時に変更を行う必要があります。(ただし、これは removeIf の目的とは異なります。)
  • 原因
    述語は、要素を削除するかどうかを決定するためだけに使うべきであり、リストの要素自体を変更してはいけません。特に const T& ではなく T& で引数を受け取っている場合に発生しやすいです。QList::removeIf は内部的に要素を移動させる可能性があるため、述語内で要素を変更すると、その後の処理で問題を引き起こす可能性があります。
  • エラーの症状
    予期しない動作、クラッシュ、または removeIf が想定通りに要素を削除しない。

QList の値型がコピー可能/移動可能でない

  • トラブルシューティング
    • QList に格納するカスタムクラスに、適切なコピーコンストラクタ、コピー代入演算子、ムーブコンストラクタ、ムーブ代入演算子を実装してください。
    • もしクラスが大きく、コピーコストが高い場合は、ポインタ(QSharedPointer など)を QList に格納することを検討してください。
  • 原因
    QList は要素を内部でコピーまたはムーブする必要があるため、格納する型 T はコピーコンストラクタとコピー代入演算子(またはムーブコンストラクタとムーブ代入演算子)を持つ必要があります。特に、Qt 6以降は QListQVector のエイリアスとなり、連続メモリ上に要素を保持するため、この要件がより重要になります。
  • エラーの症状
    コンパイルエラーが発生し、「no matching function for call to ...」や、コピーコンストラクタやムーブコンストラクタが見つからないといったエラーメッセージが表示されることがあります。

Qtのバージョン

  • トラブルシューティング
    • Qt 6.1以降を使用していることを確認してください。
    • Qt 5.xを使用している場合は、std::remove_ifQList::erase を組み合わせる「erase-remove イディオム」を使用する必要があります。
      #include <algorithm> // for std::remove_if
      
      QList<int> myList = {1, 2, 3, 4, 5, 6};
      
      // erase-remove イディオム
      myList.erase(std::remove_if(myList.begin(), myList.end(), [](int value) {
          return value % 2 == 0;
      }), myList.end());
      
  • 原因
    QList::removeIf() はQt 6.1で導入された機能です。それ以前のバージョン(Qt 5.xなど)では利用できません。
  • エラーの症状
    QList::removeIf() が存在しないというコンパイルエラー。


QList::removeIf() は、指定された条件(述語)を満たす要素を QList から効率的に削除する関数です。述語は、各要素に対して呼び出され、true を返すとその要素が削除されます。

例 1: 基本的な使用法 - ラムダ式で偶数を削除

最も一般的で推奨される方法は、ラムダ式を述語として使うことです。

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

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

    QList<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    qDebug() << "元のリスト:" << numbers;

    // ラムダ式で偶数を削除する
    // []  : キャプチャリスト (今回は外部変数をキャプチャしないので空)
    // (int value) : 引数 (QList<int> の要素型である int を受け取る)
    // { return value % 2 == 0; } : 述語の本体 (偶数であれば true を返す)
    qsizetype removedCount = numbers.removeIf([](int value) {
        return value % 2 == 0;
    });

    qDebug() << "偶数削除後のリスト:" << numbers;
    qDebug() << "削除された要素の数:" << removedCount; // 出力: 5

    return 0;
}

実行結果

元のリスト: (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
偶数削除後のリスト: (1, 3, 5, 7, 9)
削除された要素の数: 5

例 2: ラムダ式で外部変数をキャプチャする (値キャプチャ)

特定の閾値を超える要素を削除するなど、述語が外部の値に依存する場合に使います。

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

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

    QList<int> scores = {85, 92, 78, 65, 95, 70, 88};
    qDebug() << "元のスコアリスト:" << scores;

    int passingThreshold = 80; // 合格点
    
    // passingThreshold 以下のスコアを削除する (不合格者を削除)
    // [passingThreshold] : passingThreshold を値渡しでキャプチャする
    qsizetype removedCount = scores.removeIf([passingThreshold](int score) {
        return score < passingThreshold; // 閾値未満であれば true (削除対象)
    });

    qDebug() << "合格者スコアリスト:" << scores;
    qDebug() << "削除された不合格者の数:" << removedCount; // 出力: 3

    return 0;
}

実行結果

元のスコアリスト: (85, 92, 78, 65, 95, 70, 88)
合格者スコアリスト: (85, 92, 95, 88)
削除された不合格者の数: 3

例 3: カスタムクラスのオブジェクトをリストから削除する

QList にカスタムクラスのオブジェクトが格納されている場合も同様に removeIf を使用できます。述語はカスタムクラスのオブジェクト(またはその参照)を引数にとります。

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

// シンプルなカスタムクラス
class Person {
public:
    Person(const QString& name, int age) : m_name(name), m_age(age) {}

    QString name() const { return m_name; }
    int age() const { return m_age; }

    // QDebug 出力用の演算子オーバーロード
    friend QDebug operator<<(QDebug debug, const Person& p) {
        QDebugStateSaver saver(debug);
        debug.nospace() << "Person(Name:" << p.m_name << ", Age:" << p.m_age << ")";
        return debug;
    }

private:
    QString m_name;
    int m_age;
};

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

    QList<Person> people;
    people << Person("Alice", 30)
           << Person("Bob", 25)
           << Person("Charlie", 35)
           << Person("David", 20)
           << Person("Eve", 40);

    qDebug() << "元の人物リスト:" << people;

    // 30歳未満の人物を削除する
    // (const Person& p) : Person オブジェクトの const 参照を受け取る
    qsizetype removedCount = people.removeIf([](const Person& p) {
        return p.age() < 30; // 30歳未満であれば true (削除対象)
    });

    qDebug() << "30歳以上の人物リスト:" << people;
    qDebug() << "削除された人物の数:" << removedCount; // 出力: 2

    return 0;
}

実行結果

元の人物リスト: (Person(Name:Alice, Age:30), Person(Name:Bob, Age:25), Person(Name:Charlie, Age:35), Person(Name:David, Age:20), Person(Name:Eve, Age:40))
30歳以上の人物リスト: (Person(Name:Alice, Age:30), Person(Name:Charlie, Age:35), Person(Name:Eve, Age:40))
削除された人物の数: 2

例 4: メンバー関数を述語として使用する (関数オブジェクトの作成)

少し高度な例ですが、クラスのメンバー関数を removeIf の述語として使用したい場合、そのメンバー関数を呼び出す関数オブジェクト(またはラムダ式)を作成する必要があります。

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

class Filter {
public:
    Filter(int threshold) : m_threshold(threshold) {}

    // このメンバー関数を述語として使いたい
    bool isLessThanThreshold(int value) const {
        return value < m_threshold;
    }

private:
    int m_threshold;
};

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

    QList<int> data = {10, 20, 5, 30, 15, 25};
    qDebug() << "元のデータリスト:" << data;

    Filter myFilter(20); // 閾値 20 のフィルターオブジェクトを作成

    // myFilter オブジェクトの isLessThanThreshold メンバー関数を呼び出すラムダ式
    // [&myFilter] : myFilter オブジェクトへの参照をキャプチャする
    qsizetype removedCount = data.removeIf([&myFilter](int value) {
        return myFilter.isLessThanThreshold(value);
    });

    qDebug() << "閾値未満のデータ削除後のリスト:" << data;
    qDebug() << "削除された要素の数:" << removedCount; // 出力: 2

    return 0;
}

実行結果

元のデータリスト: (10, 20, 5, 30, 15, 25)
閾値未満のデータ削除後のリスト: (20, 30, 25)
削除された要素の数: 2

これらの例は、QList::removeIf() の柔軟性と多様な使用法を示しています。特にラムダ式は、インラインで簡潔に述語を定義できるため、非常に強力です。 Qt の QList::removeIf() は、特定の条件を満たす要素をリストから削除する際に非常に便利な関数です。ここでは、いくつかのプログラミング例を通してその使い方を詳しく解説します。

基本的な使用法:ラムダ式で数値フィルタリング

最も一般的な使用例は、シンプルなラムダ式(C++11以降で利用可能)を使って数値をフィルタリングすることです。

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

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

    QList<int> numbers;
    numbers << 10 << 5 << 20 << 3 << 15 << 8 << 25;

    qDebug() << "元のリスト:" << numbers;

    // 10より小さいすべての数値を削除
    qsizetype removedCount = numbers.removeIf([](int value) {
        return value < 10; // 10より小さければ true を返し、削除対象とする
    });

    qDebug() << "削除後のリスト:" << numbers;
    qDebug() << "削除された要素の数:" << removedCount;

    return a.exec();
}

出力

元のリスト: (10, 5, 20, 3, 15, 8, 25)
削除後のリスト: (10, 20, 15, 25)
削除された要素の数: 3

QString のリストから特定の文字列を含む要素を削除

文字列のリストに対して、特定のサブストリングを含む要素を削除する例です。QString::contains() を述語として利用します。

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

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

    QList<QString> words;
    words << "apple" << "banana" << "apricot" << "grape" << "pineapple" << "orange";

    qDebug() << "元のリスト:" << words;

    // "apple" を含むすべての単語を削除
    qsizetype removedCount = words.removeIf([](const QString& s) {
        return s.contains("apple", Qt::CaseInsensitive); // "apple" を含むか (大文字小文字を区別しない)
    });

    qDebug() << "削除後のリスト:" << words;
    qDebug() << "削除された要素の数:" << removedCount;

    return a.exec();
}

出力

元のリスト: ("apple", "banana", "apricot", "grape", "pineapple", "orange")
削除後のリスト: ("banana", "grape", "orange")
削除された要素の数: 3

カスタムオブジェクトのリストから特定の条件を満たす要素を削除

QList にカスタムクラスのオブジェクトが格納されている場合でも removeIf() を使用できます。述語はカスタムクラスのインスタンスを受け取り、そのメンバ変数やメソッドに基づいて判断します。

MyObject.h

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>
#include <QString>
#include <QDebug>

class MyObject
{
public:
    MyObject(int id, const QString& name) : m_id(id), m_name(name) {}

    int id() const { return m_id; }
    QString name() const { return m_name; }

    // デバッグ出力用のストリーム演算子オーバーロード (QDebugでMyObjectを直接出力可能にする)
    friend QDebug operator<<(QDebug debug, const MyObject& obj) {
        QDebugStateSaver saver(debug);
        debug.nospace() << "MyObject(ID:" << obj.m_id << ", Name:'" << obj.m_name << "')";
        return debug;
    }

private:
    int m_id;
    QString m_name;
};

#endif // MYOBJECT_H

main.cpp

#include <QCoreApplication>
#include <QList>
#include <QDebug>
#include "MyObject.h" // 作成したカスタムクラスのヘッダ

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

    QList<MyObject> objects;
    objects << MyObject(1, "Alice")
            << MyObject(2, "Bob")
            << MyObject(3, "Charlie")
            << MyObject(4, "Alice")
            << MyObject(5, "David");

    qDebug() << "元のリスト:" << objects;

    // IDが偶数であるか、または名前が "Alice" であるオブジェクトを削除
    qsizetype removedCount = objects.removeIf([](const MyObject& obj) {
        return (obj.id() % 2 == 0) || (obj.name() == "Alice");
    });

    qDebug() << "削除後のリスト:" << objects;
    qDebug() << "削除された要素の数:" << removedCount;

    return a.exec();
}

出力

元のリスト: (MyObject(ID:1, Name:'Alice'), MyObject(ID:2, Name:'Bob'), MyObject(ID:3, Name:'Charlie'), MyObject(ID:4, Name:'Alice'), MyObject(ID:5, Name:'David'))
削除後のリスト: (MyObject(ID:3, Name:'Charlie'), MyObject(ID:5, Name:'David'))
削除された要素の数: 3

ラムダ式のキャプチャと外部変数

ラムダ式は、外部の変数をキャプチャして述語の条件に含めることができます。

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

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

    QList<int> scores;
    scores << 85 << 60 << 92 << 45 << 70 << 55 << 88;

    qDebug() << "元のリスト:" << scores;

    int minScore = 65; // 外部変数
    QString criteria = "不及格"; // 外部変数

    // minScore より低いスコアを削除 (値キャプチャ [minScore])
    qsizetype removedCount = scores.removeIf([minScore](int score) {
        qDebug() << "判定中: スコア" << score << ", 基準点:" << minScore;
        return score < minScore;
    });

    qDebug() << "削除後のリスト (基準点 " << minScore << " 以上):" << scores;
    qDebug() << "削除された要素の数:" << removedCount;

    // 別の例: 特定の文字で終わる文字列を削除
    QList<QString> files;
    files << "document.txt" << "image.jpg" << "report.pdf" << "archive.zip" << "data.txt";
    qDebug() << "\n元のファイルリスト:" << files;

    QString extensionToRemove = ".txt"; // 外部変数

    qsizetype removedFilesCount = files.removeIf([&extensionToRemove](const QString& filename) {
        // 参照キャプチャ [&extensionToRemove]
        return filename.endsWith(extensionToRemove);
    });

    qDebug() << "削除後のファイルリスト (拡張子 " << extensionToRemove << " 以外のファイル):" << files;
    qDebug() << "削除されたファイルの数:" << removedFilesCount;

    return a.exec();
}

出力

元のリスト: (85, 60, 92, 45, 70, 55, 88)
判定中: スコア 85 , 基準点: 65
判定中: スコア 60 , 基準点: 65
判定中: スコア 92 , 基準点: 65
判定中: スコア 45 , 基準点: 65
判定中: スコア 70 , 基準点: 65
判定中: スコア 55 , 基準点: 65
判定中: スコア 88 , 基準点: 65
削除後のリスト (基準点 65 以上): (85, 92, 70, 88)
削除された要素の数: 3

元のファイルリスト: ("document.txt", "image.jpg", "report.pdf", "archive.zip", "data.txt")
削除後のファイルリスト (拡張子 ".txt" 以外のファイル): ("image.jpg", "report.pdf", "archive.zip")
削除されたファイルの数: 2

この例では、ラムダ式が minScore を値でキャプチャし、extensionToRemove を参照でキャプチャしているのがわかります。キャプチャは、ラムダ式がその定義されたスコープ外の変数にアクセスできるようにする強力な機能です。



QList::removeIf() は非常に便利ですが、特定の状況や古いQtのバージョン(Qt 6.1より前)では利用できません。そのような場合に、同様の機能を実現するための代替手段がいくつかあります。

std::remove_if と QList::erase (erase-remove イディオム)

これは、QList::removeIf() が導入されるまで、最も一般的で推奨されていた方法です。C++標準ライブラリの std::remove_if アルゴリズムと、QListerase メソッドを組み合わせます。

std::remove_if は、指定された述語を満たす要素をコンテナの先頭に「移動」させ、削除すべきではない要素の新しい論理的な終端へのイテレータを返します。この時点では要素は削除されていません。その後、QList::erase を使って、そのイテレータからリストの終端までの要素を実際に削除します。

#include <QCoreApplication>
#include <QList>
#include <QDebug>
#include <algorithm> // std::remove_if を使うために必要

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

    QList<int> numbers;
    numbers << 10 << 5 << 20 << 3 << 15 << 8 << 25;

    qDebug() << "元のリスト:" << numbers;

    // 10より小さい数値を削除 (erase-remove イディオム)
    // std::remove_if は、削除対象の要素をリストの後方に移動させ、
    // 削除せずに残す要素の新しい「論理的な末尾」へのイテレータを返す
    auto newEnd = std::remove_if(numbers.begin(), numbers.end(), [](int value) {
        return value < 10;
    });

    // erase は、newEnd からリストの実際の末尾までの要素を物理的に削除する
    numbers.erase(newEnd, numbers.end());

    qDebug() << "削除後のリスト (erase-remove):" << numbers;

    return a.exec();
}

利点

  • 標準的なイディオムとして広く知られており、パフォーマンスも効率的です。
  • Qt 6.1より前のバージョンを含む、ほぼすべてのC++環境で利用できます。

欠点

  • removeIf() に比べてコードが2行になり、少し冗長に感じられるかもしれません。

手動でループし、条件を満たす要素を削除

これは最も基本的な方法ですが、効率性やコードの簡潔さの点で劣る場合があります。要素を削除するとイテレータが無効になるため、ループ内でイテレータを注意深く管理する必要があります。通常は、逆順にループするか、削除後にイテレータを調整します。

a. 逆順ループで削除

リストの末尾から先頭に向かってループすると、要素を削除しても、まだ処理していない前の要素のインデックスには影響がありません。

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

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

    QList<int> numbers;
    numbers << 10 << 5 << 20 << 3 << 15 << 8 << 25;

    qDebug() << "元のリスト:" << numbers;

    // 逆順ループで10より小さい数値を削除
    for (int i = numbers.size() - 1; i >= 0; --i) {
        if (numbers.at(i) < 10) {
            numbers.removeAt(i); // あるいは numbers.removeAt(i);
        }
    }

    qDebug() << "削除後のリスト (逆順ループ):" << numbers;

    return a.exec();
}

利点

  • std::remove_if を知らない場合や、非常に単純なケースでは直感的です。

欠点

  • リストのサイズがNの場合、削除操作は最悪O(N)かかるため、全体の計算量はO(N^2)になり、効率が非常に悪くなる可能性があります。

b. イテレータを使用し、削除後に調整

前方へのループ中に要素を削除する場合、QList::erase() メソッドが次の有効なイテレータを返すことを利用します。

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

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

    QList<int> numbers;
    numbers << 10 << 5 << 20 << 3 << 15 << 8 << 25;

    qDebug() << "元のリスト:" << numbers;

    // イテレータを使った前方ループで10より小さい数値を削除
    auto it = numbers.begin();
    while (it != numbers.end()) {
        if (*it < 10) {
            it = numbers.erase(it); // eraseは次の要素へのイテレータを返す
        } else {
            ++it; // 削除しなかった場合はイテレータを進める
        }
    }

    qDebug() << "削除後のリスト (イテレータ調整):" << numbers;

    return a.exec();
}

利点

  • 逆順ループよりは効率的です(O(N)に近い)。
  • erase-remove イディオムが使えない場合や、イテレータの振る舞いを細かく制御したい場合に有効です。

欠点

  • イテレータの管理が複雑になり、ミスを犯しやすいです。特に、erase の戻り値を正しく使わないと、クラッシュや予期せぬ動作につながります。

新しいリストを作成してフィルタリング

元のリストを変更するのではなく、条件を満たさない要素だけを新しいリストにコピーする方法です。

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

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

    QList<int> originalNumbers;
    originalNumbers << 10 << 5 << 20 << 3 << 15 << 8 << 25;

    qDebug() << "元のリスト:" << originalNumbers;

    QList<int> filteredNumbers;
    for (int value : originalNumbers) {
        if (!(value < 10)) { // 削除対象ではない要素を新しいリストに追加
            filteredNumbers.append(value);
        }
    }
    // または QList::reserve() を使って事前にサイズを確保すると効率的になる場合がある

    qDebug() << "フィルタリング後の新しいリスト:" << filteredNumbers;
    // 必要であれば、元のリストを新しいリストで置き換える
    // originalNumbers = filteredNumbers;

    return a.exec();
}

利点

  • コードが非常に読みやすく、理解しやすいです。
  • 元のリストを変更しないため、副作用を心配する必要がありません。
  • 新しいリストのためのメモリを消費し、要素のコピーに時間がかかるため、大規模なリストではパフォーマンスオーバーヘッドが発生する可能性があります。
代替手段利点欠点状況
std::remove_if + erase効率的、標準的、Qtバージョンに依存しない。removeIf() よりコードが長い。Qt 6.1より前、または標準的なアプローチを好む場合。
手動ループ (逆順)直感的、簡単。大規模リストでは非効率(O(N^2))。非常に小規模なリスト、またはデバッグ用。
手動ループ (イテレータ)効率的、細かな制御が可能。イテレータ管理が複雑で、エラーを起こしやすい。特定の複雑な削除ロジックが必要な場合。
新しいリストにコピーコードが明確、副作用なし。追加のメモリ消費、コピーコスト。元のリストを変更したくない場合、パフォーマンスが最優先でない場合。