From 5b7c3e31b538376f2b4733bd868b5875b504cdb3 Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Fri, 17 Jan 2020 14:33:53 +0100 Subject: [PATCH] New QHash implementation A brand new QHash implementation using a faster and more memory efficient data structure than the old QHash. A new implementation for QHash. Instead of a node based approach as the old QHash, this implementation now uses a two stage lookup table. The total amount of buckets in the table are divided into spans of 128 entries. Inside each span, we use an array of chars to index into a storage area for the span. The storage area for each span is a simple array, that gets (re-)allocated with size increments of 16 items. This gives an average memory overhead of 8*sizeof(struct{ Key; Value; }) + 128*sizeof(char) + 16 for each span. To give good performance and avoid too many collisions, the array keeps its load factor between .25 and .5 (and grows and rehashes if the load factor goes above .5). This design allows us to keep the memory overhead of the Hash very small, while at the same time giving very good performance. The calculated overhead for a QHash comes to 1.7-3.3 bytes per entry and to 2.2-4.3 bytes for a QHash. The new implementation also completely splits the QHash and QMultiHash classes. One behavioral change to note is that the new QHash implementation will not provide stable references to nodes in the hash when the table needs to grow. Benchmarking using https://github.com/Tessil/hash-table-shootout shows very nice performance compared to many different hash table implementation. Numbers shown below are for a hash with 1 million entries. These numbers scale nicely (mostly in a linear fashion with some variation due to varying load factors) to smaller and larger tables. All numbers are in seconds, measured with gcc on Linux: Hash table random random random random reads full insertion insertion full full after iteration (reserved) deletes reads deletes ------------------------------------------------------------------------------ std::unordered_map 0,3842 0,1969 0,4511 0,1300 0,1169 0,0708 google::dense_hash_map 0,1091 0,0846 0,0550 0,0452 0,0754 0,0160 google::sparse_hash_map 0,2888 0,1582 0,0948 0,1020 0,1348 0,0112 tsl::sparse_map 0,1487 0,1013 0,0735 0,0448 0,0505 0,0042 old QHash 0,2886 0,1798 0,5065 0,0840 0,0717 0,1387 new QHash 0,0940 0,0714 0,1494 0,0579 0,0449 0,0146 Numbers for hash, with the string having 15 characters: Hash table random random random random reads insertion insertion full full after (reserved) deletes reads deletes -------------------------------------------------------------------- std::unordered_map 0,4993 0,2563 0,5515 0,2950 0,2153 google::dense_hash_map 0,2691 0,1870 0,1547 0,1125 0,1622 google::sparse_hash_map 0,6979 0,3304 0,1884 0,1822 0,2122 tsl::sparse_map 0,4066 0,2586 0,1929 0,1146 0,1095 old QHash 0,3236 0,2064 0,5986 0,2115 0,1666 new QHash 0,2119 0,1652 0,2390 0,1378 0,0965 Memory usage numbers (in MB for a table with 1M entries) also look very nice: Hash table Key int64 std::string (15 chars) Value int64 int64 --------------------------------------------------------- std::unordered_map 44.63 75.35 google::dense_hash_map 32.32 80,60 google::sparse_hash_map 18.08 44.21 tsl::sparse_map 20.44 45,93 old QHash 53.95 69,16 new QHash 23.23 51,32 Fixes: QTBUG-80311 Change-Id: I5679734144bc9bca2102acbe725fcc2fa89f0dff Reviewed-by: Thiago Macieira --- src/corelib/tools/qhash.cpp | 322 -- src/corelib/tools/qhash.h | 2678 ++++++++++------- src/corelib/tools/qset.h | 20 +- src/corelib/tools/qsharedpointer.cpp | 2 +- .../tools/collections/tst_collections.cpp | 26 +- tests/auto/corelib/tools/qhash/tst_qhash.cpp | 45 +- tests/auto/corelib/tools/qset/tst_qset.cpp | 17 +- 7 files changed, 1678 insertions(+), 1432 deletions(-) diff --git a/src/corelib/tools/qhash.cpp b/src/corelib/tools/qhash.cpp index ca5b7ffd55..f048aa01de 100644 --- a/src/corelib/tools/qhash.cpp +++ b/src/corelib/tools/qhash.cpp @@ -405,328 +405,6 @@ uint qt_hash(QStringView key, uint chained) noexcept return h; } -/* - The prime_deltas array contains the difference between a power - of two and the next prime number: - - prime_deltas[i] = nextprime(2^i) - 2^i - - Basically, it's sequence A092131 from OEIS, assuming: - - nextprime(1) = 1 - - nextprime(2) = 2 - and - - left-extending it for the offset 0 (A092131 starts at i=1) - - stopping the sequence at i = 28 (the table is big enough...) -*/ - -static const uchar prime_deltas[] = { - 0, 0, 1, 3, 1, 5, 3, 3, 1, 9, 7, 5, 3, 17, 27, 3, - 1, 29, 3, 21, 7, 17, 15, 9, 43, 35, 15, 0, 0, 0, 0, 0 -}; - -/* - The primeForNumBits() function returns the prime associated to a - power of two. For example, primeForNumBits(8) returns 257. -*/ - -static inline int primeForNumBits(int numBits) -{ - return (1 << numBits) + prime_deltas[numBits]; -} - -/* - Returns the smallest integer n such that - primeForNumBits(n) >= hint. -*/ -static int countBits(int hint) -{ - int numBits = 0; - int bits = hint; - - while (bits > 1) { - bits >>= 1; - numBits++; - } - - if (numBits >= (int)sizeof(prime_deltas)) { - numBits = sizeof(prime_deltas) - 1; - } else if (primeForNumBits(numBits) < hint) { - ++numBits; - } - return numBits; -} - -/* - A QHash has initially around pow(2, MinNumBits) buckets. For - example, if MinNumBits is 4, it has 17 buckets. -*/ -const int MinNumBits = 4; - -const QHashData QHashData::shared_null = { - nullptr, nullptr, Q_REFCOUNT_INITIALIZE_STATIC, 0, 0, MinNumBits, 0, 0, 0, true, false, 0 -}; - -void *QHashData::allocateNode(int nodeAlign) -{ - void *ptr = strictAlignment ? qMallocAligned(nodeSize, nodeAlign) : malloc(nodeSize); - Q_CHECK_PTR(ptr); - return ptr; -} - -void QHashData::freeNode(void *node) -{ - if (strictAlignment) - qFreeAligned(node); - else - free(node); -} - -QHashData *QHashData::detach_helper(void (*node_duplicate)(Node *, void *), - void (*node_delete)(Node *), - int nodeSize, - int nodeAlign) -{ - union { - QHashData *d; - Node *e; - }; - if (this == &shared_null) - qt_initialize_qhash_seed(); // may throw - d = new QHashData; - d->fakeNext = nullptr; - d->buckets = nullptr; - d->ref.initializeOwned(); - d->size = size; - d->nodeSize = nodeSize; - d->userNumBits = userNumBits; - d->numBits = numBits; - d->numBuckets = numBuckets; - d->seed = (this == &shared_null) ? uint(qt_qhash_seed.loadRelaxed()) : seed; - d->sharable = true; - d->strictAlignment = nodeAlign > 8; - d->reserved = 0; - - if (numBuckets) { - QT_TRY { - d->buckets = new Node *[numBuckets]; - } QT_CATCH(...) { - // restore a consistent state for d - d->numBuckets = 0; - // roll back - d->free_helper(node_delete); - QT_RETHROW; - } - - Node *this_e = reinterpret_cast(this); - for (int i = 0; i < numBuckets; ++i) { - Node **nextNode = &d->buckets[i]; - Node *oldNode = buckets[i]; - while (oldNode != this_e) { - QT_TRY { - Node *dup = static_cast(allocateNode(nodeAlign)); - - QT_TRY { - node_duplicate(oldNode, dup); - } QT_CATCH(...) { - freeNode( dup ); - QT_RETHROW; - } - - *nextNode = dup; - nextNode = &dup->next; - oldNode = oldNode->next; - } QT_CATCH(...) { - // restore a consistent state for d - *nextNode = e; - d->numBuckets = i+1; - // roll back - d->free_helper(node_delete); - QT_RETHROW; - } - } - *nextNode = e; - } - } - return d; -} - -void QHashData::free_helper(void (*node_delete)(Node *)) -{ - if (node_delete) { - Node *this_e = reinterpret_cast(this); - Node **bucket = reinterpret_cast(this->buckets); - - int n = numBuckets; - while (n--) { - Node *cur = *bucket++; - while (cur != this_e) { - Node *next = cur->next; - node_delete(cur); - freeNode(cur); - cur = next; - } - } - } - delete [] buckets; - delete this; -} - -QHashData::Node *QHashData::nextNode(Node *node) -{ - union { - Node *next; - Node *e; - QHashData *d; - }; - next = node->next; - Q_ASSERT_X(next, "QHash", "Iterating beyond end()"); - if (next->next) - return next; - - int start = (node->h % d->numBuckets) + 1; - Node **bucket = d->buckets + start; - int n = d->numBuckets - start; - while (n--) { - if (*bucket != e) - return *bucket; - ++bucket; - } - return e; -} - -QHashData::Node *QHashData::previousNode(Node *node) -{ - union { - Node *e; - QHashData *d; - }; - - e = node; - while (e->next) - e = e->next; - - int start; - if (node == e) - start = d->numBuckets - 1; - else - start = node->h % d->numBuckets; - - Node *sentinel = node; - Node **bucket = d->buckets + start; - while (start >= 0) { - if (*bucket != sentinel) { - Node *prev = *bucket; - while (prev->next != sentinel) - prev = prev->next; - return prev; - } - - sentinel = e; - --bucket; - --start; - } - Q_ASSERT_X(start >= 0, "QHash", "Iterating backward beyond begin()"); - return e; -} - -/* - If hint is negative, -hint gives the approximate number of - buckets that should be used for the hash table. If hint is - nonnegative, (1 << hint) gives the approximate number - of buckets that should be used. -*/ -void QHashData::rehash(int hint) -{ - if (hint < 0) { - hint = countBits(-hint); - if (hint < MinNumBits) - hint = MinNumBits; - userNumBits = hint; - while (primeForNumBits(hint) < (size >> 1)) - ++hint; - } else if (hint < MinNumBits) { - hint = MinNumBits; - } - - if (numBits != hint) { - Node *e = reinterpret_cast(this); - Node **oldBuckets = buckets; - int oldNumBuckets = numBuckets; - - int nb = primeForNumBits(hint); - buckets = new Node *[nb]; - numBits = hint; - numBuckets = nb; - for (int i = 0; i < numBuckets; ++i) - buckets[i] = e; - - for (int i = 0; i < oldNumBuckets; ++i) { - Node *firstNode = oldBuckets[i]; - while (firstNode != e) { - uint h = firstNode->h; - Node *lastNode = firstNode; - while (lastNode->next != e && lastNode->next->h == h) - lastNode = lastNode->next; - - Node *afterLastNode = lastNode->next; - Node **beforeFirstNode = &buckets[h % numBuckets]; - while (*beforeFirstNode != e) - beforeFirstNode = &(*beforeFirstNode)->next; - lastNode->next = *beforeFirstNode; - *beforeFirstNode = firstNode; - firstNode = afterLastNode; - } - } - delete [] oldBuckets; - } -} - -#ifdef QT_QHASH_DEBUG - -void QHashData::dump() -{ - qDebug("Hash data (ref = %d, size = %d, nodeSize = %d, userNumBits = %d, numBits = %d, numBuckets = %d)", - int(ref), size, nodeSize, userNumBits, numBits, - numBuckets); - qDebug(" %p (fakeNode = %p)", this, fakeNext); - for (int i = 0; i < numBuckets; ++i) { - Node *n = buckets[i]; - if (n != reinterpret_cast(this)) { - QString line = QString::asprintf("%d:", i); - while (n != reinterpret_cast(this)) { - line += QString::asprintf(" -> [%p]", n); - if (!n) { - line += " (CORRUPT)"; - break; - } - n = n->next; - } - qDebug("%ls", qUtf16Printable(line)); - } - } -} - -void QHashData::checkSanity() -{ - if (Q_UNLIKELY(fakeNext)) - qFatal("Fake next isn't 0"); - - for (int i = 0; i < numBuckets; ++i) { - Node *n = buckets[i]; - Node *p = n; - if (Q_UNLIKELY(!n)) - qFatal("%d: Bucket entry is 0", i); - if (n != reinterpret_cast(this)) { - while (n != reinterpret_cast(this)) { - if (Q_UNLIKELY(!n->next)) - qFatal("%d: Next of %p is 0, should be %p", i, n, this); - n = n->next; - } - } - } -} -#endif - /*! \fn template uint qHash(const QPair &key, uint seed = 0) \since 5.0 diff --git a/src/corelib/tools/qhash.h b/src/corelib/tools/qhash.h index d03f301306..cdf5577fcb 100644 --- a/src/corelib/tools/qhash.h +++ b/src/corelib/tools/qhash.h @@ -1,7 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. @@ -41,218 +40,769 @@ #ifndef QHASH_H #define QHASH_H -#include #include -#include +#include #include #include #include +#include -#include #include -#if defined(Q_CC_MSVC) -#pragma warning( push ) -#pragma warning( disable : 4311 ) // disable pointer truncation warning -#pragma warning( disable : 4127 ) // conditional expression is constant -#endif - QT_BEGIN_NAMESPACE -struct Q_CORE_EXPORT QHashData -{ - struct Node { - Node *next; - uint h; - }; - - Node *fakeNext; - Node **buckets; - QtPrivate::RefCount ref; - int size; - int nodeSize; - short userNumBits; - short numBits; - int numBuckets; - uint seed; - uint sharable : 1; - uint strictAlignment : 1; - uint reserved : 30; - - void *allocateNode(int nodeAlign); - void freeNode(void *node); - QHashData *detach_helper(void (*node_duplicate)(Node *, void *), void (*node_delete)(Node *), - int nodeSize, int nodeAlign); - bool willGrow(); - void hasShrunk(); - void rehash(int hint); - void free_helper(void (*node_delete)(Node *)); - Node *firstNode(); -#ifdef QT_QHASH_DEBUG - void dump(); - void checkSanity(); -#endif - static Node *nextNode(Node *node); - static Node *previousNode(Node *node); - - static const QHashData shared_null; -}; - -inline bool QHashData::willGrow() -{ - if (size >= numBuckets) { - rehash(numBits + 1); - return true; - } else { - return false; - } -} - -inline void QHashData::hasShrunk() -{ - if (size <= (numBuckets >> 3) && numBits > userNumBits) { - QT_TRY { - rehash(qMax(int(numBits) - 2, int(userNumBits))); - } QT_CATCH(const std::bad_alloc &) { - // ignore bad allocs - shrinking shouldn't throw. rehash is exception safe. - } - } -} - -inline QHashData::Node *QHashData::firstNode() -{ - Node *e = reinterpret_cast(this); - Node **bucket = buckets; - int n = numBuckets; - while (n--) { - if (*bucket != e) - return *bucket; - ++bucket; - } - return e; -} - struct QHashDummyValue { + bool operator==(const QHashDummyValue &) const noexcept { return true; } }; -constexpr bool operator==(const QHashDummyValue &, const QHashDummyValue &) noexcept +namespace QHashPrivate { + +// QHash uses a power of two growth policy. +namespace GrowthPolicy { - return true; +inline constexpr size_t maxNumBuckets() noexcept +{ + return size_t(1) << (8*sizeof(size_t) - 1); +} +inline constexpr size_t bucketsForCapacity(size_t requestedCapacity) noexcept +{ + if (requestedCapacity <= 8) + return 16; + if (requestedCapacity >= maxNumBuckets()) + return maxNumBuckets(); + return qNextPowerOfTwo(QIntegerForSize::Unsigned(2*requestedCapacity - 1)); +} +inline constexpr size_t bucketForHash(size_t nBuckets, size_t hash) noexcept +{ + return hash & (nBuckets - 1); +} } -Q_DECLARE_TYPEINFO(QHashDummyValue, Q_MOVABLE_TYPE | Q_DUMMY_TYPE); - -template -struct QHashNode +template +struct Node { - QHashNode *next; - const uint h; - const Key key; + using KeyType = Key; + using ValueType = T; + + Key key; T value; - - inline QHashNode(const Key &key0, const T &value0, uint hash, QHashNode *n) - : next(n), h(hash), key(key0), value(value0) {} - inline bool same_key(uint h0, const Key &key0) const { return h0 == h && key0 == key; } - -private: - Q_DISABLE_COPY(QHashNode) + static Node create(Key &&k, T &&t) noexcept(std::is_nothrow_move_assignable_v && std::is_nothrow_move_assignable_v) + { + return Node{ std::move(k), std::move(t) }; + } + static Node create(const Key &k, const T &t) noexcept(std::is_nothrow_copy_constructible_v && std::is_nothrow_copy_constructible_v) + { + return Node{ k, t }; + } + void replace(const T &t) noexcept(std::is_nothrow_assignable_v) + { + value = t; + } + void replace(T &&t) noexcept(std::is_nothrow_move_assignable_v) + { + value = std::move(t); + } + T &&takeValue() noexcept(std::is_nothrow_move_assignable_v) + { + return std::move(value); + } }; -// Specialize for QHashDummyValue in order to save some memory -template -struct QHashNode +template +struct MultiNodeChain { - union { - QHashNode *next; - QHashDummyValue value; - }; - const uint h; - const Key key; - - inline QHashNode(const Key &key0, const QHashDummyValue &, uint hash, QHashNode *n) - : next(n), h(hash), key(key0) {} - inline bool same_key(uint h0, const Key &key0) const { return h0 == h && key0 == key; } - -private: - Q_DISABLE_COPY(QHashNode) + T value; + MultiNodeChain *next = nullptr; + ~MultiNodeChain() + { + } + qsizetype free() noexcept(std::is_nothrow_destructible_v) + { + qsizetype nEntries = 0; + MultiNodeChain *e = this; + while (e) { + MultiNodeChain *n = e->next; + ++nEntries; + delete e; + e = n; + } + return nEntries; + } + bool contains(const T &val) const noexcept + { + const MultiNodeChain *e = this; + while (e) { + if (e->value == val) + return true; + e = e->next; + } + return false; + } }; +template +struct MultiNode +{ + using KeyType = Key; + using ValueType = T; + using Chain = MultiNodeChain; -#if 0 -// ### -// The introduction of the QHash random seed breaks this optimization, as it -// relies on qHash(int i) = i. If the hash value is salted, then the hash -// table becomes corrupted. -// -// A bit more research about whether it makes sense or not to salt integer -// keys (and in general keys whose hash value is easy to invert) -// is needed, or about how keep this optimization and the seed (f.i. by -// specializing QHash for integer values, and re-apply the seed during lookup). -// -// Be aware that such changes can easily be binary incompatible, and therefore -// cannot be made during the Qt 5 lifetime. -#define Q_HASH_DECLARE_INT_NODES(key_type) \ - template \ - struct QHashDummyNode { \ - QHashDummyNode *next; \ - union { uint h; key_type key; }; \ -\ - inline QHashDummyNode(key_type /* key0 */) {} \ - }; \ -\ - template \ - struct QHashNode { \ - QHashNode *next; \ - union { uint h; key_type key; }; \ - T value; \ -\ - inline QHashNode(key_type /* key0 */) {} \ - inline QHashNode(key_type /* key0 */, const T &value0) : value(value0) {} \ - inline bool same_key(uint h0, key_type) const { return h0 == h; } \ + Key key; + Chain *value; + + static MultiNode create(Key &&k, T &&t) + { + Chain *c = new Chain{ std::move(t), nullptr }; + return MultiNode(std::move(k), c); + } + static MultiNode create(const Key &k, const T &t) + { + Chain *c = new Chain{ t, nullptr }; + return MultiNode(k, c); } -#if defined(Q_BYTE_ORDER) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN -Q_HASH_DECLARE_INT_NODES(short); -Q_HASH_DECLARE_INT_NODES(ushort); -#endif -Q_HASH_DECLARE_INT_NODES(int); -Q_HASH_DECLARE_INT_NODES(uint); -#undef Q_HASH_DECLARE_INT_NODES -#endif // #if 0 + MultiNode(const Key &k, Chain *c) + : key(k), + value(c) + {} + MultiNode(Key &&k, Chain *c) noexcept(std::is_nothrow_move_assignable_v) + : key(std::move(k)), + value(c) + {} + + MultiNode(MultiNode &&other) + : key(other.key), + value(other.value) + { + other.value = nullptr; + } + + MultiNode(const MultiNode &other) + : key(other.key) + { + Chain *c = other.value; + Chain **e = &value; + while (c) { + Chain *chain = new Chain{ c->value, nullptr }; + *e = chain; + e = &chain->next; + c = c->next; + } + } + ~MultiNode() + { + if (value) + value->free(); + } + static qsizetype freeChain(MultiNode *n) noexcept(std::is_nothrow_destructible_v) + { + qsizetype size = n->value->free(); + n->value = nullptr; + return size; + } + void replace(const T &t) noexcept(std::is_nothrow_assignable_v) + { + value->value = t; + } + void replace(T &&t) noexcept(std::is_nothrow_move_assignable_v) + { + value->value = std::move(t); + } + void insertMulti(const T &t) + { + Chain *e = new Chain{ t, nullptr }; + e->next = value; + value = e; + } + + // compiler generated move operators are fine +}; + +template +constexpr bool isRelocatable() +{ + return QTypeInfo::isRelocatable && QTypeInfo::isRelocatable; +} + +// Regular hash tables consist of a list of buckets that can store Nodes. But simply allocating one large array of buckets +// would waste a lot of memory. To avoid this, we split the vector of buckets up into a vector of Spans. Each Span represents +// NEntries buckets. To quickly find the correct Span that holds a bucket, NEntries must be a power of two. +// +// Inside each Span, there is an offset array that represents the actual buckets. offsets contains either an index into the +// actual storage space for the Nodes (the 'entries' member) or 0xff (UnusedEntry) to flag that the bucket is empty. +// As we have only 128 entries per Span, the offset array can be represented using an unsigned char. This trick makes the hash +// table have a very small memory overhead compared to many other implementations. +template +struct Span { + enum { + NEntries = 128, + LocalBucketMask = (NEntries - 1), + UnusedEntry = 0xff + }; + static_assert ((NEntries & LocalBucketMask) == 0, "EntriesPerSpan must be a power of two."); + + // Entry is a slot available for storing a Node. The Span holds a pointer to + // an array of Entries. Upon construction of the array, those entries are + // unused, and nextFree() is being used to set up a singly linked list + // of free entries. + // When a node gets inserted, the first free entry is being picked, removed + // from the singly linked list and the Node gets constructed in place. + struct Entry { + typename std::aligned_storage::type storage; + + unsigned char &nextFree() { return *reinterpret_cast(&storage); } + Node &node() { return *reinterpret_cast(&storage); } + }; + + unsigned char offsets[NEntries]; + Entry *entries = nullptr; + unsigned char allocated = 0; + unsigned char nextFree = 0; + Span() noexcept + { + memset(offsets, UnusedEntry, sizeof(offsets)); + } + ~Span() + { + freeData(); + } + void freeData() noexcept(std::is_nothrow_destructible::value) + { + if (entries) { + if constexpr (!std::is_trivially_destructible::value) { + for (auto o : offsets) { + if (o != UnusedEntry) + entries[o].node().~Node(); + } + } + delete [] entries; + entries = nullptr; + } + } + void insert(size_t i, Node &&n) + { + Q_ASSERT(i <= NEntries); + Q_ASSERT(offsets[i] == UnusedEntry); + if (nextFree == allocated) + addStorage(); + unsigned char entry = nextFree; + Q_ASSERT(entry < allocated); + nextFree = entries[entry].nextFree(); + offsets[i] = entry; + new (&entries[entry].node()) Node(std::move(n)); + } + void erase(size_t bucket) noexcept(std::is_nothrow_destructible::value) + { + Q_ASSERT(bucket <= NEntries); + Q_ASSERT(offsets[bucket] != UnusedEntry); + + unsigned char entry = offsets[bucket]; + offsets[bucket] = UnusedEntry; + + entries[entry].node().~Node(); + entries[entry].nextFree() = nextFree; + nextFree = entry; + } + size_t offset(size_t i) const noexcept + { + return offsets[i]; + } + bool hasNode(size_t i) const noexcept + { + return (offsets[i] != UnusedEntry); + } + Node &at(size_t i) noexcept + { + Q_ASSERT(i <= NEntries); + Q_ASSERT(offsets[i] != UnusedEntry); + + return entries[offsets[i]].node(); + } + const Node &at(size_t i) const noexcept + { + Q_ASSERT(i <= NEntries); + Q_ASSERT(offsets[i] != UnusedEntry); + + return entries[offsets[i]].node(); + } + Node &atOffset(size_t o) noexcept + { + Q_ASSERT(o < allocated); + + return entries[o].node(); + } + const Node &atOffset(size_t o) const noexcept + { + Q_ASSERT(o < allocated); + + return entries[o].node(); + } + void moveLocal(size_t from, size_t to) noexcept + { + Q_ASSERT(offsets[from] != UnusedEntry); + Q_ASSERT(offsets[to] == UnusedEntry); + offsets[to] = offsets[from]; + offsets[from] = UnusedEntry; + } + void moveFromSpan(Span &fromSpan, size_t fromIndex, size_t to) noexcept(std::is_nothrow_move_constructible_v) + { + Q_ASSERT(to <= NEntries); + Q_ASSERT(offsets[to] == UnusedEntry); + Q_ASSERT(fromIndex <= NEntries); + Q_ASSERT(fromSpan.offsets[fromIndex] != UnusedEntry); + if (nextFree == allocated) + addStorage(); + Q_ASSERT(nextFree < allocated); + offsets[to] = nextFree; + Entry &toEntry = entries[nextFree]; + nextFree = toEntry.nextFree(); + + size_t fromOffset = fromSpan.offsets[fromIndex]; + fromSpan.offsets[fromIndex] = UnusedEntry; + Entry &fromEntry = fromSpan.entries[fromOffset]; + + if constexpr (isRelocatable()) { + memcpy(&toEntry, &fromEntry, sizeof(Entry)); + } else { + new (&toEntry.node()) Node(std::move(fromEntry.node())); + fromEntry.node().~Node(); + } + fromEntry.nextFree() = fromSpan.nextFree; + fromSpan.nextFree = static_cast(fromOffset); + } + + void addStorage() + { + Q_ASSERT(allocated < NEntries); + Q_ASSERT(nextFree == allocated); + // the hash table should always be between 25 and 50% full + // this implies that we on average have between 32 and 64 entries + // in here. The likelihood of having below 16 entries is very small, + // so start with that and increment by 16 each time we need to add + // some more space + const size_t increment = NEntries/8; + size_t alloc = allocated + increment; + Entry *newEntries = new Entry[alloc]; + // we only add storage if the previous storage was fully filled, so + // simply copy the old data over + if constexpr (isRelocatable()) { + memcpy(newEntries, entries, allocated*sizeof(Entry)); + } else { + for (size_t i = 0; i < allocated; ++i) { + new (&newEntries[i].node()) Node(std::move(entries[i].node())); + entries[i].node().~Node(); + } + } + for (size_t i = allocated; i < allocated + increment; ++i) { + newEntries[i].nextFree() = uchar(i + 1); + } + delete [] entries; + entries = newEntries; + allocated = uchar(alloc); + } +}; + +template +struct iterator; + +template +struct Data +{ + using Key = typename Node::KeyType; + using T = typename Node::ValueType; + using Span = QHashPrivate::Span; + using iterator = QHashPrivate::iterator; + + QtPrivate::RefCount ref = {{1}}; + size_t size = 0; + size_t numBuckets = 0; + size_t seed = 0; + + + Span *spans = nullptr; + + Data(size_t reserve = 0) + { + numBuckets = GrowthPolicy::bucketsForCapacity(reserve); + size_t nSpans = (numBuckets + Span::LocalBucketMask) / Span::NEntries; + spans = new Span[nSpans]; + seed = qGlobalQHashSeed(); + } + Data(const Data &other) + : size(other.size), + numBuckets(other.numBuckets), + seed(other.seed) + { + size_t nSpans = (other.numBuckets + Span::LocalBucketMask) / Span::NEntries; + spans = new Span[nSpans]; + + for (size_t s = 0; s < nSpans; ++s) { + const Span &span = other.spans[s]; + for (size_t index = 0; index < Span::NEntries; ++index) { + if (!span.hasNode(index)) + continue; + const Node &n = span.at(index); + iterator it{ this, s*Span::NEntries + index }; + Q_ASSERT(it.isUnused()); + + spans[it.span()].insert(it.index(), std::move(Node(n))); + } + } + } + Data(const Data &other, size_t reserved) + : size(other.size), + seed(other.seed) + { + numBuckets = GrowthPolicy::bucketsForCapacity(qMax(size, reserved)); + size_t nSpans = (numBuckets + Span::LocalBucketMask) / Span::NEntries; + spans = new Span[nSpans]; + + for (size_t s = 0; s < nSpans; ++s) { + const Span &span = other.spans[s]; + for (size_t index = 0; index < Span::NEntries; ++index) { + if (!span.hasNode(index)) + continue; + const Node &n = span.at(index); + iterator it = find(n.key); + Q_ASSERT(it.isUnused()); + spans[it.span()].insert(it.index(), std::move(Node(n))); + } + } + } + + static Data *detached(Data *d) + { + if (!d) + return new Data; + Data *dd = new Data(*d); + if (!d->ref.deref()) + delete d; + return dd; + } + static Data *detached(Data *d, size_t size) + { + if (!d) + return new Data(size); + Data *dd = new Data(*d, size); + if (!d->ref.deref()) + delete d; + return dd; + } + + + iterator detachedIterator(iterator other) const noexcept + { + return iterator{this, other.bucket}; + } + + iterator begin() const noexcept + { + iterator it{ this, 0 }; + if (it.isUnused()) + ++it; + return it; + } + + constexpr iterator end() const noexcept + { + return iterator(); + } + + void rehash(size_t sizeHint = 0) + { + if (sizeHint == 0) + sizeHint = size; + size_t newBucketCount = GrowthPolicy::bucketsForCapacity(sizeHint); + + Span *oldSpans = spans; + size_t oldBucketCount = numBuckets; + size_t nSpans = (newBucketCount + Span::LocalBucketMask) / Span::NEntries; + spans = new Span[nSpans]; + numBuckets = newBucketCount; + size_t oldNSpans = (oldBucketCount + Span::LocalBucketMask) / Span::NEntries; + + for (size_t s = 0; s < oldNSpans; ++s) { + Span &span = oldSpans[s]; + for (size_t index = 0; index < Span::NEntries; ++index) { + if (!span.hasNode(index)) + continue; + Node &n = span.at(index); + iterator it = find(n.key); + Q_ASSERT(it.isUnused()); + spans[it.span()].insert(it.index(), std::move(n)); + } + span.freeData(); + } + delete [] oldSpans; + } + + size_t nextBucket(size_t bucket) const noexcept + { + ++bucket; + if (bucket == numBuckets) + bucket = 0; + return bucket; + } + + float loadFactor() const noexcept + { + return float(size)/numBuckets; + } + bool shouldGrow() const noexcept + { + return size >= (numBuckets >> 1); + } + + iterator find(const Key &key) const noexcept + { + Q_ASSERT(numBuckets > 0); + size_t hash = qHash(key, seed); + size_t bucket = GrowthPolicy::bucketForHash(numBuckets, hash); + // loop over the buckets until we find the entry we search for + // or an empty slot, in which case we know the entry doesn't exist + while (true) { + // Split the bucket into the indexex of span array, and the local + // offset inside the span + size_t span = bucket / Span::NEntries; + size_t index = bucket & Span::LocalBucketMask; + Span &s = spans[span]; + size_t offset = s.offset(index); + if (offset == Span::UnusedEntry) { + return iterator{ this, bucket }; + } else { + Node &n = s.atOffset(offset); + if (n.key == key) + return iterator{ this, bucket }; + } + bucket = nextBucket(bucket); + } + } + + Node *findNode(const Key &key) const noexcept + { + if (!size) + return nullptr; + iterator it = find(key); + if (it.isUnused()) + return nullptr; + return it.node(); + } + + Node *findAndInsertNode(const Key &key) noexcept + { + if (shouldGrow()) + rehash(size + 1); + iterator it = find(key); + if (it.isUnused()) { + spans[it.span()].insert(it.index(), Node::create(key, T())); + ++size; + } + return it.node(); + } + + + iterator insert(Node &&n) + { + if (shouldGrow()) + rehash(size + 1); + iterator it = find(n.key); + if (it.isUnused()) { + spans[it.span()].insert(it.index(), std::move(n)); + ++size; + } else { + it.node()->replace(std::move(n.takeValue())); + } + return it; + } + + iterator insert(const Key &key, const T &value) + { + if (shouldGrow()) + rehash(size + 1); + auto it = find(key); + if (it.isUnused()) { + spans[it.span()].insert(it.index(), Node::create(key, value)); + ++size; + } else { + it.node()->replace(value); + } + return it; + } + + iterator insertMulti(const Key &key, const T &value) + { + if (shouldGrow()) + rehash(size + 1); + auto it = find(key); + if (it.isUnused()) { + spans[it.span()].insert(it.index(), std::move(Node::create(key, value))); + ++size; + } else { + it.node()->insertMulti(value); + } + return it; + } + + + iterator erase(iterator it) noexcept(std::is_nothrow_destructible::value) + { + size_t bucket = it.bucket; + size_t span = bucket / Span::NEntries; + size_t index = bucket & Span::LocalBucketMask; + Q_ASSERT(spans[span].hasNode(index)); + spans[span].erase(index); + --size; + + // re-insert the following entries to avoid holes + size_t hole = bucket; + size_t next = bucket; + while (true) { + next = nextBucket(next); + size_t nextSpan = next / Span::NEntries; + size_t nextIndex = next & Span::LocalBucketMask; + if (!spans[nextSpan].hasNode(nextIndex)) + break; + size_t hash = qHash(spans[nextSpan].at(nextIndex).key, seed); + size_t newBucket = GrowthPolicy::bucketForHash(numBuckets, hash); + while (true) { + if (newBucket == next) { + // nothing to do, item is at the right plae + break; + } else if (newBucket == hole) { + // move into hole + size_t holeSpan = hole / Span::NEntries; + size_t holeIndex = hole & Span::LocalBucketMask; + if (nextSpan == holeSpan) { + spans[holeSpan].moveLocal(nextIndex, holeIndex); + } else { + // move between spans, more expensive + spans[holeSpan].moveFromSpan(spans[nextSpan], nextIndex, holeIndex); + } + hole = next; + break; + } + newBucket = nextBucket(newBucket); + } + } + + // return correct position of the next element + if (!spans[span].hasNode(index)) + ++it; + return it; + } + + ~Data() + { + delete [] spans; + } +}; + +template +struct iterator { + using Span = QHashPrivate::Span; + + const Data *d = nullptr; + size_t bucket = 0; + + size_t span() const noexcept { return bucket / Span::NEntries; } + size_t index() const noexcept { return bucket & Span::LocalBucketMask; } + inline bool isUnused() const noexcept { return !d->spans[span()].hasNode(index()); } + + inline Node *node() const noexcept + { + Q_ASSERT(!isUnused()); + return &d->spans[span()].at(index()); + } + bool atEnd() const noexcept { return !d; } + + iterator operator++() noexcept + { + while (true) { + ++bucket; + if (bucket == d->numBuckets) { + d = nullptr; + bucket = 0; + break; + } + if (!isUnused()) + break; + } + return *this; + } + bool operator==(iterator other) const noexcept + { return d == other.d && bucket == other.bucket; } + bool operator!=(iterator other) const noexcept + { return !(*this == other); } +}; + + + +} // namespace QHashPrivate template class QHash { - typedef QHashNode Node; + using Node = QHashPrivate::Node; + using Data = QHashPrivate::Data; + friend class QSet; - union { - QHashData *d; - QHashNode *e; - }; - - static inline Node *concrete(QHashData::Node *node) { - return reinterpret_cast(node); - } - - static inline int alignOfNode() { return qMax(sizeof(void*), alignof(Node)); } + Data *d = nullptr; public: - inline QHash() noexcept : d(const_cast(&QHashData::shared_null)) { } + using key_type = Key; + using mapped_type = T; + using value_type = T; + using size_type = qsizetype; + using difference_type = qsizetype; + using reference = T &; + using const_reference = const T &; + + inline QHash() noexcept = default; inline QHash(std::initializer_list > list) - : d(const_cast(&QHashData::shared_null)) + : d(new Data(list.size())) { - reserve(int(list.size())); for (typename std::initializer_list >::const_iterator it = list.begin(); it != list.end(); ++it) insert(it->first, it->second); } - QHash(const QHash &other) : d(other.d) { d->ref.ref(); if (!d->sharable) detach(); } - ~QHash() { if (!d->ref.deref()) freeData(d); } + QHash(const QHash &other) noexcept + : d(other.d) + { + if (d) + d->ref.ref(); + } + ~QHash() + { + if (d && !d->ref.deref()) + delete d; + } - QHash &operator=(const QHash &other); - QHash(QHash &&other) noexcept : d(other.d) { other.d = const_cast(&QHashData::shared_null); } - QHash &operator=(QHash &&other) noexcept - { QHash moved(std::move(other)); swap(moved); return *this; } + QHash &operator=(const QHash &other) noexcept(std::is_nothrow_destructible::value) + { + if (d != other.d) { + Data *o = other.d; + if (o) + o->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = o; + } + return *this; + } + + QHash(QHash &&other) noexcept + : d(std::exchange(other.d, nullptr)) + { + } + QHash &operator=(QHash &&other) noexcept(std::is_nothrow_destructible::value) + { + if (d != other.d) { + if (d && !d->ref.deref()) + delete d; + d = std::exchange(other.d, nullptr); + } + return *this; + } #ifdef Q_QDOC template QHash(InputIterator f, InputIterator l); @@ -277,170 +827,220 @@ public: #endif void swap(QHash &other) noexcept { qSwap(d, other.d); } - bool operator==(const QHash &other) const; - bool operator!=(const QHash &other) const { return !(*this == other); } + bool operator==(const QHash &other) const noexcept + { + if (d == other.d) + return true; + if (size() != other.size()) + return false; - inline int size() const { return d->size; } + for (const_iterator it = other.begin(); it != other.end(); ++it) { + const_iterator i = find(it.key()); + if (i == end() || !(i.value() == it.value())) + return false; + } + // all values must be the same as size is the same + return true; + } + bool operator!=(const QHash &other) const noexcept { return !(*this == other); } - inline bool isEmpty() const { return d->size == 0; } + inline qsizetype size() const noexcept { return d ? qsizetype(d->size) : 0; } + inline bool isEmpty() const noexcept { return !d || d->size == 0; } - inline int capacity() const { return d->numBuckets; } - void reserve(int size); - inline void squeeze() { reserve(1); } + inline qsizetype capacity() const noexcept { return d ? qsizetype(d->numBuckets >> 1) : 0; } + void reserve(qsizetype size) + { + if (isDetached()) + d->rehash(size); + else + d = Data::detached(d, size_t(size)); + } + inline void squeeze() { reserve(0); } - inline void detach() { if (d->ref.isShared()) detach_helper(); } - inline bool isDetached() const { return !d->ref.isShared(); } - bool isSharedWith(const QHash &other) const { return d == other.d; } + inline void detach() { if (!d || d->ref.isShared()) d = Data::detached(d); } + inline bool isDetached() const noexcept { return d && !d->ref.isShared(); } + bool isSharedWith(const QHash &other) const noexcept { return d == other.d; } - void clear(); + void clear() noexcept(std::is_nothrow_destructible::value) + { + if (d && !d->ref.deref()) + delete d; + d = nullptr; + } - int remove(const Key &key); - T take(const Key &key); + bool remove(const Key &key) + { + if (isEmpty()) // prevents detaching shared null + return false; + detach(); - bool contains(const Key &key) const; - Key key(const T &value) const; - Key key(const T &value, const Key &defaultKey) const; - T value(const Key &key) const; - T value(const Key &key, const T &defaultValue) const; - T &operator[](const Key &key); - const T operator[](const Key &key) const; + auto it = d->find(key); + if (it.isUnused()) + return false; + d->erase(it); + return true; + } + T take(const Key &key) + { + if (isEmpty()) // prevents detaching shared null + return T(); + detach(); - QList keys() const; - QList keys(const T &value) const; - QList values() const; -#if QT_DEPRECATED_SINCE(5, 15) - QT_DEPRECATED_VERSION_X_5_15("Use QMultiHash for hashes storing multiple values with the same key.") QList uniqueKeys() const; - QT_DEPRECATED_VERSION_X_5_15("Use QMultiHash for hashes storing multiple values with the same key.") QList values(const Key &key) const; -#endif - int count(const Key &key) const; + auto it = d->find(key); + if (it.isUnused()) + return T(); + T value = it.node()->takeValue(); + d->erase(it); + return value; + } + + bool contains(const Key &key) const noexcept + { + if (!d) + return false; + return d->findNode(key) != nullptr; + } + qsizetype count(const Key &key) const noexcept + { + return contains(key) ? 1 : 0; + } + + Key key(const T &value, const Key &defaultKey = Key()) const noexcept + { + if (d) { + const_iterator i = begin(); + while (i != end()) { + if (i.value() == value) + return i.key(); + ++i; + } + } + + return defaultKey; + } + T value(const Key &key, const T &defaultValue = T()) const noexcept + { + if (d) { + Node *n = d->findNode(key); + if (n) + return n->value; + } + return defaultValue; + } + T &operator[](const Key &key) + { + detach(); + Node *n = d->findAndInsertNode(key); + Q_ASSERT(n); + return n->value; + } + + const T operator[](const Key &key) const noexcept + { + return value(key); + } + + QVector keys() const + { + return QVector(keyBegin(), keyEnd()); + } + QVector keys(const T &value) const + { + QVector res; + const_iterator i = begin(); + while (i != end()) { + if (i.value() == value) + res.append(i.key()); + ++i; + } + return res; + } + QVector values() const + { + return QVector(begin(), end()); + } class const_iterator; class iterator { + using piter = typename QHashPrivate::iterator; friend class const_iterator; friend class QHash; friend class QSet; - QHashData::Node *i; + piter i; + explicit inline iterator(piter it) noexcept : i(it) { } public: -#if QT_DEPRECATED_WARNINGS_SINCE < QT_VERSION_CHECK(5, 15, 0) - typedef std::bidirectional_iterator_tag iterator_category; -#else typedef std::forward_iterator_tag iterator_category; -#endif typedef qptrdiff difference_type; typedef T value_type; typedef T *pointer; typedef T &reference; - inline iterator() : i(nullptr) { } - explicit inline iterator(void *node) : i(reinterpret_cast(node)) { } + constexpr iterator() noexcept = default; - inline const Key &key() const { return concrete(i)->key; } - inline T &value() const { return concrete(i)->value; } - inline T &operator*() const { return concrete(i)->value; } - inline T *operator->() const { return &concrete(i)->value; } - inline bool operator==(const iterator &o) const { return i == o.i; } - inline bool operator!=(const iterator &o) const { return i != o.i; } + inline const Key &key() const noexcept { return i.node()->key; } + inline T &value() const noexcept { return i.node()->value; } + inline T &operator*() const noexcept { return i.node()->value; } + inline T *operator->() const noexcept { return &i.node()->value; } + inline bool operator==(const iterator &o) const noexcept { return i == o.i; } + inline bool operator!=(const iterator &o) const noexcept { return i != o.i; } - inline iterator &operator++() { - i = QHashData::nextNode(i); + inline iterator &operator++() noexcept + { + ++i; return *this; } - inline iterator operator++(int) { - iterator r = *this; - i = QHashData::nextNode(i); - return r; - } -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 iterator &operator--() - { - i = QHashData::previousNode(i); - return *this; - } - inline QT_DEPRECATED_VERSION_5_15 iterator operator--(int) + inline iterator operator++(int) noexcept { iterator r = *this; - i = QHashData::previousNode(i); + ++i; return r; } - inline QT_DEPRECATED_VERSION_5_15 iterator operator+(int j) const - { iterator r = *this; if (j > 0) while (j--) ++r; else while (j++) --r; return r; } - inline QT_DEPRECATED_VERSION_5_15 iterator operator-(int j) const { return operator+(-j); } - inline QT_DEPRECATED_VERSION_5_15 iterator &operator+=(int j) { return *this = *this + j; } - inline QT_DEPRECATED_VERSION_5_15 iterator &operator-=(int j) { return *this = *this - j; } - friend inline QT_DEPRECATED_VERSION_5_15 iterator operator+(int j, iterator k) { return k + j; } -#endif - inline bool operator==(const const_iterator &o) const { return i == o.i; } - inline bool operator!=(const const_iterator &o) const { return i != o.i; } + inline bool operator==(const const_iterator &o) const noexcept { return i == o.i; } + inline bool operator!=(const const_iterator &o) const noexcept { return i != o.i; } }; friend class iterator; class const_iterator { + using piter = typename QHashPrivate::iterator; friend class iterator; friend class QHash; - friend class QMultiHash; friend class QSet; - QHashData::Node *i; + piter i; + explicit inline const_iterator(piter it) : i(it) { } public: -#if QT_DEPRECATED_WARNINGS_SINCE < QT_VERSION_CHECK(5, 15, 0) - typedef std::bidirectional_iterator_tag iterator_category; -#else typedef std::forward_iterator_tag iterator_category; -#endif typedef qptrdiff difference_type; typedef T value_type; typedef const T *pointer; typedef const T &reference; - Q_DECL_CONSTEXPR inline const_iterator() : i(nullptr) { } - explicit inline const_iterator(void *node) - : i(reinterpret_cast(node)) { } - inline const_iterator(const iterator &o) - { i = o.i; } + constexpr const_iterator() noexcept = default; + inline const_iterator(const iterator &o) noexcept : i(o.i) { } - inline const Key &key() const { return concrete(i)->key; } - inline const T &value() const { return concrete(i)->value; } - inline const T &operator*() const { return concrete(i)->value; } - inline const T *operator->() const { return &concrete(i)->value; } - Q_DECL_CONSTEXPR inline bool operator==(const const_iterator &o) const { return i == o.i; } - Q_DECL_CONSTEXPR inline bool operator!=(const const_iterator &o) const { return i != o.i; } + inline const Key &key() const noexcept { return i.node()->key; } + inline const T &value() const noexcept { return i.node()->value; } + inline const T &operator*() const noexcept { return i.node()->value; } + inline const T *operator->() const noexcept { return &i.node()->value; } + inline bool operator==(const const_iterator &o) const noexcept { return i == o.i; } + inline bool operator!=(const const_iterator &o) const noexcept { return i != o.i; } - inline const_iterator &operator++() { - i = QHashData::nextNode(i); + inline const_iterator &operator++() noexcept + { + ++i; return *this; } - inline const_iterator operator++(int) { - const_iterator r = *this; - i = QHashData::nextNode(i); - return r; - } -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 const_iterator &operator--() - { - i = QHashData::previousNode(i); - return *this; - } - inline QT_DEPRECATED_VERSION_5_15 const_iterator operator--(int) + inline const_iterator operator++(int) noexcept { const_iterator r = *this; - i = QHashData::previousNode(i); + ++i; return r; } - inline QT_DEPRECATED_VERSION_5_15 const_iterator operator+(int j) const - { const_iterator r = *this; if (j > 0) while (j--) ++r; else while (j++) --r; return r; } - inline QT_DEPRECATED_VERSION_5_15 const_iterator operator-(int j) const { return operator+(-j); } - inline QT_DEPRECATED_VERSION_5_15 const_iterator &operator+=(int j) { return *this = *this + j; } - inline QT_DEPRECATED_VERSION_5_15 const_iterator &operator-=(int j) { return *this = *this - j; } - friend inline QT_DEPRECATED_VERSION_5_15 const_iterator operator+(int j, const_iterator k) - { - return k + j; - } -#endif }; friend class const_iterator; @@ -450,561 +1050,155 @@ public: public: typedef typename const_iterator::iterator_category iterator_category; - typedef typename const_iterator::difference_type difference_type; + typedef qptrdiff difference_type; typedef Key value_type; typedef const Key *pointer; typedef const Key &reference; - key_iterator() = default; - explicit key_iterator(const_iterator o) : i(o) { } + key_iterator() noexcept = default; + explicit key_iterator(const_iterator o) noexcept : i(o) { } - const Key &operator*() const { return i.key(); } - const Key *operator->() const { return &i.key(); } - bool operator==(key_iterator o) const { return i == o.i; } - bool operator!=(key_iterator o) const { return i != o.i; } + const Key &operator*() const noexcept { return i.key(); } + const Key *operator->() const noexcept { return &i.key(); } + bool operator==(key_iterator o) const noexcept { return i == o.i; } + bool operator!=(key_iterator o) const noexcept { return i != o.i; } - inline key_iterator &operator++() { ++i; return *this; } - inline key_iterator operator++(int) { return key_iterator(i++);} -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 key_iterator &operator--() - { - --i; - return *this; - } - inline QT_DEPRECATED_VERSION_5_15 key_iterator operator--(int) { return key_iterator(i--); } -#endif - const_iterator base() const { return i; } + inline key_iterator &operator++() noexcept { ++i; return *this; } + inline key_iterator operator++(int) noexcept { return key_iterator(i++);} + const_iterator base() const noexcept { return i; } }; typedef QKeyValueIterator const_key_value_iterator; typedef QKeyValueIterator key_value_iterator; // STL style - inline iterator begin() { detach(); return iterator(d->firstNode()); } - inline const_iterator begin() const { return const_iterator(d->firstNode()); } - inline const_iterator cbegin() const { return const_iterator(d->firstNode()); } - inline const_iterator constBegin() const { return const_iterator(d->firstNode()); } - inline iterator end() { detach(); return iterator(e); } - inline const_iterator end() const { return const_iterator(e); } - inline const_iterator cend() const { return const_iterator(e); } - inline const_iterator constEnd() const { return const_iterator(e); } - inline key_iterator keyBegin() const { return key_iterator(begin()); } - inline key_iterator keyEnd() const { return key_iterator(end()); } + inline iterator begin() { detach(); return iterator(d->begin()); } + inline const_iterator begin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator cbegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator constBegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline iterator end() noexcept { return iterator(); } + inline const_iterator end() const noexcept { return const_iterator(); } + inline const_iterator cend() const noexcept { return const_iterator(); } + inline const_iterator constEnd() const noexcept { return const_iterator(); } + inline key_iterator keyBegin() const noexcept { return key_iterator(begin()); } + inline key_iterator keyEnd() const noexcept { return key_iterator(end()); } inline key_value_iterator keyValueBegin() { return key_value_iterator(begin()); } inline key_value_iterator keyValueEnd() { return key_value_iterator(end()); } - inline const_key_value_iterator keyValueBegin() const { return const_key_value_iterator(begin()); } - inline const_key_value_iterator constKeyValueBegin() const { return const_key_value_iterator(begin()); } - inline const_key_value_iterator keyValueEnd() const { return const_key_value_iterator(end()); } - inline const_key_value_iterator constKeyValueEnd() const { return const_key_value_iterator(end()); } + inline const_key_value_iterator keyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator constKeyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator keyValueEnd() const noexcept { return const_key_value_iterator(end()); } + inline const_key_value_iterator constKeyValueEnd() const noexcept { return const_key_value_iterator(end()); } - QPair equal_range(const Key &key); - QPair equal_range(const Key &key) const noexcept; - iterator erase(iterator it) { return erase(const_iterator(it.i)); } - iterator erase(const_iterator it); + iterator erase(const_iterator it) + { + Q_ASSERT(it != constEnd()); + detach(); + // ensure a valid iterator across the detach: + iterator i = iterator{d->detachedIterator(it.i)}; + + i.i = d->erase(i.i); + return i; + } + + QPair equal_range(const Key &key) + { + auto first = find(key); + auto second = first; + if (second != iterator()) + ++second; + return qMakePair(first, second); + } + + QPair equal_range(const Key &key) const noexcept + { + auto first = find(key); + auto second = first; + if (second != iterator()) + ++second; + return qMakePair(first, second); + } - // more Qt typedef iterator Iterator; typedef const_iterator ConstIterator; - inline int count() const { return d->size; } - iterator find(const Key &key); - const_iterator find(const Key &key) const; - const_iterator constFind(const Key &key) const; - iterator insert(const Key &key, const T &value); - void insert(const QHash &hash); -#if QT_DEPRECATED_SINCE(5, 15) - QT_DEPRECATED_VERSION_X_5_15("Use QMultiHash for hashes storing multiple values with the same key.") iterator insertMulti(const Key &key, const T &value); - QT_DEPRECATED_VERSION_X_5_15("Use QMultiHash for hashes storing multiple values with the same key.") QHash &unite(const QHash &other); -#endif - - // STL compatibility - typedef T mapped_type; - typedef Key key_type; - typedef qptrdiff difference_type; - typedef int size_type; - - inline bool empty() const { return isEmpty(); } - -#ifdef QT_QHASH_DEBUG - inline void dump() const { d->dump(); } - inline void checkSanity() const { d->checkSanity(); } -#endif - -private: - void detach_helper(); - void freeData(QHashData *d); - Node **findNode(const Key &key, uint *hp = nullptr) const; - Node **findNode(const Key &key, uint h) const; - Node *createNode(uint h, const Key &key, const T &value, Node **nextNode); - void deleteNode(Node *node); - static void deleteNode2(QHashData::Node *node); - - static void duplicateNode(QHashData::Node *originalNode, void *newNode); - - bool isValidIterator(const iterator &it) const noexcept - { return isValidNode(it.i); } - bool isValidIterator(const const_iterator &it) const noexcept - { return isValidNode(it.i); } - bool isValidNode(QHashData::Node *node) const noexcept + inline qsizetype count() const noexcept { return d ? qsizetype(d->size) : 0; } + iterator find(const Key &key) { -#if defined(QT_DEBUG) && !defined(Q_HASH_NO_ITERATOR_DEBUG) - while (node->next) - node = node->next; - return (static_cast(node) == d); -#else - Q_UNUSED(node); - return true; -#endif + if (isEmpty()) // prevents detaching shared null + return end(); + detach(); + auto it = d->find(key); + if (it.isUnused()) + it = d->end(); + return iterator(it); } - friend class QSet; - friend class QMultiHash; + const_iterator find(const Key &key) const noexcept + { + if (isEmpty()) + return end(); + auto it = d->find(key); + if (it.isUnused()) + it = d->end(); + return const_iterator(it); + } + const_iterator constFind(const Key &key) const noexcept + { + return find(key); + } + iterator insert(const Key &key, const T &value) + { + detach(); + + auto i = d->insert(Node{key, value}); + return iterator(i); + } + void insert(const QHash &hash) + { + if (d == hash.d || !hash.d) + return; + if (!d) { + *this = hash; + return; + } + + detach(); + + for (auto it = hash.begin(); it != hash.end(); ++it) + insert(it.key(), it.value()); + } + + float load_factor() const noexcept { return d ? d->loadFactor() : 0; } + static float max_load_factor() noexcept { return 0.5; } + size_t bucket_count() const noexcept { return d ? d->numBuckets : 0; } + static size_t max_bucket_count() noexcept { return QHashPrivate::GrowthPolicy::maxNumBuckets(); } + + inline bool empty() const noexcept { return isEmpty(); } }; -template -Q_INLINE_TEMPLATE void QHash::deleteNode(Node *node) -{ - deleteNode2(reinterpret_cast(node)); - d->freeNode(node); -} template -Q_INLINE_TEMPLATE void QHash::deleteNode2(QHashData::Node *node) +class QMultiHash { -#ifdef Q_CC_BOR - concrete(node)->~QHashNode(); -#else - concrete(node)->~Node(); -#endif -} + using Node = QHashPrivate::MultiNode; + using Data = QHashPrivate::Data; + using Chain = QHashPrivate::MultiNodeChain; -template -Q_INLINE_TEMPLATE void QHash::duplicateNode(QHashData::Node *node, void *newNode) -{ - Node *concreteNode = concrete(node); - new (newNode) Node(concreteNode->key, concreteNode->value, concreteNode->h, nullptr); -} + Data *d = nullptr; + qsizetype m_size = 0; -template -Q_INLINE_TEMPLATE typename QHash::Node * -QHash::createNode(uint ah, const Key &akey, const T &avalue, Node **anextNode) -{ - Node *node = new (d->allocateNode(alignOfNode())) Node(akey, avalue, ah, *anextNode); - *anextNode = node; - ++d->size; - return node; -} - -template -Q_OUTOFLINE_TEMPLATE void QHash::freeData(QHashData *x) -{ - x->free_helper(deleteNode2); -} - -template -Q_INLINE_TEMPLATE void QHash::clear() -{ - *this = QHash(); -} - -template -Q_OUTOFLINE_TEMPLATE void QHash::detach_helper() -{ - QHashData *x = d->detach_helper(duplicateNode, deleteNode2, sizeof(Node), alignOfNode()); - if (!d->ref.deref()) - freeData(d); - d = x; -} - -template -Q_INLINE_TEMPLATE QHash &QHash::operator=(const QHash &other) -{ - if (d != other.d) { - QHashData *o = other.d; - o->ref.ref(); - if (!d->ref.deref()) - freeData(d); - d = o; - if (!d->sharable) - detach_helper(); - } - return *this; -} - -template -Q_INLINE_TEMPLATE T QHash::value(const Key &akey) const -{ - Node *node; - if (d->size == 0 || (node = *findNode(akey)) == e) { - return T(); - } else { - return node->value; - } -} - -template -Q_INLINE_TEMPLATE T QHash::value(const Key &akey, const T &adefaultValue) const -{ - Node *node; - if (d->size == 0 || (node = *findNode(akey)) == e) { - return adefaultValue; - } else { - return node->value; - } -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::keys() const -{ - QList res; - res.reserve(size()); - const_iterator i = begin(); - while (i != end()) { - res.append(i.key()); - ++i; - } - return res; -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::keys(const T &avalue) const -{ - QList res; - const_iterator i = begin(); - while (i != end()) { - if (i.value() == avalue) - res.append(i.key()); - ++i; - } - return res; -} - -template -Q_OUTOFLINE_TEMPLATE Key QHash::key(const T &avalue) const -{ - return key(avalue, Key()); -} - -template -Q_OUTOFLINE_TEMPLATE Key QHash::key(const T &avalue, const Key &defaultValue) const -{ - const_iterator i = begin(); - while (i != end()) { - if (i.value() == avalue) - return i.key(); - ++i; - } - - return defaultValue; -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::values() const -{ - QList res; - res.reserve(size()); - const_iterator i = begin(); - while (i != end()) { - res.append(i.value()); - ++i; - } - return res; -} - -template -Q_OUTOFLINE_TEMPLATE int QHash::count(const Key &akey) const -{ - int cnt = 0; - Node *node = *findNode(akey); - if (node != e) { - do { - ++cnt; - } while ((node = node->next) != e && node->key == akey); - } - return cnt; -} - -template -Q_INLINE_TEMPLATE const T QHash::operator[](const Key &akey) const -{ - return value(akey); -} - -template -Q_INLINE_TEMPLATE T &QHash::operator[](const Key &akey) -{ - detach(); - - uint h; - Node **node = findNode(akey, &h); - if (*node == e) { - if (d->willGrow()) - node = findNode(akey, h); - return createNode(h, akey, T(), node)->value; - } - return (*node)->value; -} - -template -Q_INLINE_TEMPLATE typename QHash::iterator QHash::insert(const Key &akey, - const T &avalue) -{ - detach(); - - uint h; - Node **node = findNode(akey, &h); - if (*node == e) { - if (d->willGrow()) - node = findNode(akey, h); - return iterator(createNode(h, akey, avalue, node)); - } - - if (!std::is_same::value) - (*node)->value = avalue; - return iterator(*node); -} - -template -Q_INLINE_TEMPLATE void QHash::insert(const QHash &hash) -{ - if (d == hash.d) - return; - - detach(); - - QHashData::Node *i = hash.d->firstNode(); - QHashData::Node *end = reinterpret_cast(hash.e); - while (i != end) { - Node *n = concrete(i); - Node **node = findNode(n->key, n->h); - if (*node == e) { - if (d->willGrow()) - node = findNode(n->key, n->h); - createNode(n->h, n->key, n->value, node); - } else { - if (!std::is_same::value) - (*node)->value = n->value; - } - i = QHashData::nextNode(i); - } -} - -template -Q_OUTOFLINE_TEMPLATE int QHash::remove(const Key &akey) -{ - if (isEmpty()) // prevents detaching shared null - return 0; - detach(); - - int oldSize = d->size; - Node **node = findNode(akey); - if (*node != e) { - bool deleteNext = true; - do { - Node *next = (*node)->next; - deleteNext = (next != e && next->key == (*node)->key); - deleteNode(*node); - *node = next; - --d->size; - } while (deleteNext); - d->hasShrunk(); - } - return oldSize - d->size; -} - -template -Q_OUTOFLINE_TEMPLATE T QHash::take(const Key &akey) -{ - if (isEmpty()) // prevents detaching shared null - return T(); - detach(); - - Node **node = findNode(akey); - if (*node != e) { - T t = std::move((*node)->value); - Node *next = (*node)->next; - deleteNode(*node); - *node = next; - --d->size; - d->hasShrunk(); - return t; - } - return T(); -} - -template -Q_OUTOFLINE_TEMPLATE typename QHash::iterator QHash::erase(const_iterator it) -{ - Q_ASSERT_X(isValidIterator(it), "QHash::erase", "The specified iterator argument 'it' is invalid"); - - if (it == const_iterator(e)) - return iterator(it.i); - - if (d->ref.isShared()) { - // save 'it' across the detach: - int bucketNum = (it.i->h % d->numBuckets); - const_iterator bucketIterator(*(d->buckets + bucketNum)); - int stepsFromBucketStartToIte = 0; - while (bucketIterator != it) { - ++stepsFromBucketStartToIte; - ++bucketIterator; - } - detach(); - it = const_iterator(*(d->buckets + bucketNum)); - while (stepsFromBucketStartToIte > 0) { - --stepsFromBucketStartToIte; - ++it; - } - } - - iterator ret(it.i); - ++ret; - - Node *node = concrete(it.i); - Node **node_ptr = reinterpret_cast(&d->buckets[node->h % d->numBuckets]); - while (*node_ptr != node) - node_ptr = &(*node_ptr)->next; - *node_ptr = node->next; - deleteNode(node); - --d->size; - return ret; -} - -template -Q_INLINE_TEMPLATE void QHash::reserve(int asize) -{ - detach(); - d->rehash(-qMax(asize, 1)); -} - -template -Q_INLINE_TEMPLATE typename QHash::const_iterator QHash::find(const Key &akey) const -{ - return const_iterator(*findNode(akey)); -} - -template -Q_INLINE_TEMPLATE typename QHash::const_iterator QHash::constFind(const Key &akey) const -{ - return const_iterator(*findNode(akey)); -} - -template -Q_INLINE_TEMPLATE typename QHash::iterator QHash::find(const Key &akey) -{ - detach(); - return iterator(*findNode(akey)); -} - -template -Q_INLINE_TEMPLATE bool QHash::contains(const Key &akey) const -{ - return *findNode(akey) != e; -} - -template -Q_OUTOFLINE_TEMPLATE typename QHash::Node **QHash::findNode(const Key &akey, uint h) const -{ - Node **node; - - if (d->numBuckets) { - node = reinterpret_cast(&d->buckets[h % d->numBuckets]); - Q_ASSERT(*node == e || (*node)->next); - while (*node != e && !(*node)->same_key(h, akey)) - node = &(*node)->next; - } else { - node = const_cast(reinterpret_cast(&e)); - } - return node; -} - -template -Q_OUTOFLINE_TEMPLATE typename QHash::Node **QHash::findNode(const Key &akey, - uint *ahp) const -{ - uint h = 0; - - if (d->numBuckets || ahp) { - h = qHash(akey, d->seed); - if (ahp) - *ahp = h; - } - return findNode(akey, h); -} - -template -Q_OUTOFLINE_TEMPLATE bool QHash::operator==(const QHash &other) const -{ - if (d == other.d) - return true; - if (size() != other.size()) - return false; - - const_iterator it = begin(); - - while (it != end()) { - // Build two equal ranges for i.key(); one for *this and one for other. - // For *this we can avoid a lookup via equal_range, as we know the beginning of the range. - auto thisEqualRangeStart = it; - const Key &thisEqualRangeKey = it.key(); - size_type n = 0; - do { - ++it; - ++n; - } while (it != end() && it.key() == thisEqualRangeKey); - - const auto otherEqualRange = other.equal_range(thisEqualRangeKey); - - if (n != std::distance(otherEqualRange.first, otherEqualRange.second)) - return false; - - // Keys in the ranges are equal by construction; this checks only the values. - if (!qt_is_permutation(thisEqualRangeStart, it, otherEqualRange.first, otherEqualRange.second)) - return false; - } - - return true; -} - -template -QPair::iterator, typename QHash::iterator> QHash::equal_range(const Key &akey) -{ - detach(); - auto pair = qAsConst(*this).equal_range(akey); - return qMakePair(iterator(pair.first.i), iterator(pair.second.i)); -} - -template -QPair::const_iterator, typename QHash::const_iterator> QHash::equal_range(const Key &akey) const noexcept -{ - Node *node = *findNode(akey); - const_iterator firstIt = const_iterator(node); - - if (node != e) { - // equal keys must hash to the same value and so they all - // end up in the same bucket. So we can use node->next, - // which only works within a bucket, instead of (out-of-line) - // QHashData::nextNode() - while (node->next != e && node->next->key == akey) - node = node->next; - - // 'node' may be the last node in the bucket. To produce the end iterator, we'd - // need to enter the next bucket in this case, so we need to use - // QHashData::nextNode() here, which, unlike node->next above, can move between - // buckets. - node = concrete(QHashData::nextNode(reinterpret_cast(node))); - } - - return qMakePair(firstIt, const_iterator(node)); -} - -template -class QMultiHash : public QHash -{ public: - QMultiHash() noexcept {} + using key_type = Key; + using mapped_type = T; + using value_type = T; + using size_type = qsizetype; + using difference_type = qsizetype; + using reference = T &; + using const_reference = const T &; + + QMultiHash() noexcept = default; inline QMultiHash(std::initializer_list > list) + : d(new Data(list.size())) { - this->reserve(int(list.size())); for (typename std::initializer_list >::const_iterator it = list.begin(); it != list.end(); ++it) insert(it->first, it->second); } @@ -1028,293 +1222,681 @@ public: insert(f->first, f->second); } #endif - // compiler-generated copy/move ctors/assignment operators are fine! - // compiler-generated destructor is fine! + QMultiHash(const QMultiHash &other) noexcept + : d(other.d), m_size(other.m_size) + { + if (d) + d->ref.ref(); + } + ~QMultiHash() + { + if (d && !d->ref.deref()) + delete d; + } - QMultiHash(const QHash &other) : QHash(other) {} - QMultiHash(QHash &&other) noexcept : QHash(std::move(other)) {} - void swap(QMultiHash &other) noexcept { QHash::swap(other); } // prevent QMultiHash<->QHash swaps + QMultiHash &operator=(const QMultiHash &other) noexcept(std::is_nothrow_destructible::value) + { + if (d != other.d) { + Data *o = other.d; + if (o) + o->ref.ref(); + if (d && !d->ref.deref()) + delete d; + d = o; + m_size = other.m_size; + } + return *this; + } + QMultiHash(QMultiHash &&other) noexcept : d(other.d), m_size(other.m_size) + { + other.d = nullptr; + other.m_size = 0; + } + QMultiHash &operator=(QMultiHash &&other) noexcept(std::is_nothrow_destructible::value) + { + QMultiHash moved(std::move(other)); + swap(moved); + return *this; + } - inline typename QHash::iterator replace(const Key &key, const T &value) - { return QHash::insert(key, value); } + QMultiHash(const QHash &other) + : QMultiHash(other.begin(), other.end()) + {} + void swap(QMultiHash &other) noexcept { qSwap(d, other.d); qSwap(m_size, other.m_size); } - typename QHash::iterator insert(const Key &key, const T &value); + bool operator==(const QMultiHash &other) const noexcept + { + if (d == other.d) + return true; + if (!d || ! other.d) + return false; + if (m_size != other.m_size || d->size != other.d->size) + return false; + for (auto it = other.d->begin(); it != other.d->end(); ++it) { + auto i = d->find(it.node()->key); + if (i == d->end()) + return false; + Chain *e = it.node()->value; + while (e) { + Chain *oe = i.node()->value; + while (oe) { + if (oe->value == e->value) + break; + oe = oe->next; + } + if (!oe) + return false; + e = e->next; + } + } + // all values must be the same as size is the same + return true; + } + bool operator!=(const QMultiHash &other) const noexcept { return !(*this == other); } - inline QMultiHash &unite(const QMultiHash &other); + inline qsizetype size() const noexcept { return m_size; } + + inline bool isEmpty() const noexcept { return !m_size; } + + inline qsizetype capacity() const noexcept { return d ? qsizetype(d->numBuckets >> 1) : 0; } + void reserve(qsizetype size) + { + if (isDetached()) + d->rehash(size); + else + d = Data::detached(d, size_t(size)); + } + inline void squeeze() { reserve(0); } + + inline void detach() { if (!d || d->ref.isShared()) d = Data::detached(d); } + inline bool isDetached() const noexcept { return d && !d->ref.isShared(); } + bool isSharedWith(const QMultiHash &other) const noexcept { return d == other.d; } + + void clear() noexcept(std::is_nothrow_destructible::value) + { + if (d && !d->ref.deref()) + delete d; + d = nullptr; + m_size = 0; + } + + qsizetype remove(const Key &key) + { + if (isEmpty()) // prevents detaching shared null + return 0; + detach(); + + auto it = d->find(key); + if (it.isUnused()) + return 0; + qsizetype n = Node::freeChain(it.node()); + m_size -= n; + Q_ASSERT(m_size >= 0); + d->erase(it); + return n; + } + T take(const Key &key) + { + if (isEmpty()) // prevents detaching shared null + return T(); + detach(); + + auto it = d->find(key); + if (it.isUnused()) + return T(); + Q_ASSERT(it.node()->value.size()); + Chain *e = it.node()->value; + Q_ASSERT(e); + if (!e->next) + d->erase(it); + else + it.node()->value = e->next; + --m_size; + Q_ASSERT(m_size >= 0); + T t = std::move(e->value); + delete e; + return t; + } + + bool contains(const Key &key) const noexcept + { + if (!d) + return false; + return d->findNode(key) != nullptr; + } + + Key key(const T &value, const Key &defaultKey = Key()) const noexcept + { + if (d) { + auto i = d->begin(); + while (i != d->end()) { + Chain *e = i.node()->value; + if (e->contains(value)) + return i.node()->key; + ++i; + } + } + + return defaultKey; + } + T value(const Key &key, const T &defaultValue = T()) const noexcept + { + if (d) { + Node *n = d->findNode(key); + if (n) { + Q_ASSERT(n->value); + return n->value->value; + } + } + return defaultValue; + } + + T &operator[](const Key &key) + { + detach(); + Node *n = d->findAndInsertNode(key); + Q_ASSERT(n); + return n->value->value; + } + + const T operator[](const Key &key) const noexcept + { + return value(key); + } + + QVector uniqueKeys() const + { + QVector res; + if (d) { + auto i = d->begin(); + while (i != d->end()) { + res.append(i.node()->key); + ++i; + } + } + return res; + } + + QVector keys() const + { + return QVector(keyBegin(), keyEnd()); + } + QVector keys(const T &value) const + { + QVector res; + const_iterator i = begin(); + while (i != end()) { + if (i.value()->contains(value)) + res.append(i.key()); + ++i; + } + return res; + } + QVector values() const + { + return QVector(begin(), end()); + } + QVector values(const Key &key) const + { + QVector values; + if (d) { + Node *n = d->findNode(key); + if (n) { + Chain *e = n->value; + while (e) { + values.append(e->value); + e = e->next; + } + } + } + return values; + } + + class const_iterator; + + class iterator + { + using piter = typename QHashPrivate::iterator; + friend class const_iterator; + friend class QMultiHash; + piter i; + Chain **e = nullptr; + explicit inline iterator(piter it, Chain **entry = nullptr) noexcept : i(it), e(entry) + { + if (!it.atEnd() && !e) { + e = &it.node()->value; + Q_ASSERT(e && *e); + } + } + + public: + typedef std::forward_iterator_tag iterator_category; + typedef qptrdiff difference_type; + typedef T value_type; + typedef T *pointer; + typedef T &reference; + + constexpr iterator() noexcept = default; + + inline const Key &key() const noexcept { return i.node()->key; } + inline T &value() const noexcept { return (*e)->value; } + inline T &operator*() const noexcept { return (*e)->value; } + inline T *operator->() const noexcept { return &(*e)->value; } + inline bool operator==(const iterator &o) const noexcept { return e == o.e; } + inline bool operator!=(const iterator &o) const noexcept { return e != o.e; } + + inline iterator &operator++() noexcept { + Q_ASSERT(e && *e); + e = &(*e)->next; + Q_ASSERT(e); + if (!*e) { + ++i; + e = i.atEnd() ? nullptr : &i.node()->value; + } + return *this; + } + inline iterator operator++(int) noexcept { + iterator r = *this; + ++(*this); + return r; + } + + inline bool operator==(const const_iterator &o) const noexcept { return e == o.e; } + inline bool operator!=(const const_iterator &o) const noexcept { return e != o.e; } + }; + friend class iterator; + + class const_iterator + { + using piter = typename QHashPrivate::iterator; + friend class iterator; + friend class QMultiHash; + piter i; + Chain **e = nullptr; + explicit inline const_iterator(piter it, Chain **entry = nullptr) noexcept : i(it), e(entry) + { + if (!it.atEnd() && !e) { + e = &it.node()->value; + Q_ASSERT(e && *e); + } + } + + public: + typedef std::forward_iterator_tag iterator_category; + typedef qptrdiff difference_type; + typedef T value_type; + typedef const T *pointer; + typedef const T &reference; + + constexpr const_iterator() noexcept = default; + inline const_iterator(const iterator &o) noexcept : i(o.i), e(o.e) { } + + inline const Key &key() const noexcept { return i.node()->key; } + inline T &value() const noexcept { return (*e)->value; } + inline T &operator*() const noexcept { return (*e)->value; } + inline T *operator->() const noexcept { return &(*e)->value; } + inline bool operator==(const const_iterator &o) const noexcept { return e == o.e; } + inline bool operator!=(const const_iterator &o) const noexcept { return e != o.e; } + + inline const_iterator &operator++() noexcept { + Q_ASSERT(e && *e); + e = &(*e)->next; + Q_ASSERT(e); + if (!*e) { + ++i; + e = i.atEnd() ? nullptr : &i.node()->value; + } + return *this; + } + inline const_iterator operator++(int) noexcept + { + const_iterator r = *this; + ++(*this); + return r; + } + }; + friend class const_iterator; + + class key_iterator + { + const_iterator i; + + public: + typedef typename const_iterator::iterator_category iterator_category; + typedef qptrdiff difference_type; + typedef Key value_type; + typedef const Key *pointer; + typedef const Key &reference; + + key_iterator() noexcept = default; + explicit key_iterator(const_iterator o) noexcept : i(o) { } + + const Key &operator*() const noexcept { return i.key(); } + const Key *operator->() const noexcept { return &i.key(); } + bool operator==(key_iterator o) const noexcept { return i == o.i; } + bool operator!=(key_iterator o) const noexcept { return i != o.i; } + + inline key_iterator &operator++() noexcept { ++i; return *this; } + inline key_iterator operator++(int) noexcept { return key_iterator(i++);} + const_iterator base() const noexcept { return i; } + }; + + typedef QKeyValueIterator const_key_value_iterator; + typedef QKeyValueIterator key_value_iterator; + + // STL style + inline iterator begin() { detach(); return iterator(d->begin()); } + inline const_iterator begin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator cbegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline const_iterator constBegin() const noexcept { return d ? const_iterator(d->begin()): const_iterator(); } + inline iterator end() noexcept { return iterator(); } + inline const_iterator end() const noexcept { return const_iterator(); } + inline const_iterator cend() const noexcept { return const_iterator(); } + inline const_iterator constEnd() const noexcept { return const_iterator(); } + inline key_iterator keyBegin() const noexcept { return key_iterator(begin()); } + inline key_iterator keyEnd() const noexcept { return key_iterator(end()); } + inline key_value_iterator keyValueBegin() noexcept { return key_value_iterator(begin()); } + inline key_value_iterator keyValueEnd() noexcept { return key_value_iterator(end()); } + inline const_key_value_iterator keyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator constKeyValueBegin() const noexcept { return const_key_value_iterator(begin()); } + inline const_key_value_iterator keyValueEnd() const noexcept { return const_key_value_iterator(end()); } + inline const_key_value_iterator constKeyValueEnd() const noexcept { return const_key_value_iterator(end()); } + + iterator detach(const_iterator it) + { + auto i = it.i; + Chain **e = it.e; + if (d->ref.isShared()) { + // need to store iterator position before detaching + qsizetype n = 0; + Chain *entry = i.node()->value; + while (entry != *it.e) { + ++n; + entry = entry->next; + } + Q_ASSERT(entry); + detach_helper(); + + i = d->detachedIterator(i); + e = &i.node()->value; + while (n) { + e = &(*e)->next; + --n; + } + Q_ASSERT(e && *e); + } + return iterator(i, e); + } + + iterator erase(const_iterator it) + { + Q_ASSERT(d); + iterator i = detach(it); + Chain *e = *i.e; + Chain *next = e->next; + *i.e = next; + delete e; + if (!next) { + if (i.e == &i.i.node()->value) { + // last remaining entry, erase + i = iterator(d->erase(i.i)); + } else { + i = iterator(++it.i); + } + } + --m_size; + Q_ASSERT(m_size >= 0); + return i; + } + + // more Qt + typedef iterator Iterator; + typedef const_iterator ConstIterator; + inline qsizetype count() const noexcept { return size(); } + iterator find(const Key &key) + { + if (isEmpty()) + return end(); + detach(); + auto it = d->find(key); + if (it.isUnused()) + it = d->end(); + return iterator(it); + } + const_iterator find(const Key &key) const noexcept + { + return constFind(key); + } + const_iterator constFind(const Key &key) const noexcept + { + if (isEmpty()) + return end(); + auto it = d->find(key); + if (it.isUnused()) + it = d->end(); + return const_iterator(it); + } + iterator insert(const Key &key, const T &value) + { + detach(); + auto it = d->insertMulti(key, value); + ++m_size; + return iterator(it); + } + + float load_factor() const noexcept { return d ? d->loadFactor() : 0; } + static float max_load_factor() noexcept { return 0.5; } + size_t bucket_count() const noexcept { return d ? d->numBuckets : 0; } + static size_t max_bucket_count() noexcept { return QHashPrivate::GrowthPolicy::maxNumBuckets(); } + + inline bool empty() const noexcept { return isEmpty(); } + + inline typename QMultiHash::iterator replace(const Key &key, const T &value) + { + detach(); + qsizetype s = d->size; + auto it = d->insert(key, value); + m_size += d->size - s; + return iterator(it); + } inline QMultiHash &operator+=(const QMultiHash &other) - { return unite(other); } + { this->unite(other); return *this; } inline QMultiHash operator+(const QMultiHash &other) const { QMultiHash result = *this; result += other; return result; } - using QHash::contains; - using QHash::remove; - using QHash::count; - using QHash::find; - using QHash::constFind; - using QHash::values; - using QHash::findNode; - using QHash::createNode; - using QHash::concrete; - using QHash::detach; - - using typename QHash::Node; - using typename QHash::iterator; - using typename QHash::const_iterator; - - bool contains(const Key &key, const T &value) const; - - int remove(const Key &key, const T &value); - - int count(const Key &key, const T &value) const; - - QList uniqueKeys() const; - - QList values(const Key &akey) const; - - typename QHash::iterator find(const Key &key, const T &value) { - typename QHash::iterator i(find(key)); - typename QHash::iterator end(this->end()); - while (i != end && i.key() == key) { - if (i.value() == value) - return i; - ++i; - } - return end; + bool contains(const Key &key, const T &value) const noexcept + { + if (isEmpty()) + return false; + auto n = d->findNode(key); + if (n == nullptr) + return false; + return n->value->contains(value); } - typename QHash::const_iterator find(const Key &key, const T &value) const { - typename QHash::const_iterator i(constFind(key)); - typename QHash::const_iterator end(QHash::constEnd()); - while (i != end && i.key() == key) { - if (i.value() == value) - return i; - ++i; - } - return end; - } - typename QHash::const_iterator constFind(const Key &key, const T &value) const - { return find(key, value); } -private: - T &operator[](const Key &key); - const T operator[](const Key &key) const; -}; -template -Q_INLINE_TEMPLATE typename QHash::iterator QMultiHash::insert(const Key &akey, const T &avalue) -{ - detach(); - this->d->willGrow(); + qsizetype remove(const Key &key, const T &value) + { + if (isEmpty()) // prevents detaching shared null + return false; + detach(); - uint h; - Node **nextNode = findNode(akey, &h); - return iterator(createNode(h, akey, avalue, nextNode)); -} - -template -Q_INLINE_TEMPLATE QMultiHash &QMultiHash::unite(const QMultiHash &other) -{ - if (this->d == &QHashData::shared_null) { - *this = other; - } else { -#if QT_DEPRECATED_SINCE(5, 15) - QMultiHash copy(other); - const_iterator it = copy.constEnd(); - while (it != copy.constBegin()) { - it.i = QHashData::previousNode(it.i); - insert(it.key(), it.value()); - } -#else - const QMultiHash copy(other); - const_iterator it = copy.cbegin(); - const const_iterator end = copy.cend(); - while (it != end) { - const auto rangeStart = it++; - while (it != end && rangeStart.key() == it.key()) - ++it; - const qint64 last = std::distance(rangeStart, it) - 1; - for (qint64 i = last; i >= 0; --i) { - auto next = std::next(rangeStart, i); - insert(next.key(), next.value()); + auto it = d->find(key); + if (it.isUnused()) + return 0; + qsizetype n = 0; + Chain **e = &it.node()->value; + while (*e) { + Chain *entry = *e; + if (entry->value == value) { + *e = entry->next; + delete entry; + ++n; + } else { + e = &entry->next; } } -#endif + if (!it.node()->value) + d->erase(it); + m_size -= n; + Q_ASSERT(m_size >= 0); + return n; } - return *this; -} - -template -Q_INLINE_TEMPLATE bool QMultiHash::contains(const Key &key, const T &value) const -{ - return constFind(key, value) != QHash::constEnd(); -} - -template -Q_INLINE_TEMPLATE int QMultiHash::remove(const Key &key, const T &value) -{ - int n = 0; - typename QHash::iterator i(find(key)); - typename QHash::iterator end(QHash::end()); - while (i != end && i.key() == key) { - if (i.value() == value) { - i = this->erase(i); + qsizetype count(const Key &key) const noexcept + { + auto it = d->find(key); + if (it.isUnused()) + return 0; + qsizetype n = 0; + Chain *e = it.node()->value; + while (e) { ++n; - } else { + e = e->next; + } + + return n; + } + + qsizetype count(const Key &key, const T &value) const noexcept + { + auto it = d->find(key); + if (it.isUnused()) + return 0; + qsizetype n = 0; + Chain *e = it.node()->value; + while (e) { + if (e->value == value) + ++n; + e = e->next; + } + + return n; + } + + iterator find(const Key &key, const T &value) + { + detach(); + auto it = constFind(key, value); + return iterator(it.i, it.e); + } + const_iterator find(const Key &key, const T &value) const noexcept + { + return constFind(key, value); + } + const_iterator constFind(const Key &key, const T &value) const noexcept + { + const_iterator i(constFind(key)); + const_iterator end(constEnd()); + while (i != end && i.key() == key) { + if (i.value() == value) + return i; ++i; } + return end; } - return n; -} -template -Q_INLINE_TEMPLATE int QMultiHash::count(const Key &key, const T &value) const -{ - int n = 0; - typename QHash::const_iterator i(constFind(key)); - typename QHash::const_iterator end(QHash::constEnd()); - while (i != end && i.key() == key) { - if (i.value() == value) - ++n; - ++i; - } - return n; -} - -template -Q_OUTOFLINE_TEMPLATE QList QMultiHash::uniqueKeys() const -{ - QList res; - res.reserve(QHash::size()); // May be too much, but assume short lifetime - typename QHash::const_iterator i = QHash::begin(); - if (i != QHash::end()) { - for (;;) { - const Key &aKey = i.key(); - res.append(aKey); - do { - if (++i == QHash::end()) - goto break_out_of_outer_loop; - } while (aKey == i.key()); + QMultiHash &unite(const QMultiHash &other) + { + if (isEmpty()) { + *this = other; + } else if (other.isEmpty()) { + ; + } else { + QMultiHash copy(other); + detach(); + for (auto cit = copy.cbegin(); cit != copy.cend(); ++cit) + insert(cit.key(), *cit); } + return *this; } -break_out_of_outer_loop: - return res; -} -#if QT_DEPRECATED_SINCE(5, 15) - -template -Q_OUTOFLINE_TEMPLATE typename QHash::iterator QHash::insertMulti(const Key &key, const T &value) { - return static_cast *>(this)->insert(key, value); -} - -template -Q_OUTOFLINE_TEMPLATE QHash &QHash::unite(const QHash &other) { - return static_cast *>(this)->unite(other); -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::values(const Key &akey) const -{ - return static_cast *>(this)->values(akey); -} - -template -Q_OUTOFLINE_TEMPLATE QList QHash::uniqueKeys() const -{ - return static_cast *>(this)->uniqueKeys(); -} -#endif - -template -Q_OUTOFLINE_TEMPLATE QList QMultiHash::values(const Key &akey) const -{ - QList res; - Node *node = *findNode(akey); - if (node != this->e) { - do { - res.append(node->value); - } while ((node = node->next) != this->e && node->key == akey); + QPair equal_range(const Key &key) + { + detach(); + auto pair = qAsConst(*this).equal_range(key); + return qMakePair(iterator(pair.first.i), iterator(pair.second.i)); } - return res; -} + + QPair equal_range(const Key &key) const noexcept + { + auto it = d->find(key); + if (it.isUnused()) + return qMakePair(end(), end()); + auto end = it; + ++end; + return qMakePair(const_iterator(it), const_iterator(end)); + } + +private: + void detach_helper() + { + if (!d) { + d = new Data; + return; + } + Data *dd = new Data(*d); + if (!d->ref.deref()) + delete d; + d = dd; + } +}; #if !defined(QT_NO_JAVA_STYLE_ITERATORS) -template +template class QHashIterator { typedef typename QHash::const_iterator const_iterator; typedef const_iterator Item; QHash c; const_iterator i, n; - inline bool item_exists() const { return n != c.constEnd(); } + inline bool item_exists() const noexcept { return n != c.constEnd(); } public: - inline QHashIterator(const QHash &container) + inline QHashIterator(const QHash &container) noexcept : c(container), i(c.constBegin()), n(c.constEnd()) - { - } - inline QHashIterator &operator=(const QHash &container) + { } + inline QHashIterator &operator=(const QHash &container) noexcept { c = container; i = c.constBegin(); n = c.constEnd(); return *this; } - inline void toFront() + inline void toFront() noexcept { i = c.constBegin(); n = c.constEnd(); } - inline void toBack() + inline void toBack() noexcept { i = c.constEnd(); n = c.constEnd(); } - inline bool hasNext() const { return i != c.constEnd(); } - inline Item next() + inline bool hasNext() const noexcept { return i != c.constEnd(); } + inline Item next() noexcept { n = i++; return n; } - inline Item peekNext() const { return i; } - inline const T &value() const + inline Item peekNext() const noexcept { return i; } + inline const T &value() const noexcept { Q_ASSERT(item_exists()); return *n; } - inline const Key &key() const + inline const Key &key() const noexcept { Q_ASSERT(item_exists()); return n.key(); } - inline bool findNext(const T &t) + inline bool findNext(const T &t) noexcept { while ((n = i) != c.constEnd()) if (*i++ == t) return true; return false; } -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 bool hasPrevious() const { return i != c.constBegin(); } - inline QT_DEPRECATED_VERSION_5_15 Item previous() - { - n = --i; - return n; - } - inline QT_DEPRECATED_VERSION_5_15 Item peekPrevious() const - { - const_iterator p = i; - return --p; - } - inline bool QT_DEPRECATED_VERSION_5_15 findPrevious(const T &t) - { - while (i != c.constBegin()) - if (*(n = --i) == t) - return true; - n = c.constEnd(); - return false; - } -#endif }; template @@ -1325,10 +1907,11 @@ class QMutableHashIterator typedef iterator Item; QHash *c; iterator i, n; - inline bool item_exists() const { return const_iterator(n) != c->constEnd(); } + inline bool item_exists() const noexcept { return const_iterator(n) != c->constEnd(); } public: - inline QMutableHashIterator(QHash &container) : c(&container) + inline QMutableHashIterator(QHash &container) + : c(&container) { i = c->begin(); n = c->end(); @@ -1345,18 +1928,18 @@ public: i = c->begin(); n = c->end(); } - inline void toBack() + inline void toBack() noexcept { i = c->end(); n = c->end(); } - inline bool hasNext() const { return const_iterator(i) != c->constEnd(); } - inline Item next() + inline bool hasNext() const noexcept { return const_iterator(i) != c->constEnd(); } + inline Item next() noexcept { n = i++; return n; } - inline Item peekNext() const { return i; } + inline Item peekNext() const noexcept { return i; } inline void remove() { if (const_iterator(n) != c->constEnd()) { @@ -1369,49 +1952,28 @@ public: if (const_iterator(n) != c->constEnd()) *n = t; } - inline T &value() + inline T &value() noexcept { Q_ASSERT(item_exists()); return *n; } - inline const T &value() const + inline const T &value() const noexcept { Q_ASSERT(item_exists()); return *n; } - inline const Key &key() const + inline const Key &key() const noexcept { Q_ASSERT(item_exists()); return n.key(); } - inline bool findNext(const T &t) + inline bool findNext(const T &t) noexcept { while (const_iterator(n = i) != c->constEnd()) if (*i++ == t) return true; return false; } -#if QT_DEPRECATED_SINCE(5, 15) - inline QT_DEPRECATED_VERSION_5_15 bool hasPrevious() const { return const_iterator(i) != c->constBegin(); } - inline QT_DEPRECATED_VERSION_5_15 Item previous() - { - n = --i; - return n; - } - inline QT_DEPRECATED_VERSION_5_15 Item peekPrevious() const - { - iterator p = i; - return --p; - } - inline QT_DEPRECATED_VERSION_5_15 bool findPrevious(const T &t) - { - while (const_iterator(i) != c->constBegin()) - if (*(n = --i) == t) - return true; - n = c->end(); - return false; - } -#endif }; #endif // !QT_NO_JAVA_STYLE_ITERATORS @@ -1438,8 +2000,4 @@ inline uint qHash(const QMultiHash &key, uint seed = 0) QT_END_NAMESPACE -#if defined(Q_CC_MSVC) -#pragma warning( pop ) -#endif - #endif // QHASH_H diff --git a/src/corelib/tools/qset.h b/src/corelib/tools/qset.h index ef363ed52d..5b7aa8fa28 100644 --- a/src/corelib/tools/qset.h +++ b/src/corelib/tools/qset.h @@ -281,24 +281,14 @@ Q_INLINE_TEMPLATE bool QSet::intersects(const QSet &other) const const bool otherIsBigger = other.size() > size(); const QSet &smallestSet = otherIsBigger ? *this : other; const QSet &biggestSet = otherIsBigger ? other : *this; - const bool equalSeeds = q_hash.d->seed == other.q_hash.d->seed; typename QSet::const_iterator i = smallestSet.cbegin(); typename QSet::const_iterator e = smallestSet.cend(); - if (Q_LIKELY(equalSeeds)) { - // If seeds are equal we take the fast path so no hash is recalculated. - while (i != e) { - if (*biggestSet.q_hash.findNode(*i, i.i.i->h) != biggestSet.q_hash.e) - return true; - ++i; - } - } else { - while (i != e) { - if (biggestSet.contains(*i)) - return true; - ++i; - } - } + while (i != e) { + if (biggestSet.contains(*i)) + return true; + ++i; + } return false; } diff --git a/src/corelib/tools/qsharedpointer.cpp b/src/corelib/tools/qsharedpointer.cpp index 0576fb2bd0..499546da4b 100644 --- a/src/corelib/tools/qsharedpointer.cpp +++ b/src/corelib/tools/qsharedpointer.cpp @@ -1630,7 +1630,7 @@ void QtSharedPointer::internalSafetyCheckCleanCheck() qFatal("Internal consistency error: the number of pointers is not equal!"); if (Q_UNLIKELY(!kp->dPointers.isEmpty())) - qFatal("Pointer cleaning failed: %d entries remaining", kp->dPointers.size()); + qFatal("Pointer cleaning failed: %d entries remaining", int(kp->dPointers.size())); # endif } diff --git a/tests/auto/corelib/tools/collections/tst_collections.cpp b/tests/auto/corelib/tools/collections/tst_collections.cpp index 6fcf726c21..456c3e6cf9 100644 --- a/tests/auto/corelib/tools/collections/tst_collections.cpp +++ b/tests/auto/corelib/tools/collections/tst_collections.cpp @@ -1286,19 +1286,13 @@ void tst_Collections::hash() QHash hash; for (int i = 0; i < 1000; ++i) hash.insert(i, i); - QVERIFY(hash.capacity() == 1031); - hash.squeeze(); - QVERIFY(hash.capacity() == 521); - - hash.insert(12345, 12345); - QVERIFY(hash.capacity() == 1031); + QVERIFY(hash.capacity() > 1000); for (int j = 0; j < 900; ++j) hash.remove(j); - QVERIFY(hash.capacity() == 257); + QVERIFY(hash.capacity() > 1000); hash.squeeze(); - QVERIFY(hash.capacity() == 67); - hash.reserve(0); + QVERIFY(hash.capacity() < 200); } } @@ -1331,13 +1325,21 @@ void tst_Collections::hash() hash1.unite(hash2); QCOMPARE(hash1.size(), 5); - QCOMPARE(hash1.values(), - (QList() << "Gamma" << "Gamma" << "Beta" << "Gamma" << "Alpha")); + auto values = hash1.values(); + qSort(values); + QList expected; + expected << "Gamma" << "Gamma" << "Beta" << "Gamma" << "Alpha"; + qSort(expected); + QCOMPARE(values, expected); hash2 = hash1; hash2.unite(hash2); QCOMPARE(hash2.size(), 10); - QCOMPARE(hash2.values(), hash1.values() + hash1.values()); + values = hash2.values(); + qSort(values); + expected += expected; + qSort(expected); + QCOMPARE(values, expected); } } diff --git a/tests/auto/corelib/tools/qhash/tst_qhash.cpp b/tests/auto/corelib/tools/qhash/tst_qhash.cpp index 2a18f8d3e6..b987adaa3f 100644 --- a/tests/auto/corelib/tools/qhash/tst_qhash.cpp +++ b/tests/auto/corelib/tools/qhash/tst_qhash.cpp @@ -62,7 +62,6 @@ private slots: void keyIterator(); void keyValueIterator(); void keys_values_uniqueKeys(); // slightly modified from tst_QMap - void noNeedlessRehashes(); void const_shared_null(); void twoArguments_qHash(); @@ -70,6 +69,8 @@ private slots: void eraseValidIteratorOnSharedHash(); void equal_range(); void insert_hash(); + + void badHashFunction(); }; struct IdentityTracker { @@ -1325,20 +1326,6 @@ void tst_QHash::keys_values_uniqueKeys() QVERIFY(sorted(hash.values()) == sorted(QList() << 2 << 1 << 4 << -2)); } -void tst_QHash::noNeedlessRehashes() -{ - QHash hash; - for (int i = 0; i < 512; ++i) { - int j = (i * 345) % 512; - hash.insert(j, j); - int oldCapacity = hash.capacity(); - hash[j] = j + 1; - QCOMPARE(oldCapacity, hash.capacity()); - hash.insert(j, j + 1); - QCOMPARE(oldCapacity, hash.capacity()); - } -} - void tst_QHash::const_shared_null() { QHash hash2; @@ -1663,5 +1650,33 @@ void tst_QHash::insert_hash() } } +struct BadKey { + int k; + BadKey(int i) : k(i) {} + bool operator==(const BadKey &other) const + { + return k == other.k; + } +}; + +size_t qHash(BadKey, size_t seed) +{ + return seed; +} + +void tst_QHash::badHashFunction() +{ + QHash hash; + for (int i = 0; i < 10000; ++i) + hash.insert(i, i); + + for (int i = 0; i < 10000; ++i) + QCOMPARE(hash.value(i), i); + + for (int i = 10000; i < 20000; ++i) + QVERIFY(!hash.contains(i)); + +} + QTEST_APPLESS_MAIN(tst_QHash) #include "tst_qhash.moc" diff --git a/tests/auto/corelib/tools/qset/tst_qset.cpp b/tests/auto/corelib/tools/qset/tst_qset.cpp index 0ca8be7fa3..21ceee87c0 100644 --- a/tests/auto/corelib/tools/qset/tst_qset.cpp +++ b/tests/auto/corelib/tools/qset/tst_qset.cpp @@ -247,21 +247,24 @@ void tst_QSet::squeeze() set.squeeze(); QVERIFY(set.capacity() < 100); - for (int i = 0; i < 500; ++i) + for (int i = 0; i < 512; ++i) set.insert(i); - QVERIFY(set.capacity() >= 500 && set.capacity() < 10000); + QVERIFY(set.capacity() == 512); set.reserve(50000); QVERIFY(set.capacity() >= 50000); set.squeeze(); - QVERIFY(set.capacity() < 500); + QVERIFY(set.capacity() == 512); set.remove(499); - QVERIFY(set.capacity() < 500); + QVERIFY(set.capacity() == 512); set.insert(499); - QVERIFY(set.capacity() >= 500); + QVERIFY(set.capacity() == 512); + + set.insert(1000); + QVERIFY(set.capacity() == 1024); for (int i = 0; i < 500; ++i) set.remove(i); @@ -495,13 +498,13 @@ void tst_QSet::end() QVERIFY(i == j); QVERIFY(k == ell); - QVERIFY(i != k); - QVERIFY(j != ell); QVERIFY(set1.constBegin() != set1.constEnd()); QVERIFY(set2.constBegin() == set2.constEnd()); + QVERIFY(set1.constBegin() != set2.constBegin()); } + set2 = set1; {