QListの要素削除テクニック:removeAll()以外の選択肢と使い分け
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()
を呼び出すことができます。
-
デフォルトの型を使用する場合(一般的な使用法)
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
に推論されます。 -
異なる型を指定する場合(あまり一般的ではないが、比較可能な場合に有用)
例えば、QList<QString>
があり、const char*
型の文字列を削除したい場合などです。QString
はconst 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
として指定しています。これにより、QString
とQStringView
の間で比較が行われます。
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
する必要があります。あるいは、リストにスマートポインタ(QSharedPointer
やstd::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_if
とQList::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()
を使用する際の主なトラブルシューティングは、以下の点に集約されます。
- 比較のロジック
operator==
が正しく実装されているか、特にカスタム型やポインタの場合に注意する。 - メモリ管理
ポインタを格納している場合、removeAll()
がメモリを解放しないことを理解し、必要に応じて手動またはスマートポインタで解放する。 - イテレーション中の変更
foreach
ループではなく、QMutableListIterator
やstd::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
) が返されます。
- 最も基本的な使用例です。
-
QString
型のQList
でremoveAll()
を使用する例QStringList
から文字列"Apple"
をすべて削除します。QString
はoperator==
を適切に実装しているため、期待通りに動作します。
-
カスタムクラス
MyObject
のQList
でremoveAll()
を使用する例MyObject
というカスタムクラスを定義しています。- 重要な点
QList::removeAll()
がMyObject
オブジェクトを比較できるように、MyObject
クラス内でoperator==
をオーバーロードしています。このoperator==
が、removeAll()
が各要素を比較する際に使用されます。 objectToRemove
というMyObject(1, "Alice")
を作成し、これと一致するすべてのオブジェクトを削除します。operator==
のデバッグ出力を見ることで、比較が行われていることがわかります。
-
ポインタ型の
QList
でremoveAll()
を使用する際の注意点QList<MyObject*>
のようにポインタを格納する場合の例です。obj1
とobj3
はnew MyObject(100, "PtrA")
で作成されており、内容は同じですが、メモリ上の異なるアドレスを指しています。pointerList.removeAll(obj1)
を呼び出すと、removeAll()
はobj1
のポインタの値(アドレス) とリスト内の他のポインタの値を比較します。- 結果として、
obj1
と同じアドレスを持つポインタだけが削除されます。obj3
は内容が同じでもアドレスが異なるため削除されません。 - トラブルシューティングの重要性
この例では、removeAll()
が呼び出された後も、削除されたobj1
の指すメモリは解放されません。これはメモリリークの原因となります。ポインタリストを扱う場合は、qDeleteAll()
や手動でのdelete
、またはスマートポインタ (QSharedPointer
など) を使用して、メモリを適切に管理する必要があります。例の最後でqDeleteAll()
を使ってリスト内のオブジェクトを解放しています。
-
異なる型を指定して
removeAll()
を使用する例 (滅多に使わない)QList<double>
からfloat
型の値 (2.5f
) を削除する例です。- この場合、
removeAll(2.5f)
と呼び出すと、AT
はfloat
に推論されます。double
とfloat
は比較可能なので、期待通りに動作します。 - このような明示的な型指定は、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==
を使用して、リスト内の各MyObject
とtarget
オブジェクトを比較し、一致するものを削除します。MyObject
クラスでは、operator==
をオーバーロードして、id
とname
の両方が一致する場合にオブジェクトが等しいと判断されるようにしています。
ポインタを格納している場合の注意と対処
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
のアドレスと一致する要素のみを削除します。p3
はp1
と内容が同じでも、異なるアドレスを持つため削除されません。また、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")
のように、AT
をQStringView
として明示的に指定することもできます。QString
はQStringView
と比較可能であるため、これは正しく機能します。この場合、コンパイラは"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()
よりも効率的になることがあります(特にカスタム型の場合)。 - イテレータベース
QMutableListIterator
やstd::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()
+ イテレータ、または新しいリストの作成)?