QListの要素削除テクニック:removeAll()以外の選択肢と使い分け

2025-06-06

QList とは?

まず、QListはQtフレームワークが提供するコンテナクラスの一つで、C++のstd::vectorのように動的な配列として要素を格納します。要素の追加、削除、アクセスなどを効率的に行えるように設計されています。

removeAll()は、QList内に存在する特定の値と一致するすべての要素を削除するためのメソッドです。そして、削除された要素の数を返します。

template <typename AT = T> の意味

ここがこのシグネチャの重要な部分です。

  • template <typename AT = T>: これはC++のテンプレート構文です。
    • template: これは関数がテンプレートであることを示します。
    • typename AT: ATという名前の型パラメータを導入します。これは、removeAll()メソッドが削除する対象の値を表現する型を指定するために使われます。
    • = T: これが「デフォルトテンプレート引数」です。これは、AT型が明示的に指定されない場合、QList自体の要素の型(T)がデフォルトで使用されることを意味します。

具体的にどういうことか?

QList<T> のインスタンスがあるとき、removeAll()を呼び出す際に、削除したい値の型を明示的に指定する必要がある場合があります。しかし、ほとんどの場合、削除したい値の型はQListが保持している要素の型(T)と同じです。

このtemplate <typename AT = T>という記述により、以下の2つの方法でremoveAll()を呼び出すことができます。

  1. デフォルトの型を使用する場合(一般的な使用法)

    QList<int> myList;
    myList << 1 << 2 << 3 << 2 << 4;
    qsizetype removedCount = myList.removeAll(2); // 2という値が一致するすべての要素を削除
    // removedCount は 2 になる (2が2つ削除されたため)
    // myList は {1, 3, 4} になる
    

    この場合、removeAll(2)2の型はintであり、これはmyListの要素の型T(この場合はint)と一致するため、ATは自動的にintに推論されます。

  2. 異なる型を指定する場合(あまり一般的ではないが、比較可能な場合に有用)
    例えば、QList<QString> があり、const char* 型の文字列を削除したい場合などです。QStringconst char*からの変換をサポートしているため、このテンプレート引数が役立つことがあります。

    QList<QString> myStringList;
    myStringList << "Apple" << "Banana" << "Orange" << "Apple";
    qsizetype removedCount = myStringList.removeAll<QStringView>("Apple"); // C++17のQStringViewを使って効率的に比較
    // removedCount は 2 になる
    // myStringList は {"Banana", "Orange"} になる
    

    この例では、QStringViewという型を明示的にATとして指定しています。これにより、QStringQStringViewの間で比較が行われます。

qsizetypeはQtで定義された型エイリアスで、通常はintまたはqint64(64ビット整数)に解決されます。これは、プラットフォームに依存しない適切なサイズでコンテナのサイズや要素数を表現するために使用されます。removeAll()メソッドが返す値は、リストから削除された要素の数であるため、この型が使用されます。

template <typename AT = T> qsizetype QList::removeAll()は、QtのQListクラスにおいて、指定された値と一致するすべての要素をリストから削除し、削除された要素の数を返すためのメンバー関数です。

  • qsizetype: 削除された要素の数を安全かつプラットフォームに依存しない形で返すための型です。
  • template <typename AT = T>: このテンプレート構文は、削除する値の型を柔軟に指定できるようにします。多くの場合、QListの要素型がデフォルトで使用されますが、必要に応じて異なる(ただし互換性のある)型を指定することも可能です。


QList::removeAll() は、リストから特定の値の要素をすべて削除する非常に便利な関数ですが、いくつか注意すべき点があります。

値の比較に関する問題 (operator== の未実装または不適切)

removeAll() は、削除する値をリスト内の各要素と比較するために operator== を使用します。もし、カスタム型(自分で定義したクラスや構造体)をQListに格納していて、その型に対して operator== を実装していない、または不適切に実装している場合、予期せぬ動作やコンパイルエラーが発生します。

エラーの例

  • 実行時エラー/論理エラー
    removeAll() が何も削除しない、または期待通りの数の要素を削除しない。
  • コンパイルエラー
    no match for 'operator==' のようなエラーが表示される。

