【Qtプログラミング】QList::endsWith()の落とし穴と解決策

2025-06-06

QList::endsWith()とは

QList::endsWith()は、QtフレームワークのコンテナクラスであるQListのメンバ関数の一つです。この関数は、リストが特定の要素で終わっているかどうか、つまりリストの最後の要素が指定された値と等しいかどうかを判定するために使用されます。

構文

bool QList::endsWith(const T &value) const
  • 戻り値: bool型。リストがvalueで終わっていればtrueを、そうでなければfalseを返します。
  • value: 比較対象となる値です。
  • T: QListが格納している要素の型です。

動作

QList::endsWith(const T &value)は、以下のロジックで動作します。

  1. リストが空の場合: リストが空である場合、どの要素でも終わっているとは言えないため、falseを返します。
  2. リストが空でない場合: リストの最後の要素(QList::last()またはQList::at(size() - 1)でアクセスできる要素)と、引数として渡されたvalueを比較します。
    • 両者が等しければtrueを返します。
    • 等しくなければfalseを返します。
#include <QList>
#include <QDebug>

int main() {
    QList<int> myList;
    myList << 10 << 20 << 30 << 40;

    // 40で終わっているか?
    qDebug() << "List ends with 40:" << myList.endsWith(40); // true

    // 50で終わっているか?
    qDebug() << "List ends with 50:" << myList.endsWith(50); // false

    // リストが空の場合
    QList<QString> emptyList;
    qDebug() << "Empty list ends with 'apple':" << emptyList.endsWith("apple"); // false

    // リストに一つの要素がある場合
    QList<double> singleElementList;
    singleElementList << 3.14;
    qDebug() << "Single element list ends with 3.14:" << singleElementList.endsWith(3.14); // true
    qDebug() << "Single element list ends with 2.71:" << singleElementList.endsWith(2.71); // false

    return 0;
}

上記のコードを実行すると、以下のような出力が得られます。

List ends with 40: true
List ends with 50: false
Empty list ends with 'apple': false
Single element list ends with 3.14: true
Single element list ends with 2.71: false


リストが空の場合の誤解

よくある間違い
QList::endsWith()は、リストが空の場合でも特定の要素で終わっていると誤解してしまうことです。

説明
QList::endsWith()のドキュメントにも明記されていますが、リストが空の場合、endsWith()は常にfalseを返します。これは、リストに要素がないため、どの要素で「終わる」こともできないという論理に基づいています。

トラブルシューティング
endsWith()を使用する前に、リストが空でないことを確認する必要があります。

QList<int> myList; // 空のリスト

if (myList.isEmpty()) {
    qDebug() << "リストは空です。endsWith()は常にfalseを返します。";
}
qDebug() << "myList.endsWith(5):" << myList.endsWith(5); // false

比較対象の型不一致またはoperator==の未定義

よくある間違い
QListがカスタム型を格納している場合、そのカスタム型に対してoperator==が適切に定義されていないと、コンパイルエラーになったり、予期せぬ比較結果になったりします。

説明
QList::endsWith()は、内部的にリストの最後の要素と引数valueを比較するために、格納されている型Toperator==を使用します。もしTが標準のintやQStringなどではなく、独自のクラスや構造体である場合、そのクラスにoperator==が定義されていなければ、比較ができません。

トラブルシューティング
カスタム型をQListに格納し、endsWith()を使用する場合は、そのカスタム型に対してoperator==を適切に定義する必要があります。

// NG例: operator==が定義されていない
struct MyData {
    int id;
    QString name;
};

// OK例: operator==を定義する
struct MyData {
    int id;
    QString name;

    bool operator==(const MyData& other) const {
        return id == other.id && name == other.name;
    }
};

QList<MyData> myCustomList;
myCustomList << {1, "Apple"} << {2, "Banana"} << {3, "Cherry"};

MyData searchData = {3, "Cherry"};
qDebug() << "List ends with Cherry:" << myCustomList.endsWith(searchData); // OKならtrue

大文字・小文字の区別(QStringの場合)

よくある間違い
QList<QString>に対してendsWith()を使用する際、大文字・小文字の区別を考慮せずに期待と異なる結果を得ること。

