Why does std::prev() applied to a QSet<int*> iterator give me a segfault?

111 Views Asked by At

I have Debian 11 amd64, Qt 5.15.2, and try to use std::prev(it.end()) to get the last element in a QSet, like this:

#include <QSet>

int main() {
    return *(int*)(*(std::prev(QSet<int*>({new int(10)}).end())));
}

But when I run this, I experience a segfault.

If I change std::prev() to operator--, all works perfectly - I get a return code of 10, as expected (the last item of the set I created is int* to 10). But operator-- is deprecated in Qt 5.15.

It probably works nice on different Qt versions, but my goal is to make this exact version of Qt working.

Does anybody know what I am doing wrong?

P.S. You may check a ready-to-use demo code snippet at https://github.com/makise-homura/qt-crash-test if you need.

1

There are 1 best solutions below

0
Parisa.H.R On

The issue of your code is that you are dereferencing a pointer without checking whether it is valid or not. In C++, it's crucial to ensure that you only dereference valid pointers to avoid undefined behavior.

I didn't understand why did you return it in main function but you can write it like this:

#include <QSet>
#include <iostream>

int  main()
{
    QSet<int *>  mySet{ new int(10) };

    if (!mySet.isEmpty())
    {
        int *ptr = *mySet.begin();     
        int  a   = *ptr;      // Dereference the pointer to get the value

        std::cout << "a = " << a << std::endl;
    }
    else
    {
        std::cerr << "Set is empty." << std::endl;
    }

    return 0;
}

and comment of @RemyLebeau is True,because of that I use *mySet.begin().

in Qt 5.14.0 or older you can write it like this,I test it and works.

#include <QSet>
#include <iostream>

int main()
{
    QSet<int*> mySet;
    mySet.insert(new int(10));

    if (!mySet.isEmpty()) {
        auto it = std::prev(mySet.end());
        if (*it != nullptr) {
            int a = *(*it);
            std::cout << "a = " << a << std::endl;
        } else {
            std::cout << "Pointer is null." << std::endl;
        }
    } else {
        std::cout << "Set is empty." << std::endl;
    }

    return 0;
}

As you mentioned earlier, QSet does not maintain an order of elements, and there is no inherent concept of a "last" element in a QSet.

To work with a QSet, if you need to access elements based on some order, you would need to manage that order separately, perhaps by maintaining an auxiliary data structure like a QList or QVector to keep track of the order in which elements were added to the set. Here's an example of how you can do this:

#include <QSet>
#include <QList>
#include <iostream>

int  main()
{
    QSet<int *>   mySet;
    QList<int *>  insertionOrder;   // To maintain insertion order

    mySet.insert(new int(10));
    insertionOrder.append(new int(10));

    mySet.insert(new int(20));
    insertionOrder.append(new int(20));

    mySet.insert(new int(30));
    insertionOrder.append(new int(30));

    if (!mySet.isEmpty())
    {
        int *lastElement = insertionOrder.last();

        std::cout << "Last element: " << *lastElement << std::endl;

        // Don't forget to clean up the dynamically allocated memory
        delete lastElement;
    }
    else
    {
        std::cout << "Set is empty." << std::endl;
    }

    // Clean up remaining elements in mySet and insertionOrder
    for (auto element : mySet)
    {
        delete element;
    }

    insertionOrder.clear();

    return 0;
}