トラブルシューティング

  • ポインタの場合
    QList<MyObject*> のようにポインタを格納している場合、removeAll() はポインタの値(アドレス)を比較します。ポインタが指すオブジェクトの内容を比較したい場合は、removeAll() の代わりにイテレータを使って手動で要素を検索し、削除する必要があります(後述の「イテレータの使用」を参照)。

    // 誤った例 (ポインタのアドレス比較になる)
    QList<MyObject*> objectList;
    MyObject* objToDelete = new MyObject("foo");
    objectList.append(new MyObject("bar"));
    objectList.append(objToDelete);
    objectList.append(new MyObject("foo"));
    
    // このremoveAllはobjToDeleteのアドレスと一致する要素のみを削除します
    // 同じ内容を持つ別のMyObject*は削除されません
    objectList.removeAll(objToDelete);
    
    // 期待通りにオブジェクトの内容で削除したい場合
    // MyObject::operator== を実装していると仮定
    QMutableListIterator<MyObject*> it(objectList);
    while (it.hasNext()) {
        if (*it.next() == *objToDelete) { // ポインタが指すオブジェクトの内容を比較
            delete it.value(); // 必要であればオブジェクトを解放
            it.remove();
        }
    }
    delete objToDelete; // 比較に使用したオブジェクトも忘れずに解放
    
  • operator== の実装を確認する
    QListに格納するカスタム型に対して、正しく bool operator==(const MyType& other) const を実装しているか確認してください。比較対象のオブジェクトが「等しい」と判断される条件を明確に定義する必要があります。

メモリ管理 (ポインタを格納している場合)

QList::removeAll() は、リストから要素を削除するだけであり、その要素が指していたメモリを自動的に解放するわけではありませんQList<MyObject*> のように動的に確保されたオブジェクトへのポインタを格納している場合、removeAll() を呼び出しただけではメモリリークが発生します。

エラーの例

  • リストから要素を削除した後も、削除されたオブジェクトがヒープ上に残り続け、メモリリークが発生する。

トラブルシューティング

  • removeAll() の前に手動で delete するか、スマートポインタを使用する
    特定の値を削除し、かつその値が指すオブジェクトも削除したい場合は、removeAll() の前に、削除される対象のオブジェクトを特定し、手動で delete する必要があります。あるいは、リストにスマートポインタ(QSharedPointerstd::unique_ptr など)を格納することで、メモリ管理を自動化できます。

    // ポインタを格納していて、特定のオブジェクトを削除し、そのオブジェクトが指すメモリも解放したい場合
    QList<MyObject*> objectList;
    objectList.append(new MyObject("foo"));
    objectList.append(new MyObject("bar"));
    MyObject* targetObj = objectList.at(0); // "foo" を指すポインタ
    
    // 削除対象のオブジェクトを特定し、deleteする
    // ただし、removeAll()は値による比較なので、ここでdeleteしたポインタと
    // 同じ値のポインタをremoveAll()に渡す必要がある
    // ここでは単純に最初に見つかったものを削除する例として
    // 実際には、どのオブジェクトを削除するかを慎重に考える必要がある
    // 複数ある場合は、ループ内でdeleteしながらremoveAllする方が安全
    
    // オブジェクトの値を元に削除する場合
    MyObject valueToDelete("foo"); // 比較用のオブジェクト
    QList<MyObject*> toDeletePointers; // 削除対象のポインタを一時的に保持
    for (MyObject* obj : objectList) {
        if (*obj == valueToDelete) {
            toDeletePointers.append(obj);
        }
    }
    for (MyObject* obj : toDeletePointers) {
        objectList.removeAll(obj); // リストからポインタを削除
        delete obj; // オブジェクトを解放
    }
    
    // スマートポインタを使用する場合
    QList<QSharedPointer<MyObject>> sharedObjectList;
    sharedObjectList.append(QSharedPointer<MyObject>(new MyObject("foo")));
    sharedObjectList.append(QSharedPointer<MyObject>(new MyObject("bar")));
    sharedObjectList.append(QSharedPointer<MyObject>(new MyObject("foo")));
    
    // QSharedPointerは参照カウントが0になると自動的にオブジェクトを解放します
    // ここでremoveすると、そのQSharedPointerオブジェクトがリストから削除され、
    // 参照カウントが減少し、0になれば自動的にdeleteされます
    sharedObjectList.removeAll(QSharedPointer<MyObject>(new MyObject("foo"))); // 新しいQSharedPointerを作成して比較
    // もしくは、元のリスト内のQSharedPointerの値を直接渡す
    QSharedPointer<MyObject> targetSharedObj = sharedObjectList.at(0);
    sharedObjectList.removeAll(targetSharedObj);
    

    スマートポインタを使用する場合は、removeAll() に渡す比較対象の型もスマートポインタ型にするか、operator== を適切に定義する必要があります。

  • qDeleteAll() と組み合わせる
    QListがポインタを格納している場合、removeAll() の前に qDeleteAll() を使用して、リスト内のすべての要素が指すオブジェクトを削除することができます。ただし、removeAll() は特定の値に一致するものを削除するため、リスト全体を削除する目的でなければ qDeleteAll() は適していません。