説明
QString::operator==はデフォルトで大文字・小文字を区別します。そのため、endsWith()もこの挙動に従います。例えば、リストの最後の要素が "Apple" で、endsWith("apple")とするとfalseが返されます。

トラブルシューティング
大文字・小文字を区別しない比較が必要な場合は、endsWith()の前にリストの最後の要素や比較対象の文字列を全て大文字または小文字に変換してから比較を行うか、より適切なQStringの比較関数を検討します。ただし、QList::endsWith()自体にはQt::CaseSensitivityのようなオプションはありません。

QList<QString> stringList;
stringList << "One" << "Two" << "THREE";

qDebug() << "Ends with 'THREE':" << stringList.endsWith("THREE"); // true
qDebug() << "Ends with 'three':" << stringList.endsWith("three"); // false (大文字小文字を区別する)

// 大文字小文字を区別しない比較の例(endsWith()に直接は適用できないが、必要に応じて)
if (!stringList.isEmpty() && stringList.last().toLower() == "three") {
    qDebug() << "Ends with 'three' (case-insensitive): true";
}

暗黙の共有(Implicit Sharing)とコピーの発生

よくある間違い
QListは暗黙の共有(Implicit Sharing)を使用しており、非定数な操作を行うとディープコピーが発生する可能性があります。endsWith()const関数なので直接的な問題にはなりませんが、リストの操作とendsWith()の呼び出しの順序によって、意図しないパフォーマンス低下や複雑さが生じる可能性があります。

説明
QListのようなQtのコンテナクラスは、コピー時にデータのディープコピーをすぐには行わず、データへの参照を共有します。しかし、いずれかのコピーが変更(非定数操作)されると、その時点でディープコピーが行われます。endsWith()const関数なのでリストを変更しないため、ディープコピーを引き起こしません。しかし、もしendsWith()の前にリストに対する変更操作(append(), removeLast()など)があった場合、そこでディープコピーが発生し、その後endsWith()が呼び出される、という流れになります。これはエラーというよりはパフォーマンスの考慮点です。

トラブルシューティング
通常の使用ではendsWith()が原因でパフォーマンス問題が発生することは稀ですが、非常に大規模なリストに対して頻繁にリストの変更とendsWith()を組み合わせるようなケースでは、この挙動を意識すると良いでしょう。

ポインタ型を格納している場合

よくある間違い
QList<MyObject*>のようなポインタのリストで、ポインタの値そのもので比較を行ってしまうこと。

説明
QList<T*>::endsWith(const T* &value)は、ポインタの値(アドレス)を比較します。もし、ポインタが指すオブジェクトの内容で比較したい場合は、endsWith()は適切な関数ではありません。

トラブルシューティング
ポインタが指すオブジェクトの内容で比較したい場合は、QList::last()で最後の要素(ポインタ)を取得し、そのポインタが指すオブジェクトの内容を直接比較する必要があります。

class MyObject {
public:
    int value;
    MyObject(int v) : value(v) {}
    // ポインタの先の内容を比較するためのメソッド
    bool equals(const MyObject* other) const {
        return other && value == other->value;
    }
};

QList<MyObject*> objectList;
objectList << new MyObject(10) << new MyObject(20) << new MyObject(30);

MyObject* searchObj = new MyObject(30); // 新しいオブジェクトだが値は同じ

// これはアドレス比較なのでfalseになる可能性が高い
qDebug() << "Ends with searchObj (address compare):" << objectList.endsWith(searchObj); // false

// 内容で比較したい場合
if (!objectList.isEmpty() && objectList.last()->equals(searchObj)) {
    qDebug() << "Ends with searchObj (content compare): true";
}

// メモリリークを防ぐためにオブジェクトを削除
qDeleteAll(objectList);
delete searchObj;


QList::endsWith()は、リストの最後の要素が特定の期待値と一致するかどうかを効率的にチェックするための関数です。様々なデータ型やシナリオでの使用例を見ていきましょう。

基本的な数値型 (int) の例

最も基本的な使用例です。整数値のリストが特定の整数で終わっているかを確認します。

#include <QList>
#include <QDebug> // qCoutの代わりにQDebugを使用

