Note: You can find a minimal working example at the end of this post.
I'm using Qt 5.7. Let's say I have the following QHash
:
QHash<HashKey, HashValue> hm;
with
enum HashKey {
K1,
K2,
K3,
K4,
K5
}
and
class HashValue {
public:
int x;
HashValue(int x) {
this->x = x;
}
}
I have initialized the hash map like this:
hm.insert(K1, HashValue((int)K1));
hm.insert(K2, HashValue((int)K2));
hm.insert(K3, HashValue((int)K3));
hm.insert(K4, HashValue((int)K4));
hm.insert(K5, HashValue((int)K5));
I have tested it by calling
cout << hm.value(K4).x << endl;
cout << hm.find(K4).value().x << endl;
Both return the same result that is 3
. Now I tried doing the same with a key that is not part of the hash map by casting an integer to HashKey
and calling the above two methods on it:
cout << hm.value(static_cast<HashKey>(100)).x << endl;
cout << hm.find(static_cast<HashKey>(100)).value().x << endl;
What I got is 8
(for the first call with value().x
) and 5
(for the second call with find(...).value().x
)
The docs states that
If there is no item with the specified key in the hash, these functions return a default-constructed value.
I followed the link for default-constructed value
and got the following:
[...] for example, QVector automatically initializes its items with default-constructed values, and QMap::value() returns a default-constructed value if the specified key isn't in the map. For most value types, this simply means that a value is created using the default constructor (e.g. an empty string for QString). But for primitive types like int and double, as well as for pointer types, the C++ language doesn't specify any initialization; in those cases, Qt's containers automatically initialize the value to 0.
In my case this would mean a HashValue()
call. However the fact that I get different results is baffling to say the least. I would expect to get the same result though the docs don't mention what find(...)
does when an invalid key is passed as argument. All it says it finds the first occurrence of that key and returns an iterator (obviously since I call value()
on it in the call above).
The quoted doc snippet from above is followed (again back to the document for QHash
) by
If you want to check whether the hash contains a particular key, use contains()
I can deal with having to call contains()
every time I query my hash map though this means making two function calls - first to check if key is present and then to call value(...)
to get the actual value if a valid entry is found. The call below returns "Key 100 not found"
:
cout << (hm.contains(static_cast<HashKey>(100)) ? "Key 100 found" : "Key 100 not found") << endl;
I would expect this check to be done internally but obviously this doesn't happen (my guess would be to prevent some performance impact on the querying functionality of this container).
The question here is why is all this happening and what is actually happening underneath all that?
Here is the project and the code for it:
HashTest.pro
QT += core
QT += gui
CONFIG += c++11
TARGET = HashTest
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
main.cpp
#include <QCoreApplication>
#include <QHash>
#include <iostream>
using namespace std;
enum HashKey {
K1 = 0,
K2 = 1,
K3 = 2,
K4 = 3,
K5 = 4
};
class HashValue {
public:
int x;
HashValue(int x) { this->x = x; }
HashValue() {}
};
int main(int argc, char *argv[])
{
QHash<HashKey, HashValue> hm;
hm.insert(K1, HashValue((int)K1));
hm.insert(K2, HashValue((int)K2));
hm.insert(K3, HashValue((int)K3));
hm.insert(K4, HashValue((int)K4));
hm.insert(K5, HashValue((int)K5));
cout << hm.value(K4).x << endl;
cout << hm.value(static_cast<HashKey>(100)).x << endl;
cout << hm.find(K4).value().x << endl;
cout << hm.find(static_cast<HashKey>(100)).value().x << endl;
cout << (hm.contains(static_cast<HashKey>(100)) ? "Key 100 found" : "Key 100 not found") << endl;
return a.exec();
}
The value()
function is basically just for accessing values not checking if you have a one.
It returns a value and there is no way to indicate whether the value is "invalid" or not. So the choice if the design was to construct one. Qt could as an alternative throw an exception but this is not done here for several reasons (same as the containers of the c++ standard library btw.).
Secondly:
You are kind of using find()
in a wrong way.
With find
you can check whether the key is in the list and if not it point to the end()
iterator of the hash.
QHash< Key,Value >::const_iterator valueIt = hash.find(<something>)
if(valueIt == hash.end())
{ // not found. error handling etc.
}
Value value = valueIt.value();
This is usually the "standard" way to check if a key exists and access it in a Map/Hash/Set/....
So when you use
find(...).value();
you could possibly access the end()
iterator which causes undefined behavior.