QListがコピーされる場合の注意

QListは暗黙的な共有(Implicit Sharing)を使用していますが、リストの非定数メソッドを呼び出すと、リストがコピーされる可能性があります。しかし、removeAll()はリスト自体を変更するため、通常はコピーは問題になりません。

ただし、foreach マクロを使用してリストをイテレート中にremoveAll() を呼び出すことは推奨されません。foreach はリストのコピーに対して動作するため、元のリストが変更されても foreach ループには反映されません。

エラーの例

  • foreach ループ内で removeAll() を呼び出しても、期待通りに要素が削除されない、または無限ループに陥る。

トラブルシューティング

  • イテレータを使用する
    リストをイテレートしながら要素を削除する必要がある場合は、QMutableListIterator または std::remove_ifQList::erase() を組み合わせるのが正しい方法です。

    QList<int> myList;
    myList << 1 << 2 << 3 << 2 << 4;
    
    // 誤った例 (foreachループ内で直接removeAll()を呼び出す)
    // for (int val : myList) {
    //     if (val == 2) {
    //         myList.removeAll(val); // これは意図した動作にならない可能性が高い
    //     }
    // }
    
    // 正しい例 (QMutableListIteratorを使用)
    QMutableListIterator<int> i(myList);
    while (i.hasNext()) {
        if (i.next() == 2) {
            i.remove(); // イテレータ経由で要素を削除
        }
    }
    // myList は {1, 3, 4} になる
    

QListの要素が一時オブジェクトの場合

removeAll() に渡す比較対象の値が一時オブジェクトである場合、そのオブジェクトの寿命に注意が必要です。通常は問題ありませんが、カスタム型で複雑なコピーコンストラクタや代入演算子を持つ場合、予期せぬ動作を招く可能性があります。

トラブルシューティング

  • 削除したい値を明示的な変数として定義し、その変数を removeAll() に渡すことで、寿命の問題を回避できます。

QList::removeAll() を使用する際の主なトラブルシューティングは、以下の点に集約されます。

  1. 比較のロジック
    operator== が正しく実装されているか、特にカスタム型やポインタの場合に注意する。
  2. メモリ管理
    ポインタを格納している場合、removeAll() がメモリを解放しないことを理解し、必要に応じて手動またはスマートポインタで解放する。
  3. イテレーション中の変更
    foreach ループではなく、QMutableListIteratorstd::remove_if を使用してイテレート中に要素を削除する。


#include <QCoreApplication>
#include <QList>
#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; }

    // QList::removeAll() で比較するために operator== を実装
    bool operator==(const MyObject& other) const {
        qDebug() << "Comparing:" << m_id << m_name << "with" << other.m_id << other.m_name;
        return m_id == other.m_id && m_name == other.m_name;
    }

private:
    int m_id;
    QString m_name;
};

