Понимание того, что делает QHash, когда ключ не найден

Примечание. Вы можете найти минимальный рабочий пример в конце этого поста.

Я использую Qt 5.7. Допустим, у меня есть следующие QHash:

QHash<HashKey, HashValue> hm;

с участием

enum HashKey {
    K1,
    K2,
    K3,
    K4,
    K5
}

а также

class HashValue {
    public:
        int x;
        HashValue(int x) {
            this->x = x;
        }
}

Я инициализировал хеш-карту следующим образом:

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.find(K4).value().x << endl;

Оба возвращают тот же результат, что и 3. Теперь я попытался сделать то же самое с ключом, который не является частью хэш-карты, приведя целое число к HashKey и вызвав для него два вышеуказанных метода:

cout << hm.value(static_cast<HashKey>(100)).x << endl;
cout << hm.find(static_cast<HashKey>(100)).value().x << endl;

Я получил 8 (за первый звонок с value().x) и 5 (за второй звонок с find(...).value().x)

В документах указано, что

Если в хэше нет элемента с указанным ключом, эти функции возвращают значение по умолчанию.

Я перешел по ссылке для default-constructed value и получил следующее:

[...] например, QVector автоматически инициализирует свои элементы значениями, созданными по умолчанию, а QMap::value() возвращает значение, созданное по умолчанию, если указанный ключ отсутствует на карте. Для большинства типов значений это просто означает, что значение создается с использованием конструктора по умолчанию (например, пустая строка для QString). Но для примитивных типов, таких как int и double, а также для типов указателей язык C++ не определяет никакой инициализации; в таких случаях контейнеры Qt автоматически инициализируют значение равным 0.

В моем случае это будет означать вызов HashValue(). Однако тот факт, что я получаю разные результаты, мягко говоря, сбивает с толку. Я ожидаю получить тот же результат, хотя в документах не упоминается, что делает find(...), когда в качестве аргумента передается недопустимый ключ. Все, что он говорит, он находит первое вхождение этого ключа и возвращает итератор (очевидно, поскольку я вызываю value() в вызове выше).

За цитируемым выше фрагментом документа следует (снова возвращаясь к документу для QHash)

Если вы хотите проверить, содержит ли хэш конкретный ключ, используйте contains()

Я могу справиться с необходимостью вызывать contains() каждый раз, когда я запрашиваю свою хеш-карту, хотя это означает выполнение двух вызовов функций — сначала для проверки наличия ключа, а затем для вызова value(...) для получения фактического значения, если найдена действительная запись. Приведенный ниже вызов возвращает "Key 100 not found":

cout << (hm.contains(static_cast<HashKey>(100)) ? "Key 100 found" : "Key 100 not found") << endl;

Я ожидаю, что эта проверка будет выполнена внутри, но, очевидно, этого не происходит (моя догадка заключается в том, чтобы предотвратить некоторое влияние производительности на функции запросов этого контейнера).

Вопрос здесь в том, почему все это происходит и что на самом деле происходит под всем этим?

Вот проект и код для него:

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();
}

person rbaleksandar    schedule 11.10.2016    source источник


Ответы (1)


Функция value() в основном предназначена только для доступа к значениям, не проверяя, есть ли они у вас.

Он возвращает значение, и нет способа указать, является ли значение «недействительным» или нет. Таким образом, выбор, если дизайн должен был построить один. В качестве альтернативы Qt может генерировать исключение, но здесь этого не делается по нескольким причинам (так же, как и контейнеры стандартной библиотеки С++, кстати).

Во-вторых:

Вы как бы неправильно используете find().

С помощью find вы можете проверить, находится ли ключ в списке, и если нет, он указывает на end() итератор хеша.

QHash< Key,Value >::const_iterator valueIt = hash.find(<something>)
if(valueIt == hash.end())
{  // not found. error handling etc. 
}
Value value = valueIt.value();

Обычно это «стандартный» способ проверить, существует ли ключ, и получить к нему доступ в Map/Hash/Set/....

Итак, когда вы используете

find(...).value();

вы можете получить доступ к итератору end(), который вызывает неопределенное поведение.

person Hayt    schedule 11.10.2016
comment
Спасибо за подробный ответ. ^_^ Я думал о неопределенном поведении из-за разных значений, которые я получал, и я думаю, что это действительно так. Поэтому я буду использовать contains() или find() так, как вы показали. - person rbaleksandar; 11.10.2016
comment
@rbaleksandar, если вы хотите использовать значение find. При этом вам нужно искать только один раз. С contains, а затем value (или find) у вас будет 2 поиска. - person Hayt; 11.10.2016
comment
Это было то, чего я боялся. Итак, find() это так. :) - person rbaleksandar; 11.10.2016
comment
Это точно. Я только что написал транспилятор своего кода Qt на другой язык и обнаружил ошибку 5-летней давности, потому что этот язык сообщил о моем отсутствующем значении в хеше. Кажется, это никогда не вызывало никаких проблем, но кажется, что Qt просто молча вернул ноль. Удивительно! - person Dan; 13.10.2018