int main() {
    QList<int> numbers;
    numbers << 1 << 2 << 3 << 4 << 5;

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

    // リストが5で終わっているか?
    if (numbers.endsWith(5)) {
        qDebug() << "numbersは5で終わっています。"; // このメッセージが表示される
    } else {
        qDebug() << "numbersは5で終わっていません。";
    }

    // リストが10で終わっているか?
    if (numbers.endsWith(10)) {
        qDebug() << "numbersは10で終わっています。";
    } else {
        qDebug() << "numbersは10で終わっていません。"; // このメッセージが表示される
    }

    // リストを修正して再度チェック
    numbers.removeLast(); // 5を削除
    numbers << 99;         // 99を追加
    qDebug() << "変更後のリスト:" << numbers;

    if (numbers.endsWith(99)) {
        qDebug() << "numbersは99で終わっています。"; // このメッセージが表示される
    }

    return 0;
}

出力例

リスト: QList(1, 2, 3, 4, 5)
numbersは5で終わっています。
numbersは10で終わっていません。
変更後のリスト: QList(1, 2, 3, 4, 99)
numbersは99で終わっています。

文字列型 (QString) の例

QStringのリストが特定の文字列で終わっているかを確認します。大文字・小文字の区別に注意が必要です。

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

int main() {
    QList<QString> fruits;
    fruits << "Apple" << "Banana" << "Cherry" << "Date";

    qDebug() << "フルーツリスト:" << fruits;

    // "Date"で終わっているか? (完全に一致)
    if (fruits.endsWith("Date")) {
        qDebug() << "fruitsは'Date'で終わっています。"; // このメッセージが表示される
    }

    // "date"で終わっているか? (大文字小文字の違い)
    if (fruits.endsWith("date")) {
        qDebug() << "fruitsは'date'で終わっています。(小文字)";
    } else {
        qDebug() << "fruitsは'date'で終わっていません。(大文字小文字を区別)"; // このメッセージが表示される
    }

    // リストが空の場合の例
    QList<QString> emptyList;
    qDebug() << "空のリストが'test'で終わっているか?" << emptyList.endsWith("test"); // false

    return 0;
}

出力例

フルーツリスト: QList("Apple", "Banana", "Cherry", "Date")
fruitsは'Date'で終わっています。
fruitsは'date'で終わっていません。(大文字小文字を区別)
空のリストが'test'で終わっているか? false

カスタム型 (struct または class) の例

QListがカスタムオブジェクトを格納している場合、endsWith()を使用するには、そのカスタム型にoperator==を定義する必要があります。

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

// カスタム構造体
struct Person {
    int id;
    QString name;

    // QList::endsWith()で比較するために必要
    // 2つのPersonオブジェクトが等しいかどうかを定義する
    bool operator==(const Person& other) const {
        return id == other.id && name == other.name;
    }

    // QDebugで表示可能にするためのオペレータオーバーロード (任意だがデバッグに便利)
    friend QDebug operator<<(QDebug debug, const Person& p) {
        QDebugStateSaver saver(debug);
        debug.nospace() << "Person(id:" << p.id << ", name:'" << p.name << "')";
        return debug;
    }
};

int main() {
    QList<Person> people;
    people << Person{101, "Alice"}
           << Person{102, "Bob"}
           << Person{103, "Charlie"};

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

    // Charlieさんで終わっているか?
    Person charlie = {103, "Charlie"};
    if (people.endsWith(charlie)) {
        qDebug() << "peopleはCharlieさんで終わっています。"; // このメッセージが表示される
    } else {
        qDebug() << "peopleはCharlieさんで終わっていません。";
    }

    // Davidさんで終わっているか? (存在しない)
    Person david = {104, "David"};
    if (people.endsWith(david)) {
        qDebug() << "peopleはDavidさんで終わっています。";
    } else {
        qDebug() << "peopleはDavidさんで終わっていません。"; // このメッセージが表示される
    }

    // IDは同じだが名前が違う場合 (==がfalseになる例)
    Person charlieWrongName = {103, "Charly"};
    if (people.endsWith(charlieWrongName)) {
        qDebug() << "peopleはCharlyさんで終わっています。(IDは同じですが名前が違います)";
    } else {
        qDebug() << "peopleはCharlyさんで終わっていません。(名前が異なります)"; // このメッセージが表示される
    }

    return 0;
}

