Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

cbegin, cend invalid results for empty containers in Qt 5.2.1

Tags:

c++

c++11

qt

In Qt 5.2.1, how is it that the following code results are different?

QVector<int> c;
if (c.cbegin() != c.begin())
{
   std::cout << "Argh!" << std::endl;
}

this prints "argh" but the following doesn't.

QVector<int> c;
if (c.begin() != c.cbegin())
{
    std::cout << "Argh!" << std::endl;
}

Notice that cbegin and begin places are switched. But if you change the container state I mean for example push_back something in it, it works correctly. Seems to me that before calling any mutable method on container, cbegin and cend are invalid. Is it a bug or feature?

like image 311
R.J Avatar asked Aug 27 '14 06:08

R.J


1 Answers

The behavior you're observing has to do with the order of the calls being made to QVector::begin and QVector::cbegin. If the call to the former happens before the call to the latter, then their return values compare equal, but if you reverse the order, they do not compare equal. This can be demonstrated by the following code:

QVector<int> c;
std::cout << static_cast<void const *>(c.begin()) << std::endl;
std::cout << static_cast<void const *>(c.cbegin()) << std::endl;

The addresses printed will be the same. However, if you swap the order of the two print statements, the addresses will be different.

If you dig into the source code for the two member functions, you'll see

inline iterator begin() { detach(); return d->begin(); }
inline const_iterator cbegin() const { return d->constBegin(); }

And tracing back further, the bodies of both d->begin() and d->constBegin() are identical. So the difference is in the call to QVector::detach() in the first case. This is defined as

template <typename T>
void QVector<T>::detach()
{
    if (!isDetached()) {
#if QT_SUPPORTS(UNSHARABLE_CONTAINERS)
        if (!d->alloc)
            d = Data::unsharableEmpty();
        else
#endif
            reallocData(d->size, int(d->alloc));
    }
    Q_ASSERT(isDetached());
}

An explanation of what's going on can be found here.

Qt’s containers are implicitly shared – when you copy an object, only a pointer to the data is copied. When the object is modified, it first creates a deep copy of the data so that it does not affect the other objects. The process of creating a deep copy of the day is called detach

Since, STL-style iterators are conceptually just pointers, they make modification to the underlying data directly. As a consequence, non-const iterators detach when created using Container::begin() so that other implicitly shared instances do not get affected by the changes.

So, if the call to QVector::begin() happens first, then the container's underlying storage is reallocated, and the subsequent call to QVector::cbegin() will return the same pointer. However, reverse them and the call to QVector::cbegin() returns a pointer to the shared storage before any reallocation happens.

like image 65
Praetorian Avatar answered Oct 05 '22 09:10

Praetorian