// MyObject をデバッグ出力するための operator<< オーバーロード
QDebug operator<<(QDebug debug, const MyObject& obj) {
    debug.noquote() << "MyObject(ID:" << obj.id() << ", Name:'" << obj.name() << "')";
    return debug;
}

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

    qDebug() << "--- int 型の QList で removeAll() を使用する例 ---";
    QList<int> intList;
    intList << 10 << 20 << 30 << 20 << 40 << 20 << 50;
    qDebug() << "元のリスト:" << intList;

    // 値 '20' をすべて削除
    qsizetype removedCountInt = intList.removeAll(20);
    qDebug() << "削除された要素数 (int):" << removedCountInt;
    qDebug() << "変更後のリスト:" << intList;
    qDebug() << "\n";

    qDebug() << "--- QString 型の QList で removeAll() を使用する例 ---";
    QList<QString> stringList;
    stringList << "Apple" << "Banana" << "Orange" << "Apple" << "Grape";
    qDebug() << "元のリスト:" << stringList;

    // 値 "Apple" をすべて削除
    qsizetype removedCountString = stringList.removeAll("Apple");
    qDebug() << "削除された要素数 (QString):" << removedCountString;
    qDebug() << "変更後のリスト:" << stringList;
    qDebug() << "\n";

    qDebug() << "--- カスタムクラス MyObject の QList で removeAll() を使用する例 ---";
    QList<MyObject> objectList;
    objectList << MyObject(1, "Alice")
               << MyObject(2, "Bob")
               << MyObject(1, "Alice") // 同じ内容のオブジェクト
               << MyObject(3, "Charlie")
               << MyObject(1, "Alice"); // 同じ内容のオブジェクト
    qDebug() << "元のリスト:" << objectList;

    // IDが1、名前が"Alice"のMyObjectをすべて削除
    // MyObject::operator== が呼び出され、比較が行われます
    MyObject objectToRemove(1, "Alice");
    qsizetype removedCountObject = objectList.removeAll(objectToRemove);
    qDebug() << "削除された要素数 (MyObject):" << removedCountObject;
    qDebug() << "変更後のリスト:" << objectList;
    qDebug() << "\n";

    qDebug() << "--- ポインタ型の QList で removeAll() を使用する際の注意点 ---";
    qList<MyObject*> pointerList;
    MyObject* obj1 = new MyObject(100, "PtrA");
    MyObject* obj2 = new MyObject(200, "PtrB");
    MyObject* obj3 = new MyObject(100, "PtrA"); // obj1 とは別のアドレスだが、内容は同じ
    MyObject* obj4 = new MyObject(300, "PtrC");

    pointerList << obj1 << obj2 << obj3 << obj4;
    qDebug() << "元のポインタリスト:";
    for (MyObject* obj : pointerList) {
        qDebug() << obj << *obj; // ポインタのアドレスと内容を表示
    }

    // obj1 (特定のアドレスのポインタ) を削除しようとする
    // removeAll() はポインタのアドレス(値)を比較します。
    // そのため、内容が同じでもアドレスが異なる obj3 は削除されません。
    qsizetype removedCountPointer = pointerList.removeAll(obj1);
    qDebug() << "削除された要素数 (ポインタ):" << removedCountPointer;
    qDebug() << "変更後のポインタリスト:";
    for (MyObject* obj : pointerList) {
        qDebug() << obj << *obj;
    }
    qDebug() << "注意: この時点では obj1 の指すメモリは解放されていません!";
    qDebug() << "      手動で delete するか、スマートポインタを使用する必要があります。";
    qDebug() << "\n";

    // ポインタリストのクリーンアップ(メモリ解放)
    qDeleteAll(pointerList); // pointerList内のすべてのポインタが指すオブジェクトをdelete
    pointerList.clear();     // リストをクリア (ポインタは既にdelete済み)

    qDebug() << "--- 異なる型を指定して removeAll() を使用する例 (滅多に使わない) ---";
    QList<double> doubleList;
    doubleList << 1.0 << 2.5 << 3.0 << 2.5 << 4.0;
    qDebug() << "元のリスト:" << doubleList;

    // float 型の値で比較して削除 (double と float は比較可能)
    // この場合、テンプレート引数 AT は float に推論されます
    qsizetype removedCountFloat = doubleList.removeAll(2.5f);
    qDebug() << "削除された要素数 (float比較):" << removedCountFloat;
    qDebug() << "変更後のリスト:" << doubleList;

    return a.exec();
}