出力例

人物リスト: (Person(id:101, name:'Alice'), Person(id:102, name:'Bob'), Person(id:103, name:'Charlie'))
peopleはCharlieさんで終わっています。
peopleはDavidさんで終わっていません。
peopleはCharlyさんで終わっていません。(名前が異なります)

条件分岐での利用例

endsWith()は、プログラムの流れを制御するための条件式として非常に役立ちます。

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

enum State {
    Start,
    Processing,
    Finished,
    Error
};

int main() {
    QList<State> processLog;
    processLog << Start << Processing << Processing;

    qDebug() << "現在のプロセスログ:" << processLog; // QDebugでenumを表示するには少し工夫が必要な場合がある

    if (processLog.endsWith(Processing)) {
        qDebug() << "プロセスはまだ処理中です。";
        // 次のステップに進むなどのロジック
        processLog << Finished;
    } else if (processLog.endsWith(Error)) {
        qDebug() << "プロセスでエラーが発生しました。";
        // エラーハンドリングのロジック
    } else if (processLog.endsWith(Finished)) {
        qDebug() << "プロセスは完了しました。";
    }

    qDebug() << "更新されたプロセスログ:" << processLog;

    // エラー状態のシミュレーション
    processLog.clear();
    processLog << Start << Error;
    qDebug() << "新しいプロセスログ (エラー):" << processLog;

    if (processLog.endsWith(Processing)) {
        qDebug() << "プロセスはまだ処理中です。";
    } else if (processLog.endsWith(Error)) {
        qDebug() << "プロセスでエラーが発生しました。"; // このメッセージが表示される
    }

    return 0;
}
現在のプロセスログ: QList(0, 1, 1)  // Start=0, Processing=1などと仮定
プロセスはまだ処理中です。
更新されたプロセスログ: QList(0, 1, 1, 2)
新しいプロセスログ (エラー): QList(0, 3)
プロセスでエラーが発生しました。


QList::endsWith()の代替方法

主な代替方法は、リストの最後の要素に直接アクセスして比較を行うことです。

QList::last() と operator== を組み合わせる

これは最も直接的で一般的な代替方法です。まずQList::last()関数を使ってリストの最後の要素を取得し、その後その要素を比較したい値とoperator==で比較します。

特徴

  • 空のリストの取り扱い
    last()はリストが空の場合に未定義の動作を引き起こす可能性があるため、必ずisEmpty()でリストが空でないことを確認する必要があります
  • パフォーマンス
    endsWith()と同様に、最後の要素へのアクセスは通常O(1)(定数時間)で行われます。比較自体のコストはデータ型に依存します。
  • 明示的
    最後の要素へのアクセスと比較が明確に分離されています。

コード例

#include <QList>
#include <QDebug>

int main() {
    QList<int> numbers;
    numbers << 10 << 20 << 30;

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

    // 1. last() と operator== を使用
    if (!numbers.isEmpty() && numbers.last() == 30) {
        qDebug() << "numbersは30で終わっています。(last() + ==)";
    } else {
        qDebug() << "numbersは30で終わっていません。(last() + ==)";
    }

    QList<QString> words;
    words << "apple" << "banana" << "cherry";

    qDebug() << "単語リスト:" << words;

    // QStringでの例
    if (!words.isEmpty() && words.last() == "cherry") {
        qDebug() << "wordsは'cherry'で終わっています。(last() + ==)";
    }

    // 空のリストでの注意
    QList<double> emptyList;
    if (!emptyList.isEmpty() && emptyList.last() == 0.0) { // !emptyList.isEmpty() のチェックが重要
        qDebug() << "このメッセージは表示されません。";
    } else {
        qDebug() << "emptyListは空なので、last()は安全に使用できませんでした。";
    }

    return 0;
}

QList::at(int index) と operator== を組み合わせる

last()と同様に、at()関数を使ってリストの最後の要素にアクセスすることもできます。最後の要素のインデックスはsize() - 1です。

