This question is more of an inquiry then actually seeking a solution for a problem.
QList::at()
not only doesn't check if an index is out of bounds but it also returns a const
hence it can be used only in a read-only
scenario:
const T &QList::at(int i) const
Returns the item at index position i in the list. i must be a valid index position in the list (i.e., 0 <= i < size()).
This function is very fast (constant time).
I've just found out these peculiar implementation details of QList
while trying to assign a new value to an element in my list and I'm wondering if there is a reason for doing that.
In C++ STL
if we take std::array
or std::vector
(I'm not taking std::list
since it doesn't have std::list::at()
at all) we have the following:
std::vector::at
The function automatically checks whether n is within the bounds of valid elements in the vector, throwing an out_of_range exception if it is not
At first I thought that the check is not included to ensure the "This function is very fast (constant time)." however after looking at the implementation of QList
I have to say even if the check was included a constant time (and high speed) is most certainly ensured.
A check for out of bounds would require two things (as far as I know):
O(1)
if we use some wacky notation). Obviously we can't have negative index (we can in Python
but it has another meaning ;))Check if given index is < QList::size()
- constant time is also present here since the way QList::size()
is implemented is:
inline int size() const Q_DECL_NOTHROW { return d->end - d->begin; }
This is again O(1)
(if I'm not mistaken) since both values are stored inside the QList::d
parameter which is a pointer to
struct Data {
QtPrivate::RefCount ref;
int alloc, begin, end;
void *array[1];
};
so accessing those and calculating their difference is not hurting the performance that much (obviously it has some minor impact since it introduces a couple of access and arithmetic operations plus backing up and restoring the stack due to the jump inside the QList::size()
function).
So why dump the check for index validity? Is the performance impact actually that big?
By doing so the QList::at()
function offers a hidden and (omho) not very useful feature which makes the difference in using it and QList::[]
even more confusing. Also the C++ STL
equivalent for std::vector
and std::array
allows assignment of new values using this function.
There are "3" ways of accessing an item from its index in a QList
:
const T& QList::at(int) const
T& QList::operator[](int)
const T& QList::operator[](int) const
If you look at the doc, you will see that the last one is simply:
Same as at(). This function runs in constant time.
So we are left with the first one (at
) and the non-const operator[]
. Here is the problem, the documentation of the second one tells you that1:
If this function is called on a list that is currently being shared, it will trigger a copy of all elements. Otherwise, this function runs in constant time. If you do not want to modify the list you should use
QList::at()
.
Note: This is also true for QVector
:
Note that using non-const operators can cause
QVector
to do a deep copy.
1 For QList
, this is actually only true for Qt 5, not for Qt 4 (at least in the documentation), but for QVector
it was already true for Qt 4, so the .at
method was probably made consistent between all Qt containers.
This means that you cannot use operator[]
directly on a non-const shared QList
or on a non-const QVector
, you would need to do something like:
QList<T> &mySharedQList = ...;
const QList<T> &myConstRef = mySharedQList;
myConstRef[0]; // Only ways not to copy the original QList
My guess is that the purpose of the .at
is to provide you with a method that guarantees a constant access time, whatever the QList
or QVector
is (which operator[]
does not guarantee). You do not need such method for standard library containers because non-const operator[]
are already guaranteed to run in constant time (they are never going to make a copy).
Regarding the check on the index, this is probably to keep the behavior of the operator[]
since, unlike std::vector
, you probably want to use .at
everywhere you only need to read data.