このコードは、template <typename AT = T> qsizetype QList::removeAll() の様々な使用例と、特に注意すべきポインタ型のリストでの振る舞いを示しています。

    • 最も基本的な使用例です。intList から値 20 をすべて削除します。
    • removeAll(20) と呼び出すと、テンプレートパラメータ AT はデフォルトの int に推論されます。
    • qsizetype 型で削除された要素数 (20 が3つあったので 3) が返されます。
  1. QString 型の QListremoveAll() を使用する例

    • QStringList から文字列 "Apple" をすべて削除します。
    • QStringoperator== を適切に実装しているため、期待通りに動作します。
  2. カスタムクラス MyObjectQListremoveAll() を使用する例

    • MyObject というカスタムクラスを定義しています。
    • 重要な点
      QList::removeAll()MyObject オブジェクトを比較できるように、MyObject クラス内で operator== をオーバーロードしています。この operator== が、removeAll() が各要素を比較する際に使用されます。
    • objectToRemove という MyObject(1, "Alice") を作成し、これと一致するすべてのオブジェクトを削除します。operator== のデバッグ出力を見ることで、比較が行われていることがわかります。
  3. ポインタ型の QListremoveAll() を使用する際の注意点

    • QList<MyObject*> のようにポインタを格納する場合の例です。
    • obj1obj3new MyObject(100, "PtrA") で作成されており、内容は同じですが、メモリ上の異なるアドレスを指しています。
    • pointerList.removeAll(obj1) を呼び出すと、removeAll()obj1ポインタの値(アドレス) とリスト内の他のポインタの値を比較します。
    • 結果として、obj1同じアドレスを持つポインタだけが削除されます。obj3 は内容が同じでもアドレスが異なるため削除されません。
    • トラブルシューティングの重要性
      この例では、removeAll() が呼び出された後も、削除された obj1 の指すメモリは解放されません。これはメモリリークの原因となります。ポインタリストを扱う場合は、qDeleteAll() や手動での delete、またはスマートポインタ (QSharedPointer など) を使用して、メモリを適切に管理する必要があります。例の最後で qDeleteAll() を使ってリスト内のオブジェクトを解放しています。
  4. 異なる型を指定して removeAll() を使用する例 (滅多に使わない)

    • QList<double> から float 型の値 (2.5f) を削除する例です。
    • この場合、removeAll(2.5f) と呼び出すと、ATfloat に推論されます。doublefloat は比較可能なので、期待通りに動作します。
    • このような明示的な型指定は、Qtのコンテナではあまり一般的ではありませんが、特定の最適化や柔軟な比較ロジックが必要な場合に利用できます。

基本的な使用例 (プリミティブ型)

最も基本的な例として、整数のリストから特定の値を削除する場合を考えます。

#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 << 50 << 20;

    qDebug() << "元のリスト:" << numbers; // 出力: (10, 20, 30, 20, 40, 50, 20)

    // 値 '20' をすべて削除
    qsizetype removedCount = numbers.removeAll(20);

    qDebug() << "削除された要素の数:" << removedCount; // 出力: 3
    qDebug() << "変更後のリスト:" << numbers;    // 出力: (10, 30, 40, 50)

    // 存在しない値を削除しようとする場合
    removedCount = numbers.removeAll(99);
    qDebug() << "存在しない値を削除しようとした場合 (削除された数):" << removedCount; // 出力: 0
    qDebug() << "リスト (変化なし):" << numbers; // 出力: (10, 30, 40, 50)

    return a.exec();
}

解説

  • 存在しない値を削除しようとした場合、removeAll()0を返します。
  • 戻り値のremovedCountは、実際に削除された要素の数を返します。この例では20が3つあったため、3が返されます。
  • numbers.removeAll(20) は、numbersリストから値20と等しいすべての要素を削除します。
  • numbers << 10 << ... は、QListに要素を追加する便利な構文です。

カスタム型での使用例 (比較演算子のオーバーロード)

QListがカスタム型を格納している場合、removeAll()が正しく機能するためには、そのカスタム型に対してoperator==をオーバーロードする必要があります。

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

// カスタムクラスの定義
class MyObject
{
public:
    MyObject(int id, const QString& name) : m_id(id), m_name(name) {}

    // 比較演算子のオーバーロード (removeAll() がこれを使用します)
    bool operator==(const MyObject& other) const {
        return m_id == other.m_id && m_name == other.m_name;
    }

    // デバッグ表示用
    friend QDebug operator<<(QDebug debug, const MyObject& obj) {
        QDebugStateSaver saver(debug);
        debug.nospace() << "MyObject(" << obj.m_id << ", " << obj.m_name << ")";
        return debug;
    }

private:
    int m_id;
    QString m_name;
};

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

    QList<MyObject> objectList;
    objectList << MyObject(1, "Apple")
               << MyObject(2, "Banana")
               << MyObject(1, "Apple") // 意図的に重複
               << MyObject(3, "Orange")
               << MyObject(1, "Apple"); // 意図的に重複

    qDebug() << "元のリスト:" << objectList;
    // 出力: (MyObject(1, "Apple"), MyObject(2, "Banana"), MyObject(1, "Apple"), MyObject(3, "Orange"), MyObject(1, "Apple"))

    // idが1で名前が"Apple"のオブジェクトをすべて削除
    MyObject target(1, "Apple");
    qsizetype removedCount = objectList.removeAll(target);

    qDebug() << "削除された要素の数:" << removedCount; // 出力: 3
    qDebug() << "変更後のリスト:" << objectList;
    // 出力: (MyObject(2, "Banana"), MyObject(3, "Orange"))

    return a.exec();
}

