Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding what QHash does when key not found

Tags:

c++

key

hash

qt

qhash

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();
}
like image 421
rbaleksandar Avatar asked Feb 05 '23 23:02

rbaleksandar


1 Answers

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.

like image 77
Hayt Avatar answered Feb 08 '23 13:02

Hayt