特徴

  • 空のリストの取り扱い
    at()も無効なインデックスが指定された場合に未定義の動作を引き起こすため、isEmpty()またはsize()のチェックが必須です
  • パフォーマンス
    at()も通常O(1)です。
  • 汎用性
    at()は任意のインデックスにアクセスできるため、特定の状況ではより柔軟です(例えば、最後から2番目の要素をチェックする場合など)。

コード例

#include <QList>
#include <QDebug>

int main() {
    QList<double> values;
    values << 1.1 << 2.2 << 3.3 << 4.4;

    qDebug() << "値リスト:" << values;

    // 2. at(size() - 1) と operator== を使用
    if (!values.isEmpty() && values.at(values.size() - 1) == 4.4) {
        qDebug() << "valuesは4.4で終わっています。(at(size()-1) + ==)";
    } else {
        qDebug() << "valuesは4.4で終わっていません。(at(size()-1) + ==)";
    }

    // 最後から2番目の要素をチェックする例 (endsWith()では直接できない)
    if (values.size() >= 2 && values.at(values.size() - 2) == 3.3) {
        qDebug() << "valuesの最後から2番目の要素は3.3です。";
    }

    return 0;
}

QList::endsWith() と同等のロジックを自作する

endsWith()が内部的に行っているロジックを自分で実装することもできます。これは、特別なエラーハンドリングや、比較ロジックをカスタマイズしたい場合に有用です。

特徴

  • 学習目的
    Qtのコンテナの内部動作を理解するのに役立ちます。
  • 冗長性
    既存のendsWith()関数があるため、通常は冗長です。
  • 完全な制御
    空のリストの場合の動作や、比較方法(大文字・小文字を区別しない比較など)を完全にカスタマイズできます。

コード例

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

// endsWith()に似たカスタム関数 (大文字小文字を区別しないQStringのendsWith)
template<typename T>
bool myEndsWith(const QList<T>& list, const T& value) {
    if (list.isEmpty()) {
        return false;
    }
    return list.last() == value;
}

// QString専用で大文字小文字を区別しないバージョン
bool myEndsWithCaseInsensitive(const QList<QString>& list, const QString& value) {
    if (list.isEmpty()) {
        return false;
    }
    return list.last().compare(value, Qt::CaseInsensitive) == 0;
}


int main() {
    QList<QString> colors;
    colors << "Red" << "Green" << "BLUE";

    qDebug() << "色リスト:" << colors;

    // 自作関数 (標準の比較)
    if (myEndsWith(colors, QString("BLUE"))) {
        qDebug() << "colorsは'BLUE'で終わっています。(myEndsWith)"; // 表示される
    }

    // 自作関数 (大文字小文字を区別しない比較)
    if (myEndsWithCaseInsensitive(colors, QString("blue"))) {
        qDebug() << "colorsは'blue'で終わっています。(myEndsWithCaseInsensitive)"; // 表示される
    } else {
        qDebug() << "colorsは'blue'で終わっていません。(myEndsWithCaseInsensitive: 表示されないはず)";
    }

    return 0;
}
色リスト: QList("Red", "Green", "BLUE")
colorsは'BLUE'で終わっています。(myEndsWith)
colorsは'blue'で終わっています。(myEndsWithCaseInsensitive)
  • カスタム関数の実装は、endsWith()の比較ロジックが標準のoperator==では不十分な場合(例: 大文字・小文字を区別しない文字列比較、オブジェクトの一部のみでの比較など)に検討します。

  • QList::at(size() - 1)operator==の組み合わせは、last()と似ていますが、インデックスベースのアクセスを強調したい場合に選択肢となります。これも空のリストチェックが必須です。

  • QList::last()operator==の組み合わせは、endsWith()が使えない古いQtバージョンを使用している場合や、endsWith()が提供する機能以上の柔軟性(例: 最後の要素をチェックしてから別の処理を行う)が必要な場合に有効です。ただし、空のリストチェックを忘れないように注意が必要です。

  • ほとんどのケースでは、QList::endsWith()が最良の選択です。 これは最も簡潔で意図が明確であり、内部で空のリストのチェックも行ってくれるため安全です。