解説

  • removeAll(target)は、このオーバーロードされたoperator==を使用して、リスト内の各MyObjecttargetオブジェクトを比較し、一致するものを削除します。
  • MyObjectクラスでは、operator==をオーバーロードして、idnameの両方が一致する場合にオブジェクトが等しいと判断されるようにしています。

ポインタを格納している場合の注意と対処

QListが動的に確保されたオブジェクトへのポインタを格納している場合、removeAll()はポインタの値(アドレス)を比較します。したがって、同じ内容を持つ別のオブジェクトへのポインタを渡しても削除されません。また、removeAll()はメモリを解放しないため、メモリリークに注意が必要です。

#include <QCoreApplication>
#include <QList>
#include <QDebug>
#include <QSharedPointer> // スマートポインタを使用

class Product
{
public:
    Product(const QString& name, double price) : m_name(name), m_price(price) {
        qDebug() << "Product created: " << m_name;
    }
    ~Product() {
        qDebug() << "Product destroyed: " << m_name;
    }

    // 比較演算子 (内容で比較)
    bool operator==(const Product& other) const {
        return m_name == other.m_name && m_price == other.m_price;
    }

    // デバッグ表示用
    friend QDebug operator<<(QDebug debug, const Product& p) {
        QDebugStateSaver saver(debug);
        debug.nospace() << "Product(" << p.m_name << ", " << p.m_price << ")";
        return debug;
    }

private:
    QString m_name;
    double m_price;
};

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

    // --- 例1: 生ポインタの場合 (非推奨、メモリリークの可能性あり) ---
    qDebug() << "--- 生ポインタの例 ---";
    QList<Product*> rawPointerList;
    Product* p1 = new Product("Laptop", 1200.0);
    Product* p2 = new Product("Mouse", 25.0);
    Product* p3 = new Product("Laptop", 1200.0); // p1 と同じ内容だが別オブジェクト
    Product* p4 = new Product("Keyboard", 75.0);
    rawPointerList << p1 << p2 << p3 << p4;

    qDebug() << "元の生ポインタリスト:" << rawPointerList;

    // removeAll() はポインタのアドレスを比較するため、p1を渡すとp1だけが削除される
    // p3 は削除されない
    qsizetype removedRawCount = rawPointerList.removeAll(p1);
    qDebug() << "削除された生ポインタの数 (p1のアドレスと一致):" << removedRawCount; // 出力: 1 (p1のアドレスのみ削除)
    qDebug() << "変更後の生ポインタリスト:" << rawPointerList; // p3は残る
    // !! メモリリーク注意: 削除されたp1が指すオブジェクトはdeleteされていない

    // 残りのオブジェクトを解放 (手動)
    qDeleteAll(rawPointerList); // 残りの要素が指すオブジェクトを削除
    rawPointerList.clear(); // リストをクリア

    qDebug() << "\n--- QSharedPointerの例 (推奨) ---";
    // --- 例2: QSharedPointerを使用する場合 (推奨) ---
    QList<QSharedPointer<Product>> sharedPointerList;
    sharedPointerList << QSharedPointer<Product>(new Product("Laptop", 1200.0))
                      << QSharedPointer<Product>(new Product("Mouse", 25.0))
                      << QSharedPointer<Product>(new Product("Laptop", 1200.0)) // 内容は同じだが別のQSharedPointerオブジェクト
                      << QSharedPointer<Product>(new Product("Keyboard", 75.0));

    qDebug() << "元のスマートポインタリスト:";
    for (const auto& p : sharedPointerList) {
        qDebug() << p.data(); // QSharedPointerが管理する生ポインタを表示
    }

    // "Laptop", 1200.0 の内容を持つProductをすべて削除したい場合
    // QSharedPointerはoperator==も提供しているため、新しいQSharedPointerを作成して比較できる
    qsizetype removedSharedCount = sharedPointerList.removeAll(
        QSharedPointer<Product>(new Product("Laptop", 1200.0)) // このProductは比較のためだけに使われ、すぐ解放される
    );

    qDebug() << "削除されたスマートポインタの数 (内容が一致):" << removedSharedCount; // 出力: 2
    qDebug() << "変更後のスマートポインタリスト:";
    for (const auto& p : sharedPointerList) {
        qDebug() << p.data(); // 削除された要素は表示されない
    }
    // QSharedPointerのおかげで、リストから削除されたProductオブジェクトは自動的に破棄される (Product destroyedメッセージが表示される)

    return a.exec();
}

解説

  • QSharedPointerの例
    こちらが推奨される方法です。QSharedPointerは参照カウントを持つスマートポインタであり、removeAll()でリストから削除されると参照カウントが減少し、ゼロになると管理しているオブジェクトを自動的にdeleteします。QSharedPointer自体もoperator==をオーバーロードしており、デフォルトでは管理しているポインタのアドレスで比較しますが、テンプレート引数ATを明示的に指定して、内容による比較を行うこともできます(上記の例では、新しいProductオブジェクトを生成し、そのオブジェクトをQSharedPointerでラップして比較しています。これにより、Productクラスで定義したoperator==が間接的に使用されます)。
  • 生ポインタの例
    removeAll(p1)p1のアドレスと一致する要素のみを削除します。p3p1と内容が同じでも、異なるアドレスを持つため削除されません。また、p1が指していたProductオブジェクトはdeleteされないため、メモリリークが発生します。手動でqDeleteAll(rawPointerList)を呼び出すことで残りのオブジェクトを解放しています。

ATテンプレート引数を明示的に指定するケースは稀ですが、例えばQStringのリストに対して、QStringView(C++17以降)のような異なるが比較可能な型で削除したい場合に有用です。

#include <QCoreApplication>
#include <QList>
#include <QString>
#include <QStringView> // C++17以降
#include <QDebug>

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

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

    qDebug() << "元の文字列リスト:" << stringList;

    // QStringList::removeAll() は、QList<QString>::removeAll() と同じ機能を提供します。
    // デフォルトのT型(QString)を使用する場合
    qsizetype removedCount1 = stringList.removeAll("apple");
    qDebug() << "削除された要素の数 (QStringで比較):" << removedCount1; // 出力: 2
    qDebug() << "変更後の文字列リスト:" << stringList; // 出力: ("banana", "cherry", "date")

    stringList.clear(); // リストをリセット
    stringList << "apple" << "banana" << "cherry" << "apple" << "date";

    // template <typename AT = T> のATを明示的に指定する例
    // QStringView を使って比較する場合 (QtのQStringはQStringViewとの比較をサポートしています)
    qsizetype removedCount2 = stringList.removeAll<QStringView>("apple"); // "apple"はQStringViewに変換される
    qDebug() << "削除された要素の数 (QStringViewで比較):" << removedCount2; // 出力: 2
    qDebug() << "変更後の文字列リスト:" << stringList; // 出力: ("banana", "cherry", "date")

    return a.exec();
}

解説

  • stringList.removeAll<QStringView>("apple") のように、ATQStringViewとして明示的に指定することもできます。QStringQStringViewと比較可能であるため、これは正しく機能します。この場合、コンパイラは"apple"リテラルをQStringViewに変換してから比較を行います。これは、文字列比較の際に一時的なQStringオブジェクトの生成を避けることができ、パフォーマンス上有利になる場合があります。
  • stringList.removeAll("apple") のように、ATを明示的に指定しない場合、QListの要素型(この場合はQString)がデフォルトで使用されます。


QList::removeAll() の代替方法

QList::removeOne() (最初の1つだけ削除)

removeAll()すべての出現を削除するのに対し、removeOne()最初に見つかった1つの要素だけを削除します。

特徴

  • 戻り値
    削除が成功した場合は true、見つからなかった場合は false を返します。
  • 単一削除
    特定の要素がリスト内に複数存在する場合でも、最初に見つかった1つだけを削除したい場合に最適です。

使用例

#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() << "元のリスト:" << numbers; // (10, 20, 30, 20, 40)

    // 最初の '20' だけを削除
    bool removed = numbers.removeOne(20);

    qDebug() << "削除成功:" << removed; // true
    qDebug() << "変更後のリスト:" << numbers; // (10, 30, 20, 40)

    removed = numbers.removeOne(99); // 存在しない値を削除
    qDebug() << "削除成功 (99):" << removed; // false

    return a.exec();
}

QList::erase() とイテレータ (特定の範囲または条件での削除)

erase() メソッドはイテレータを引数に取り、指定された位置の要素を削除します。これとイテレータを組み合わせることで、より柔軟な削除ロジックを実装できます。

特徴

  • パフォーマンス
    大量の要素を削除する場合、removeAll() よりも効率的になることがあります(特にカスタム型の場合)。
  • イテレータベース
    QMutableListIteratorstd::remove_if と組み合わせて、複雑な条件での削除や、ループ中に要素を削除する際に利用します。

使用例

a) QMutableListIterator を使用した条件付き削除

リストを反復処理しながら、特定の条件に合致する要素を削除したい場合に適しています。

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

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

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

    qDebug() << "元のリスト:" << numbers; // (10, 20, 30, 25, 40, 15)

    QMutableListIterator<int> i(numbers);
    while (i.hasNext()) {
        int value = i.next();
        // 20より大きく30より小さい値を削除
        if (value > 20 && value < 30) {
            i.remove(); // 現在の要素を削除
        }
    }

    qDebug() << "変更後のリスト (20 < val < 30):" << numbers; // (10, 20, 30, 40, 15)

    return a.exec();
}

b) std::remove_if と QList::erase() を組み合わせた削除 (C++標準ライブラリの活用)

より汎用的な条件で要素を削除したい場合、C++標準ライブラリの std::remove_if 関数と QList::erase() を組み合わせることができます。これは「erase-remove idiom」として知られています。

#include <QCoreApplication>
#include <QList>
#include <QDebug>
#include <algorithm> // for std::remove_if

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

    QList<QString> words;
    words << "apple" << "banana" << "apricot" << "grape" << "avocado";

    qDebug() << "元のリスト:" << words; // ("apple", "banana", "apricot", "grape", "avocado")

    // 'a' で始まる文字列を削除するラムダ関数
    auto predicate = [](const QString& s){
        return s.startsWith("a");
    };

    // std::remove_if は、条件に合致する要素をリストの末尾に移動させ、新しい論理的な終端を指すイテレータを返す
    QList<QString>::iterator newEnd = std::remove_if(words.begin(), words.end(), predicate);

    // erase() を使って、論理的な終端から実際の終端までの要素を物理的に削除する
    words.erase(newEnd, words.end());

    qDebug() << "変更後のリスト (aで始まる文字列を削除):" << words; // ("banana", "grape")

    return a.exec();
}

新しいリストの作成 (変換・フィルタリング)

元のリストを変更せず、条件に合致しない要素だけを含む新しいリストを作成する方法です。

特徴

  • メモリ使用量
    新しいリストを作成するため、一時的にメモリ使用量が増加する可能性があります。
  • 読みやすさ
    シンプルなフィルタリング条件であれば、コードが読みやすくなります。
  • 非破壊的
    元のリストは変更されません。

使用例

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

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

    QList<int> originalNumbers;
    originalNumbers << 10 << 20 << 30 << 20 << 40 << 50 << 20;

    qDebug() << "元のリスト:" << originalNumbers; // (10, 20, 30, 20, 40, 50, 20)

    QList<int> filteredNumbers;
    for (int number : originalNumbers) {
        if (number != 20) { // 20以外の要素だけを新しいリストに追加
            filteredNumbers.append(number);
        }
    }

    qDebug() << "元のリスト (変更なし):" << originalNumbers; // (10, 20, 30, 20, 40, 50, 20)
    qDebug() << "フィルタリングされた新しいリスト:" << filteredNumbers; // (10, 30, 40, 50)

    return a.exec();
}

選択は、以下の要因によって異なります。

  • コードの複雑性
    シンプルなケースでは removeAll()removeOne() が最も簡潔ですが、複雑な条件やポインタのメモリ管理が必要な場合は、イテレータやスマートポインタの利用が不可欠になります。
  • パフォーマンス要件
    大量の要素を扱う場合、各メソッドのパフォーマンス特性を考慮する必要があります。removeAll() は内部で効率的に実装されていますが、複雑な比較やメモリ管理が関わる場合は、イテレータによる手動制御が有利になることもあります。
  • 元のリストの変更の可否
    元のリストを変更してもよいのか、それとも元のリストは保持しつつ新しいリストが必要なのか?
  • 削除の粒度
    1つだけ削除したいのか (removeOne())、すべて削除したいのか (removeAll())、それとも特定の条件に基づいて削除したいのか (erase() + イテレータ、または新しいリストの作成)?